diff --git a/.gitignore b/.gitignore index c2658d7..ec83a3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ +.omc/ +.DS_Store node_modules/ +dist/ +*.local +.idea/ +.vscode/ +.cursor/ diff --git a/api/chat.ts b/api/chat.ts index 9a91972..bc2dc5b 100644 --- a/api/chat.ts +++ b/api/chat.ts @@ -25,6 +25,7 @@ export type AIActionResult = null | string; export interface AIChatForwardedPropsParams { enableBuiltinTools?: boolean; + text2SqlDatasetId?: null | number; extraBody?: null | Recordable; extraHeaders?: null | Recordable; frequencyPenalty?: null | number; @@ -201,6 +202,7 @@ export interface AIChatComposerParams { extra_body?: null | string; thinking?: AIChatThinkingType | boolean | null; enable_builtin_tools?: boolean; + text2sql_dataset_id?: null | number; mcp_ids?: null | number[]; web_search?: AIWebSearchType; } @@ -286,6 +288,7 @@ function toForwardedProps( ): AIChatForwardedPropsParams { return { enableBuiltinTools: params.enable_builtin_tools ?? true, + text2SqlDatasetId: params.text2sql_dataset_id ?? null, extraBody: parseExtraBody(params.extra_body), extraHeaders: params.extra_headers ?? undefined, frequencyPenalty: params.frequency_penalty, diff --git a/api/index.ts b/api/index.ts index 98283bf..90ee736 100644 --- a/api/index.ts +++ b/api/index.ts @@ -341,4 +341,228 @@ export async function deleteAIQuickPhraseApi(pk: number) { return requestClient.delete(`/api/v1/quick-phrases/${pk}`); } +// ---------------- Text2SQL ---------------- + +// 数据集 +export interface AIText2SqlDatasetParams { + description?: null | string; + enabled?: number; + name: string; + sort?: number; +} + +export type AIText2SqlDatasetUpdateParams = Partial; + +export interface AIText2SqlDatasetResult extends AIText2SqlDatasetParams { + created_time: string; + id: number; + updated_time?: null | string; +} + +export interface Text2SqlDatasetEnabled { + description?: null | string; + id: number; + name: string; +} + +export interface Text2SqlTableSelectable { + table_comment?: null | string; + table_name: string; + selected: boolean; +} + +export interface AIText2SqlTableParams { + dataset_id: number; + custom_desc?: null | string; + enabled?: number; + schema_name?: string; + sort?: number; + table_comment?: null | string; + table_name: string; +} + +export type AIText2SqlTableUpdateParams = Partial; + +export interface AIText2SqlTableResult extends AIText2SqlTableParams { + created_time: string; + id: number; + updated_time?: null | string; +} + +export interface AIText2SqlExampleParams { + dataset_id: number; + enabled?: number; + note?: null | string; + question: string; + related_tables?: null | string; + sql: string; + sort?: number; +} + +export type AIText2SqlExampleUpdateParams = Partial; + +export interface AIText2SqlExampleResult extends AIText2SqlExampleParams { + created_time: string; + id: number; + updated_time?: null | string; +} + +export interface Text2SqlQueryParams { + question: string; +} + +export interface Text2SqlQueryResult { + columns: string[]; + duration_ms: number; + history_id?: null | number; + row_count: number; + rows: Recordable[]; + sql: string; + summary: string; +} + +// 数据集 +export async function getEnabledDatasetsApi() { + return requestClient.get( + '/api/v1/text2sql/datasets/enabled', + ); +} + +export async function getDatasetListApi(params?: Recordable) { + return requestClient.get>( + '/api/v1/text2sql/datasets', + { params }, + ); +} + +export async function getAllDatasetsApi() { + return requestClient.get( + '/api/v1/text2sql/datasets/all', + ); +} + +export async function getDatasetApi(pk: number) { + return requestClient.get( + `/api/v1/text2sql/datasets/${pk}`, + ); +} + +export async function createDatasetApi(data: AIText2SqlDatasetParams) { + return requestClient.post('/api/v1/text2sql/datasets', data); +} + +export async function updateDatasetApi( + pk: number, + data: AIText2SqlDatasetUpdateParams, +) { + return requestClient.put( + `/api/v1/text2sql/datasets/${pk}`, + data, + ); +} + +export async function deleteDatasetApi(pk: number) { + return requestClient.delete(`/api/v1/text2sql/datasets/${pk}`); +} + +export async function getSelectableTablesApi( + datasetId: number, + tableSchema?: string, +) { + return requestClient.get( + '/api/v1/text2sql/tables', + { params: { dataset_id: datasetId, table_schema: tableSchema } }, + ); +} + +export async function getTableColumnsApi( + tableName: string, + tableSchema?: string, +) { + return requestClient.get[]>( + `/api/v1/text2sql/tables/${tableName}/columns`, + { params: { table_schema: tableSchema } }, + ); +} + +export async function getSelectedTableListApi(params?: Recordable) { + return requestClient.get>( + '/api/v1/text2sql/selected-tables', + { params }, + ); +} + +export async function getAllSelectedTablesApi() { + return requestClient.get( + '/api/v1/text2sql/selected-tables/all', + ); +} + +export async function getSelectedTableApi(pk: number) { + return requestClient.get( + `/api/v1/text2sql/selected-tables/${pk}`, + ); +} + +export async function createSelectedTableApi(data: AIText2SqlTableParams) { + return requestClient.post( + '/api/v1/text2sql/selected-tables', + data, + ); +} + +export async function updateSelectedTableApi( + pk: number, + data: AIText2SqlTableUpdateParams, +) { + return requestClient.put( + `/api/v1/text2sql/selected-tables/${pk}`, + data, + ); +} + +export async function deleteSelectedTableApi(pk: number) { + return requestClient.delete( + `/api/v1/text2sql/selected-tables/${pk}`, + ); +} + +export async function getExampleListApi(params?: Recordable) { + return requestClient.get>( + '/api/v1/text2sql/examples', + { params }, + ); +} + +export async function getAllExamplesApi() { + return requestClient.get( + '/api/v1/text2sql/examples/all', + ); +} + +export async function createExampleApi(data: AIText2SqlExampleParams) { + return requestClient.post('/api/v1/text2sql/examples', data); +} + +export async function updateExampleApi( + pk: number, + data: AIText2SqlExampleUpdateParams, +) { + return requestClient.put( + `/api/v1/text2sql/examples/${pk}`, + data, + ); +} + +export async function deleteExampleApi(pk: number) { + return requestClient.delete(`/api/v1/text2sql/examples/${pk}`); +} + +export async function queryText2SqlApi(data: Text2SqlQueryParams) { + return requestClient.post( + '/api/v1/text2sql/queries', + data, + ); +} + export * from './chat'; diff --git a/langs/en-US/ai.json b/langs/en-US/ai.json index b80c744..9e77bca 100644 --- a/langs/en-US/ai.json +++ b/langs/en-US/ai.json @@ -6,5 +6,71 @@ "provider": "Provider", "model": "Providers & Models", "mcp": "MCP", - "quick_phrase": "Quick Phrase" + "quick_phrase": "Quick Phrase", + "text2sql": "Dataset", + "text2sqlPage": { + "dataset": { + "title": "Datasets", + "add": "Add", + "empty": "No datasets", + "disabled": "Disabled", + "pleaseSelect": "Please select a dataset", + "name": "Dataset name", + "nameQuery": "Name", + "description": "Description", + "enabled": "Enabled", + "disabledOption": "Disabled", + "sort": "Sort", + "status": "Status", + "editTitle": "Edit Dataset", + "createTitle": "Add Dataset", + "deleteConfirm": "Are you sure you want to delete dataset \"{name}\"? Its tables and examples will be retained but no longer aggregated by this dataset.", + "saved": "Saved", + "deleted": "Deleted" + }, + "table": { + "tab": "Tables", + "name": "Table name", + "schema": "Schema", + "comment": "Table comment", + "customDesc": "Custom description", + "customDescPlaceholder": "Add business semantics to improve generation accuracy", + "customDescHelp": "Add business semantics to improve generation accuracy", + "enabled": "Enabled", + "disabledOption": "Disabled", + "status": "Status", + "sort": "Sort", + "pick": "Pick Tables", + "pickPlaceholder": "Select an unselected table", + "pickCustomDescPlaceholder": "Custom description (optional; add business semantics to improve generation accuracy)", + "pickAlert": "You can only pick tables that haven't been selected yet. Table names are resolved from the fba schema.", + "editTitle": "Edit Selected Table", + "pleaseSelectDataset": "Please select a dataset first", + "pleaseSelectTable": "Please select a table to pick", + "picked": "Picked", + "unselected": "Unselected", + "allPicked": "All available tables have been picked", + "updated": "Updated" + }, + "example": { + "tab": "Examples", + "question": "Question", + "sql": "Example SQL", + "relatedTables": "Related tables", + "relatedTablesPlaceholder": "users,orders", + "relatedTablesHelp": "Comma-separated table names used for retrieval", + "note": "Note", + "enabled": "Enabled", + "disabledOption": "Disabled", + "status": "Status", + "sort": "Sort", + "naturalLanguageQuestion": "Natural-language question", + "add": "Add Example", + "editTitle": "Edit Example", + "createTitle": "Add Example", + "pleaseSelectDataset": "Please select a dataset first", + "saved": "Saved", + "deleted": "Deleted" + } + } } diff --git a/langs/zh-CN/ai.json b/langs/zh-CN/ai.json index 8951b45..5ee22e6 100644 --- a/langs/zh-CN/ai.json +++ b/langs/zh-CN/ai.json @@ -6,5 +6,71 @@ "provider": "供应商管理", "model": "供应商与模型", "mcp": "MCP 管理", - "quick_phrase": "快捷短语" + "quick_phrase": "快捷短语", + "text2sql": "数据集", + "text2sqlPage": { + "dataset": { + "title": "数据集", + "add": "新增", + "empty": "暂无数据集", + "disabled": "停用", + "pleaseSelect": "请选择数据集", + "name": "数据集名称", + "nameQuery": "名称", + "description": "描述", + "enabled": "启用", + "disabledOption": "停用", + "sort": "排序", + "status": "状态", + "editTitle": "编辑数据集", + "createTitle": "新增数据集", + "deleteConfirm": "确认删除数据集「{name}」吗?其下表与样例保留但不再被该数据集聚合。", + "saved": "已保存", + "deleted": "已删除" + }, + "table": { + "tab": "表", + "name": "表名", + "schema": "库", + "comment": "表注释", + "customDesc": "自定义描述", + "customDescPlaceholder": "补充业务语义,提升生成精度", + "customDescHelp": "补充业务语义,提升生成精度", + "enabled": "启用", + "disabledOption": "停用", + "status": "状态", + "sort": "排序", + "pick": "挑选表", + "pickPlaceholder": "选择未挑选的表", + "pickCustomDescPlaceholder": "自定义描述(可选,补充业务语义以提升生成精度)", + "pickAlert": "仅可选择尚未挑选的表,表名来自 fba 库反查", + "editTitle": "编辑已选表", + "pleaseSelectDataset": "请先选择数据集", + "pleaseSelectTable": "请选择要挑选的表", + "picked": "已挑选", + "unselected": "已取消挑选", + "allPicked": "已挑选全部可用的表", + "updated": "已更新" + }, + "example": { + "tab": "样例", + "question": "问题", + "sql": "示范 SQL", + "relatedTables": "相关表", + "relatedTablesPlaceholder": "users,orders", + "relatedTablesHelp": "相关表名,逗号分隔,用于召回", + "note": "备注", + "enabled": "启用", + "disabledOption": "停用", + "status": "状态", + "sort": "排序", + "naturalLanguageQuestion": "自然语言问题", + "add": "新增样例", + "editTitle": "编辑样例", + "createTitle": "新增样例", + "pleaseSelectDataset": "请先选择数据集", + "saved": "已保存", + "deleted": "已删除" + } + } } diff --git a/routes/index.ts b/routes/index.ts index 08412e4..c0a295e 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -66,6 +66,15 @@ const routes: RouteRecordRaw[] = [ icon: 'mdi:lightning-bolt-outline', }, }, + { + name: 'PluginAIText2Sql', + path: '/plugins/ai/text2sql', + component: () => import('#/plugins/ai/views/text2sql/index.vue'), + meta: { + title: $t('ai.text2sql'), + icon: 'mdi:database-search-outline', + }, + }, ]; export default routes; diff --git a/views/chat/composables/use-chat-settings.ts b/views/chat/composables/use-chat-settings.ts index a4b95d1..3f1172b 100644 --- a/views/chat/composables/use-chat-settings.ts +++ b/views/chat/composables/use-chat-settings.ts @@ -20,6 +20,7 @@ type ChatThinkingValue = AIChatComposerParams['thinking']; type ChatWebSearchType = NonNullable; interface ChatSessionScopedConfig { enableBuiltinTools: boolean; + text2sqlDatasetId: number | undefined; generationType: ChatGenerationType; modelId?: string; parallelToolCalls: boolean; @@ -34,6 +35,7 @@ const DEFAULT_CHAT_SESSION_SCOPED_CONFIG: Omit< 'modelId' | 'providerId' > = { enableBuiltinTools: true, + text2sqlDatasetId: undefined, generationType: 'text', parallelToolCalls: true, selectedMcpIds: [], @@ -128,6 +130,7 @@ export function useChatSettings(options: UseChatSettingsOptions) { const parallelToolCalls = ref(true); const thinking = ref(undefined); const enableBuiltinTools = ref(true); + const text2sqlDatasetId = ref(); const selectedMcpIds = ref([]); const webSearch = ref('off'); const stopSequences = ref(''); @@ -188,6 +191,7 @@ export function useChatSettings(options: UseChatSettingsOptions) { function buildCurrentChatSessionScopedConfig(): ChatSessionScopedConfig { return { enableBuiltinTools: enableBuiltinTools.value, + text2sqlDatasetId: text2sqlDatasetId.value, generationType: generationType.value, modelId: selectedModelId.value, parallelToolCalls: parallelToolCalls.value, @@ -221,6 +225,8 @@ export function useChatSettings(options: UseChatSettingsOptions) { selectedMcpIds.value = [...(config.selectedMcpIds ?? [])]; webSearch.value = config.webSearch ?? DEFAULT_CHAT_SESSION_SCOPED_CONFIG.webSearch; + text2sqlDatasetId.value = + config.text2sqlDatasetId ?? DEFAULT_CHAT_SESSION_SCOPED_CONFIG.text2sqlDatasetId; } function rememberConversationSessionConfig(conversationId?: null | string) { @@ -356,6 +362,7 @@ export function useChatSettings(options: UseChatSettingsOptions) { WEB_SEARCH_OPTIONS, conversationSessionConfigs, enableBuiltinTools, + text2sqlDatasetId, extraBody, extraHeaders, frequencyPenalty, diff --git a/views/chat/composables/use-sender-toolbar.ts b/views/chat/composables/use-sender-toolbar.ts index b9512c8..e3034a5 100644 --- a/views/chat/composables/use-sender-toolbar.ts +++ b/views/chat/composables/use-sender-toolbar.ts @@ -6,9 +6,10 @@ import type { AIChatComposerParams, AIMcpResult, AIQuickPhraseResult, + Text2SqlDatasetEnabled, } from '../../../api'; -import { h, ref, resolveComponent } from 'vue'; +import { computed, h, ref, resolveComponent } from 'vue'; import { IconifyIcon } from '@vben/icons'; @@ -30,7 +31,9 @@ export interface UseSenderToolbarOptions { confirmClearConversationContext: () => void; confirmClearMessages: () => void; createNewConversation: () => void; + datasets: Ref; enableBuiltinTools: Ref; + text2sqlDatasetId: Ref; generationType: Ref; generationTypeButtonLabel: Ref; GENERATION_TYPE_OPTIONS: Array<{ @@ -75,6 +78,8 @@ export function useSenderToolbar(options: UseSenderToolbarOptions) { confirmClearConversationContext, confirmClearMessages, createNewConversation, + datasets, + text2sqlDatasetId, generationType, generationTypeButtonLabel, GENERATION_TYPE_OPTIONS, @@ -134,6 +139,7 @@ export function useSenderToolbar(options: UseSenderToolbarOptions) { } function renderFooterIconButton(opts: { + active?: boolean; disabled?: boolean; icon: string; onClick?: () => void; @@ -151,7 +157,7 @@ export function useSenderToolbar(options: UseSenderToolbarOptions) { shape: 'circle', size: 'small', title: opts.title, - type: 'text', + type: opts.active ? 'primary' : 'text', }); } @@ -363,6 +369,37 @@ export function useSenderToolbar(options: UseSenderToolbarOptions) { }); } + const text2sqlDatasetLabel = computed(() => { + if (text2sqlDatasetId.value === undefined) { + return datasets.value.length === 0 ? '无数据集' : '关闭'; + } + return ( + datasets.value.find((item) => item.id === text2sqlDatasetId.value)?.name || + 'Text2SQL' + ); + }); + + function renderText2SqlPopoverContent() { + const items: SenderToolbarOption[] = [ + { desc: '不启用 Text2SQL 取数', key: 'off', label: '关闭' }, + ...datasets.value.map((item) => ({ + desc: item.description ?? undefined, + key: String(item.id), + label: item.name, + })), + ]; + return renderSelectableOptionsContent({ + activeKey: + text2sqlDatasetId.value === undefined + ? 'off' + : String(text2sqlDatasetId.value), + items, + onSelect: (key) => { + text2sqlDatasetId.value = key === 'off' ? undefined : Number(key); + }, + }); + } + function getMcpDescription(item: AIMcpResult) { return item.description || item.command || item.url || `MCP #${item.id}`; } @@ -489,6 +526,26 @@ export function useSenderToolbar(options: UseSenderToolbarOptions) { }), }, ), + h( + aPopover, + { placement: 'topLeft', title: 'Text2SQL 数据集', trigger: 'click' }, + { + content: () => renderText2SqlPopoverContent(), + default: () => + renderFooterIconButton({ + active: text2sqlDatasetId.value !== undefined, + disabled: + sending.value || + generationType.value === 'image' || + datasets.value.length === 0, + icon: + text2sqlDatasetId.value !== undefined + ? 'mdi:database-search' + : 'mdi:database-search-outline', + title: `Text2SQL:${text2sqlDatasetLabel.value}`, + }), + }, + ), h( aPopover, { diff --git a/views/chat/index.vue b/views/chat/index.vue index 8711ac6..ca5214d 100644 --- a/views/chat/index.vue +++ b/views/chat/index.vue @@ -11,6 +11,7 @@ import type { AIModelResult, AIProviderResult, AIQuickPhraseResult, + Text2SqlDatasetEnabled, } from '../../api'; import type { AIChatComposerParams, @@ -48,6 +49,7 @@ import { getAllAIModelApi, getAllAIProviderApi, getAllAIQuickPhraseApi, + getEnabledDatasetsApi, } from '../../api'; import { buildChatCompletionRequest, @@ -95,6 +97,7 @@ const regeneratingMessageIndex = ref(); const providers = ref([]); const models = ref([]); const mcps = ref([]); +const datasets = ref([]); const quickPhrases = ref([]); const resourcesLoading = ref(false); @@ -188,6 +191,7 @@ const { THINKING_OPTIONS, WEB_SEARCH_OPTIONS, enableBuiltinTools, + text2sqlDatasetId, extraBody, extraHeaders, frequencyPenalty, @@ -271,6 +275,10 @@ async function fetchMcps() { mcps.value = await getAllAIMcpApi(); } +async function fetchDatasets() { + datasets.value = await getEnabledDatasetsApi(); +} + async function fetchModelsByProvider(providerId?: number) { const fetchId = ++currentModelFetchId; @@ -304,6 +312,7 @@ async function refreshChatResources() { fetchProviders(), fetchModelsByProvider(selectedProviderId.value), fetchMcps(), + fetchDatasets(), fetchQuickPhrases(), ]); } @@ -544,6 +553,7 @@ async function submitChat( conversation_id: activeConversationId.value, extra_body: extraBody.value.trim() || undefined, enable_builtin_tools: enableBuiltinTools.value, + text2sql_dataset_id: text2sqlDatasetId.value ?? undefined, extra_headers: parseJsonField>( extraHeaders.value, '额外请求头', @@ -1091,7 +1101,9 @@ const { fetchQuickPhrases: fetchQuickPhrasesFromToolbar, renderSenderFooter } = confirmClearConversationContext, confirmClearMessages, createNewConversation, + datasets, enableBuiltinTools, + text2sqlDatasetId, generationType, generationTypeButtonLabel, GENERATION_TYPE_OPTIONS, @@ -1168,6 +1180,7 @@ onMounted(async () => { await fetchProviders(); await applyAssistantDefaultModel({ force: true }); await fetchMcps(); + await fetchDatasets(); await fetchQuickPhrasesFromToolbar(); await initializeSession(); diff --git a/views/text2sql/data.ts b/views/text2sql/data.ts new file mode 100644 index 0000000..856fd65 --- /dev/null +++ b/views/text2sql/data.ts @@ -0,0 +1,285 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeGridProps } from '#/adapter/vxe-table'; +import type { + AIText2SqlDatasetResult, + AIText2SqlExampleResult, + AIText2SqlTableResult, +} from '../../api'; + +import { $t } from '@vben/locales'; + +// ---------------- 数据集 ---------------- + +export function useDatasetColumns( + onActionClick?: OnActionClickFn, +): VxeGridProps['columns'] { + return [ + { field: 'seq', title: $t('common.table.id'), type: 'seq', width: 50 }, + { field: 'name', title: $t('ai.text2sqlPage.dataset.name'), minWidth: 160 }, + { field: 'description', title: $t('ai.text2sqlPage.dataset.description'), align: 'left' }, + { + field: 'enabled', + title: $t('ai.text2sqlPage.dataset.status'), + width: 80, + formatter({ cellValue }) { + return cellValue ? $t('common.yes') : $t('common.no'); + }, + }, + { + field: 'operation', + title: $t('common.table.operation'), + align: 'center', + fixed: 'right', + width: 140, + cellRender: { + attrs: { + onClick: onActionClick, + }, + name: 'CellOperation', + options: ['edit', 'delete'], + }, + }, + ]; +} + +export const datasetQuerySchema: VbenFormSchema[] = [ + { component: 'Input', fieldName: 'name', label: $t('ai.text2sqlPage.dataset.nameQuery') }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: [ + { label: $t('ai.text2sqlPage.dataset.enabled'), value: 1 }, + { label: $t('ai.text2sqlPage.dataset.disabledOption'), value: 0 }, + ], + }, + fieldName: 'enabled', + label: $t('ai.text2sqlPage.dataset.status'), + }, +]; + +export const datasetSchema: VbenFormSchema[] = [ + { + component: 'Input', + fieldName: 'name', + label: $t('ai.text2sqlPage.dataset.name'), + rules: 'required', + }, + { + component: 'Textarea', + componentProps: { autoSize: { minRows: 2, maxRows: 4 } }, + fieldName: 'description', + label: $t('ai.text2sqlPage.dataset.description'), + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: $t('ai.text2sqlPage.dataset.enabled'), value: 1 }, + { label: $t('ai.text2sqlPage.dataset.disabledOption'), value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'enabled', + label: $t('ai.text2sqlPage.dataset.status'), + }, + { + component: 'InputNumber', + componentProps: { class: 'w-full', min: 0 }, + defaultValue: 0, + fieldName: 'sort', + label: $t('ai.text2sqlPage.dataset.sort'), + }, +]; + +// ---------------- 已选数据表(数据源) ---------------- + +export function useSelectedTableColumns( + onActionClick?: OnActionClickFn, +): VxeGridProps['columns'] { + return [ + { field: 'seq', title: $t('common.table.id'), type: 'seq', width: 50 }, + { field: 'table_name', title: $t('ai.text2sqlPage.table.name'), width: 180 }, + { field: 'schema_name', title: $t('ai.text2sqlPage.table.schema'), width: 100 }, + { field: 'table_comment', title: $t('ai.text2sqlPage.table.comment'), align: 'left' }, + { field: 'custom_desc', title: $t('ai.text2sqlPage.table.customDesc'), align: 'left' }, + { + field: 'enabled', + title: $t('ai.text2sqlPage.table.status'), + width: 80, + formatter({ cellValue }) { + return cellValue ? $t('common.yes') : $t('common.no'); + }, + }, + { + field: 'operation', + title: $t('common.table.operation'), + align: 'center', + fixed: 'right', + width: 140, + cellRender: { + attrs: { + onClick: onActionClick, + }, + name: 'CellOperation', + options: ['edit', 'delete'], + }, + }, + ]; +} + +export const selectedTableQuerySchema: VbenFormSchema[] = [ + { component: 'Input', fieldName: 'table_name', label: $t('ai.text2sqlPage.table.name') }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: [ + { label: $t('ai.text2sqlPage.table.enabled'), value: 1 }, + { label: $t('ai.text2sqlPage.table.disabledOption'), value: 0 }, + ], + }, + fieldName: 'enabled', + label: $t('ai.text2sqlPage.table.status'), + }, +]; + +export const selectedTableSchema: VbenFormSchema[] = [ + { + component: 'Input', + componentProps: { disabled: true }, + fieldName: 'table_name', + label: $t('ai.text2sqlPage.table.name'), + }, + { + component: 'Textarea', + componentProps: { + autoSize: { minRows: 3, maxRows: 6 }, + placeholder: $t('ai.text2sqlPage.table.customDescPlaceholder'), + }, + fieldName: 'custom_desc', + help: $t('ai.text2sqlPage.table.customDescHelp'), + label: $t('ai.text2sqlPage.table.customDesc'), + }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: $t('ai.text2sqlPage.table.enabled'), value: 1 }, + { label: $t('ai.text2sqlPage.table.disabledOption'), value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'enabled', + label: $t('ai.text2sqlPage.table.status'), + }, + { + component: 'InputNumber', + componentProps: { class: 'w-full', min: 0 }, + defaultValue: 0, + fieldName: 'sort', + label: $t('ai.text2sqlPage.table.sort'), + }, +]; + +// ---------------- Few-shot 样例 ---------------- + +export function useExampleColumns( + onActionClick?: OnActionClickFn, +): VxeGridProps['columns'] { + return [ + { field: 'seq', title: $t('common.table.id'), type: 'seq', width: 50 }, + { field: 'question', title: $t('ai.text2sqlPage.example.question'), align: 'left', minWidth: 200 }, + { field: 'sql', title: $t('ai.text2sqlPage.example.sql'), align: 'left', minWidth: 240 }, + { field: 'related_tables', title: $t('ai.text2sqlPage.example.relatedTables'), width: 160 }, + { field: 'note', title: $t('ai.text2sqlPage.example.note'), align: 'left' }, + { + field: 'enabled', + title: $t('ai.text2sqlPage.example.status'), + width: 80, + formatter({ cellValue }) { + return cellValue ? $t('common.yes') : $t('common.no'); + }, + }, + { + field: 'operation', + title: $t('common.table.operation'), + align: 'center', + fixed: 'right', + width: 140, + cellRender: { + attrs: { + onClick: onActionClick, + }, + name: 'CellOperation', + options: ['edit', 'delete'], + }, + }, + ]; +} + +export const exampleQuerySchema: VbenFormSchema[] = [ + { component: 'Input', fieldName: 'question', label: $t('ai.text2sqlPage.example.question') }, + { + component: 'Select', + componentProps: { + allowClear: true, + options: [ + { label: $t('ai.text2sqlPage.example.enabled'), value: 1 }, + { label: $t('ai.text2sqlPage.example.disabledOption'), value: 0 }, + ], + }, + fieldName: 'enabled', + label: $t('ai.text2sqlPage.example.status'), + }, +]; + +export const exampleSchema: VbenFormSchema[] = [ + { + component: 'Textarea', + componentProps: { autoSize: { minRows: 2, maxRows: 4 } }, + fieldName: 'question', + label: $t('ai.text2sqlPage.example.naturalLanguageQuestion'), + rules: 'required', + }, + { + component: 'Textarea', + componentProps: { autoSize: { minRows: 3, maxRows: 8 } }, + fieldName: 'sql', + label: $t('ai.text2sqlPage.example.sql'), + rules: 'required', + }, + { + component: 'Input', + componentProps: { placeholder: $t('ai.text2sqlPage.example.relatedTablesPlaceholder') }, + fieldName: 'related_tables', + help: $t('ai.text2sqlPage.example.relatedTablesHelp'), + label: $t('ai.text2sqlPage.example.relatedTables'), + }, + { component: 'Textarea', fieldName: 'note', label: $t('ai.text2sqlPage.example.note') }, + { + component: 'RadioGroup', + componentProps: { + buttonStyle: 'solid', + options: [ + { label: $t('ai.text2sqlPage.example.enabled'), value: 1 }, + { label: $t('ai.text2sqlPage.example.disabledOption'), value: 0 }, + ], + optionType: 'button', + }, + defaultValue: 1, + fieldName: 'enabled', + label: $t('ai.text2sqlPage.example.status'), + }, + { + component: 'InputNumber', + componentProps: { class: 'w-full', min: 0 }, + defaultValue: 0, + fieldName: 'sort', + label: $t('ai.text2sqlPage.example.sort'), + }, +]; diff --git a/views/text2sql/dataset-examples-pane.vue b/views/text2sql/dataset-examples-pane.vue new file mode 100644 index 0000000..08ebf75 --- /dev/null +++ b/views/text2sql/dataset-examples-pane.vue @@ -0,0 +1,181 @@ + + + diff --git a/views/text2sql/dataset-tables-pane.vue b/views/text2sql/dataset-tables-pane.vue new file mode 100644 index 0000000..045a452 --- /dev/null +++ b/views/text2sql/dataset-tables-pane.vue @@ -0,0 +1,254 @@ + + + diff --git a/views/text2sql/index.vue b/views/text2sql/index.vue new file mode 100644 index 0000000..63f516d --- /dev/null +++ b/views/text2sql/index.vue @@ -0,0 +1,232 @@ + + + + +