Skip to content

Commit

Permalink
feat(frontend): implement new post message verification ui (#302)
Browse files Browse the repository at this point in the history
  • Loading branch information
634750802 authored Sep 23, 2024
1 parent 01553a4 commit 0036ba2
Show file tree
Hide file tree
Showing 25 changed files with 361 additions and 193 deletions.
4 changes: 4 additions & 0 deletions frontend/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"@types/is-hotkey": "^0.1.10",
"@types/jest": "^29.5.12",
"@types/js-cookie": "^3.0.6",
"@types/mdast": "^4.0.4",
"@types/node": "^20.14.15",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand All @@ -111,6 +112,7 @@
"filesize": "^10.1.4",
"framer-motion": "^11.3.28",
"git-revision-webpack-plugin": "^5.0.0",
"hastscript": "^9.0.0",
"highlight.js": "^11.10.0",
"is-hotkey": "^0.2.0",
"jest": "^29.7.0",
Expand All @@ -125,6 +127,7 @@
"react-json-view-lite": "^1.4.0",
"react-lowlight": "^3.0.0",
"react-textarea-autosize": "^8.5.3",
"remark-directive": "^3.0.0",
"sass": "^1.77.8",
"sql-formatter": "^15.4.0",
"storybook": "^8.2.9",
Expand All @@ -134,6 +137,7 @@
"ts-node": "^10.9.2",
"typescript": "^5.5.4",
"undici": "^6.19.7",
"unist-util-visit": "^5.0.0",
"yaml-loader": "^0.8.1"
},
"imports": {
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/public/chats.mock.txt

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions frontend/app/src/api/chat-engines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface ChatEngine {
export interface ChatEngineOptions {
knowledge_graph: ChatEngineKnowledgeGraphOptions;
llm: ChatEngineLLMOptions;
post_verification_url?: string | null;
post_verification_token?: string | null;
}

export interface ChatEngineKnowledgeGraphOptions {
Expand Down Expand Up @@ -64,6 +66,8 @@ const llmOptionsSchema =
const chatEngineOptionsSchema = z.object({
knowledge_graph: kgOptionsSchema,
llm: llmOptionsSchema,
post_verification_url: z.string().nullable().optional(),
post_verification_token: z.string().nullable().optional(),
}) satisfies ZodType<ChatEngineOptions, any, any>;

const chatEngineSchema = z.object({
Expand Down
6 changes: 5 additions & 1 deletion frontend/app/src/api/chats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { z, type ZodType } from 'zod';

const mockChat = !!process.env.NEXT_PUBLIC_MOCKING_CHAT;

type ClientEngineOptions = Omit<ChatEngineOptions, 'post_verification_token'>;

export interface Chat {
title: string;
engine_id: number;
engine_options: ChatEngineOptions;
engine_options: ClientEngineOptions;
deleted_at: Date | null;
user_id: string | null;
browser_id: string | null;
Expand Down Expand Up @@ -44,6 +46,7 @@ export interface ChatMessage {
content: string;
sources: ChatMessageSource[];
chat_id: string;
post_verification_result_url: string | null;
}

export interface ChatMessageSource {
Expand Down Expand Up @@ -84,6 +87,7 @@ export const chatMessageSchema = z.object({
content: z.string(),
sources: chatMessageSourceSchema.array(),
chat_id: z.string(),
post_verification_result_url: z.string().url().nullable(),
}) satisfies ZodType<ChatMessage, any, any>;

const chatDetailSchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default async function RootLayout ({
const _experimentalFeatures = experimentalFeatures();

if (!settings.enable_post_verifications) {
delete _experimentalFeatures.message_verify_service;
_experimentalFeatures.enable_message_post_verification = false;
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { EditBooleanForm } from '@/components/chat-engine/edit-boolean-form';
import { EditLlmForm } from '@/components/chat-engine/edit-llm-form';
import { EditNameForm } from '@/components/chat-engine/edit-name-form';
import { EditRerankerForm } from '@/components/chat-engine/edit-reranker-form';
import { EditTokenForm } from '@/components/chat-engine/edit-token-form';
import { EditUrlForm } from '@/components/chat-engine/edit-url-form';
import { LlmInfo } from '@/components/llm/LlmInfo';
import { OptionDetail } from '@/components/option-detail';
import { RerankerInfo } from '@/components/reranker/RerankerInfo';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { ChatEngine, ChatEngineOptions } from '@/api/chat-engines';
import { EditTokenForm } from '@/components/chat-engine/edit-token-form';
import { EditUrlForm } from '@/components/chat-engine/edit-url-form';
import { ChatEngineKnowledgeGraphDetails } from '@/components/chat-engine/knowledge-graph-details';
import { ChatEngineLLMDetails } from '@/components/chat-engine/llm-details';
import { OptionDetail } from '@/components/option-detail';
import { Separator } from '@/components/ui/separator';

export function ChatEngineOptionsDetails ({
Expand All @@ -16,15 +19,19 @@ export function ChatEngineOptionsDetails ({
<>
<section className="space-y-2">
<div className="text-base font-medium">Knowledge Graph</div>
<div className="space-y-2 text-sm">
<ChatEngineKnowledgeGraphDetails detailed={detailed} editable={editable} options={options.knowledge_graph} />
</div>
<ChatEngineKnowledgeGraphDetails detailed={detailed} editable={editable} options={options.knowledge_graph} />
</section>
<Separator />
<section className="space-y-2">
<div className="text-base font-medium">LLM</div>
<ChatEngineLLMDetails editable={editable} options={options.llm} />
</section>
<Separator />
<section className="space-y-2">
<div className="text-base font-medium">Post Verification</div>
<div className="space-y-2 text-sm">
<ChatEngineLLMDetails editable={editable} options={options.llm} />
<OptionDetail title="Post Validation URL" value={options.post_verification_url} editPanel={editable && <EditUrlForm chatEngine={editable} property="post_verification_url" />} />
<OptionDetail title="Post Validation Token" value="[HIDDEN]" editPanel={editable && <EditTokenForm chatEngine={editable} property="post_verification_token" />} />
</div>
</section>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function EditPropertyForm<T, P extends keyof T & string> ({ className, ob
resolver,
disabled,
defaultValues: {
[property]: object[property],
[property]: object[property] ?? '',
} as DefaultValues<Record<P, T[P]>>,
});

Expand Down
39 changes: 39 additions & 0 deletions frontend/app/src/components/chat-engine/edit-token-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';

import { type ChatEngine, type ChatEngineOptions, updateChatEngine } from '@/api/chat-engines';
import { EditPropertyForm } from '@/components/chat-engine/edit-property-form';
import { FormInput } from '@/components/form/control-widget';
import { useRefresh } from '@/components/nextjs/app-router-hooks';
import { toast } from 'sonner';
import { z } from 'zod';

type KeyOfType<T, Value> = keyof { [P in keyof T as T[P] extends Value ? P : never]: any }

export interface EditNameFormProps<P extends KeyOfType<ChatEngineOptions, string | undefined | null>> {
property: P;
chatEngine: ChatEngine;
}

const stringSchema = z.string();

export function EditTokenForm<P extends KeyOfType<ChatEngineOptions, string | undefined | null>> ({ property, chatEngine }: EditNameFormProps<P>) {
const [refreshing, refresh] = useRefresh();

return (
<EditPropertyForm
inline
object={chatEngine.engine_options}
property={property}
schema={stringSchema}
onSubmit={async (data) => {
const options = { ...chatEngine.engine_options, ...data };
await updateChatEngine(chatEngine.id, { engine_options: options });
refresh();
toast(`ChatEngine's ${property} successfully updated.`);
}}
disabled={refreshing}
>
<FormInput type="password" />
</EditPropertyForm>
);
}
39 changes: 39 additions & 0 deletions frontend/app/src/components/chat-engine/edit-url-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';

import { type ChatEngine, type ChatEngineOptions, updateChatEngine } from '@/api/chat-engines';
import { EditPropertyForm } from '@/components/chat-engine/edit-property-form';
import { FormInput } from '@/components/form/control-widget';
import { useRefresh } from '@/components/nextjs/app-router-hooks';
import { toast } from 'sonner';
import { z } from 'zod';

type KeyOfType<T, Value> = keyof { [P in keyof T as T[P] extends Value ? P : never]: any }

export interface EditNameFormProps<P extends KeyOfType<ChatEngineOptions, string | undefined | null>> {
property: P;
chatEngine: ChatEngine;
}

const stringSchema = z.string().url();

export function EditUrlForm<P extends KeyOfType<ChatEngineOptions, string | undefined | null>> ({ property, chatEngine }: EditNameFormProps<P>) {
const [refreshing, refresh] = useRefresh();

return (
<EditPropertyForm
inline
object={chatEngine.engine_options}
property={property}
schema={stringSchema}
onSubmit={async (data) => {
const options = { ...chatEngine.engine_options, ...data };
await updateChatEngine(chatEngine.id, { engine_options: options });
refresh();
toast(`ChatEngine's option ${property} successfully updated.`);
}}
disabled={refreshing}
>
<FormInput />
</EditPropertyForm>
);
}
2 changes: 2 additions & 0 deletions frontend/app/src/components/chat/chat-stream.state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ it('pass', () => {
'error': null,
'sources': [],
'chat_id': '019091e3-5cfc-74a3-b5e0-653a73f52af2',
"post_verification_result_url": null,
},
'assistant_message': {
'id': 60034,
Expand All @@ -38,6 +39,7 @@ it('pass', () => {
'ordinal': 2,
'content': '### Comprehensive Overview of TiDB\n\n#### What is TiDB?\nTiDB is an open-source distributed SQL database designed to support Hybrid Transactional and Analytical Processing (HTAP) workloads. It is MySQL-compatible and offers horizontal scalability, strong consistency, and high availability[^1][^2]. TiDB aims to provide a one-stop solution for OLTP (Online Transactional Processing), OLAP (Online Analytical Processing), and HTAP services, making it suitable for various use cases that require high availability and strong consistency with large-scale data[^3].\n\n#### Architecture of TiDB\nThe TiDB cluster consists of three main components[^4][^5]:\n1. **TiDB Server**: Handles SQL parsing, query planning, and execution.\n2. **TiKV Server**: Acts as the distributed key-value storage engine, storing the actual data.\n3. **PD (Placement Driver) Server**: Manages cluster metadata, allocates timestamps, and handles data placement and load balancing.\n\nAdditionally, TiDB includes:\n- **TiFlash**: A columnar storage engine for analytical workloads, providing high concurrency for `INSERT` and `UPDATE` operations without impacting OLTP performance[^6].\n- **TiSpark**: A connector that enables Spark to access data stored in TiDB[^7].\n- **TiDB Binlog**: A tool for capturing and replicating data changes[^8].\n- **TiDB Lightning**: A high-performance tool for importing data into TiDB[^9].\n\n#### Key Features of TiDB\n1. **Horizontal Scalability**: TiDB allows for easy horizontal scaling of both computing and storage resources, making it adaptable to changing workloads[^10]. The architecture separates computing from storage, enabling independent scaling[^11].\n2. **High Availability**: TiDB ensures high availability through data replication and the Multi-Raft protocol, guaranteeing data integrity even in the event of failures[^12]. It supports automatic failover when a minority of replicas fail, making it transparent to applications[^13].\n3. **HTAP Capabilities**: TiDB supports both row-based (TiKV) and columnar (TiFlash) storage engines, enabling real-time processing of both transactional and analytical workloads[^14].\n4. **Cloud-Native Design**: TiDB is built for cloud environments, offering flexible scalability, reliability, and security on various cloud platforms[^15]. It integrates seamlessly with Kubernetes and offers a fully-managed service (TiDB Cloud)[^16].\n5. **MySQL Compatibility**: TiDB is compatible with the MySQL 5.7 protocol and ecosystem, allowing for easy migration of applications with minimal code changes[^17]. However, it does not support triggers, stored procedures, and user-defined functions[^18].\n\n#### Ensuring High Availability, Scalability, and Performance\n- **High Availability**: TiDB achieves high availability through its multi-replica architecture and the Multi-Raft protocol, which ensures that data is consistently replicated across multiple nodes[^19]. Transactions can only be committed when data has been successfully written into the majority of replicas[^20].\n- **Scalability**: TiDB\'s architecture allows for flexible and elastic scaling by separating computing from storage. This design enables users to scale out or scale in the computing or storage capacity online as needed[^21].\n- **Performance**: TiDB provides high performance through various optimizations, including the use of TiFlash for analytical workloads and the DeltaTree structure for efficient data modification[^22]. The system also supports distributed transactions using a two-phase commit protocol with optimizations inspired by Google\'s Percolator[^23].\n\n#### Compatibility with MySQL\nTiDB supports most MySQL 5.7 syntax and features, making it highly compatible with MySQL applications[^24]. This compatibility allows users to migrate applications to TiDB without changing a single line of code in many cases[^25]. However, certain features like triggers, stored procedures, and user-defined functions are not supported[^26].\n\n### Conclusion\nTiDB is a robust, scalable, and high-performance distributed SQL database designed for modern data workloads. Its architecture, key features, and compatibility with MySQL make it a versatile solution for various use cases, ensuring high availability, scalability, and performance.\n\n[^1]: [TiDB Overview | PingCAP Docs](https://docs.pingcap.com/tidb/stable/overview)\n[^2]: [TiDB Introduction | PingCAP Docs](https://docs.pingcap.com/tidb/v5.4/overview)\n[^3]: [TiDB Introduction and Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.1/tidb-faq)\n[^4]: [TiDB Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-architecture)\n[^5]: [TiDB Introduction and Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v6.5/tidb-faq)\n[^6]: [TiDB Introduction and Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-faq)\n[^7]: [TiDB Architecture | PingCAP Docs](https://docs.pingcap.com/tidbcloud/tidb-architecture)\n[^8]: [TiDB Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-architecture)\n[^9]: [TiDB Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-architecture)\n[^10]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v6.5/overview)\n[^11]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v5.4/overview)\n[^12]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v7.1/overview)\n[^13]: [TiDB Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-architecture)\n[^14]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v6.5/overview)\n[^15]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v7.1/overview)\n[^16]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v6.5/overview)\n[^17]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v7.1/overview)\n[^18]: [TiDB Introduction and Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-faq)\n[^19]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v5.4/overview)\n[^20]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v6.5/overview)\n[^21]: [TiDB Key Features | PingCAP Docs](https://docs.pingcap.com/tidb/v7.1/overview)\n[^22]: [TiDB Introduction and Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-faq)\n[^23]: [TiDB Introduction and Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v6.5/tidb-faq)\n[^24]: [TiDB Introduction and Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-faq)\n[^25]: [TiDB Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-architecture)\n[^26]: [TiDB Introduction and Architecture | PingCAP Docs](https://docs.pingcap.com/tidb/v7.5/tidb-faq)',
'error': null,
"post_verification_result_url": null,
'sources': [
{
'id': 8247,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function ConversationMessageGroup ({ group }: { group: ChatMessageGroup }) {

{group.assistant && <MessageError message={group.assistant} />}

<MessageVerify user={group.user} assistant={group.assistant} />
<MessageVerify assistant={group.assistant} />

{group.assistant && <MessageOperations message={group.assistant} />}
</section>
Expand Down
1 change: 1 addition & 0 deletions frontend/app/src/components/chat/testutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type ChatMessage, ChatMessageRole } from '@/api/chats';
export function createExampleInitialChatMessage (): ChatMessage {
return {
id: 1,
post_verification_result_url: null,
chat_id: '0000',
ordinal: 1,
content: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ import { fn } from '@storybook/test';
import * as api from './api';

export * from './api';
export const verify = fn(api.verify).mockName('verify');
export const getVerify = fn(api.getVerify).mockName('getVerify');
45 changes: 4 additions & 41 deletions frontend/app/src/experimental/chat-verify-service/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const enum VerifyStatus {
export interface MessageVerifyResponse {
status: VerifyStatus;
message?: string | null;
runs: MessageVerifyResponse.Run[];
runs_report: string | null;
}

export namespace MessageVerifyResponse {
Expand All @@ -29,51 +29,14 @@ export namespace MessageVerifyResponse {
}
}

const verifyResponse = z.object({
job_id: z.string(),
});

const getVerifyResponse = z.object({
status: z.enum([VerifyStatus.CREATED, VerifyStatus.EXTRACTING, VerifyStatus.VALIDATING, VerifyStatus.SUCCESS, VerifyStatus.FAILED, VerifyStatus.SKIPPED]),
message: z.string().nullish(),
runs: z.array(z.object({
sql: z.string(),
explanation: z.string(),
success: z.boolean(),
results: z.any().array().array().optional(),
sql_error_code: z.number().nullish(),
sql_error_message: z.string().nullish(),
llm_verification: z.string().nullish(),
warnings: z.string().array().optional(),
})),
runs_report: z.string().nullable(),
}) satisfies ZodType<MessageVerifyResponse, any, any>;

export interface VerifyParams {
question: string
answer: string
external_request_id?: string
}

export async function verify(service: string | undefined, {
question,
answer,
external_request_id,
}: VerifyParams) {
return await fetch(`${service}/api/v1/sqls-validation`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
external_request_id: external_request_id,
qa_content: `User question: ${question}\n\nAnswer:\n${answer}`,
}),
}).then(handleResponse(verifyResponse));
}

export async function getVerify (service: string | undefined, id: string) {
assertEnabled(service);
return await fetch(`${service}/api/v1/sqls-validation/${id}`).then(handleResponse(getVerifyResponse));
export async function getVerify (url: string) {
return await fetch(url).then(handleResponse(getVerifyResponse));
}

export function isFinalVerifyState (state: VerifyStatus) {
Expand Down
Loading

0 comments on commit 0036ba2

Please sign in to comment.