Skip to content

Commit b6042ad

Browse files
committed
feat(ui): expose database dialect selection and sync backend
1 parent ae8c0bf commit b6042ad

File tree

12 files changed

+259
-46
lines changed

12 files changed

+259
-46
lines changed

frontend/src/App.vue

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
:provider="config.provider"
1818
:status="inputStatus"
1919
:disabled="isInputDisabled"
20+
v-model:database-dialect="databaseDialect"
21+
:dialect-options="dialectOptions"
2022
@submit="handleQuery"
2123
@open-settings="showSettings = true"
2224
/>
@@ -29,7 +31,6 @@
2931
:models-map="modelsByProvider"
3032
v-model:include-explanation="includeExplanation"
3133
@save="handleSave"
32-
@test-connection="handleTest"
3334
@refresh-models="refreshModels"
3435
/>
3536
</div>
@@ -38,7 +39,7 @@
3839
<script setup lang="ts">
3940
import { ref, provide, computed } from 'vue'
4041
import { ElMessage } from 'element-plus'
41-
import type { AppContext, AIConfig } from './types'
42+
import type { AppContext, AIConfig, DatabaseDialect } from './types'
4243
import { useAIChat } from './composables/useAIChat'
4344
import AIChatHeader from './components/AIChatHeader.vue'
4445
import AIChatMessages from './components/AIChatMessages.vue'
@@ -72,9 +73,19 @@ const {
7273
refreshModels
7374
} = useAIChat(pluginContext)
7475
76+
if (!config.value.databaseDialect) {
77+
config.value.databaseDialect = 'mysql'
78+
}
79+
7580
// UI state
7681
const showSettings = ref(false)
7782
const includeExplanation = ref(false)
83+
const databaseDialect = computed<DatabaseDialect>({
84+
get: () => config.value.databaseDialect ?? 'mysql',
85+
set: (value) => {
86+
config.value.databaseDialect = value
87+
}
88+
})
7889
7990
const inputStatus = computed<'connected' | 'disconnected' | 'connecting' | 'setup'>(() => {
8091
if (!isConfigured.value) {
@@ -87,6 +98,11 @@ const isInputDisabled = computed(() => inputStatus.value !== 'connected')
8798
8899
// Get translation function from context
89100
const { t } = pluginContext.i18n
101+
const dialectOptions = computed(() => ([
102+
{ value: 'mysql' as DatabaseDialect, label: t('ai.dialect.mysql') },
103+
{ value: 'postgresql' as DatabaseDialect, label: t('ai.dialect.postgresql') },
104+
{ value: 'sqlite' as DatabaseDialect, label: t('ai.dialect.sqlite') }
105+
]))
90106
91107
// Save configuration
92108
async function handleSave() {
@@ -137,6 +153,7 @@ async function handleTest(updatedConfig?: AIConfig) {
137153
flex-direction: column;
138154
width: 100%;
139155
height: 100%;
156+
max-height: 100%;
140157
min-height: 0;
141158
padding: clamp(16px, 4vw, 32px);
142159
box-sizing: border-box;
@@ -162,6 +179,7 @@ async function handleTest(updatedConfig?: AIConfig) {
162179
box-shadow: var(--atest-shadow-md);
163180
padding: clamp(12px, 3vw, 24px);
164181
min-height: 0;
182+
max-height: 100%;
165183
}
166184
167185
@media (max-width: 1024px) {
@@ -194,6 +212,20 @@ async function handleTest(updatedConfig?: AIConfig) {
194212
</style>
195213

196214
<style>
215+
:global(html) {
216+
height: 100%;
217+
}
218+
219+
:global(body) {
220+
height: 100%;
221+
margin: 0;
222+
background: var(--atest-bg-base);
223+
}
224+
225+
:global(#app) {
226+
height: 100%;
227+
}
228+
197229
:global(#plugin-container) {
198230
height: 100%;
199231
display: flex;

frontend/src/components/AIChatInput.vue

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,26 @@
3737

3838
<div class="input-footer">
3939
<div class="footer-left">
40+
<el-tooltip :content="dialectTooltip" placement="top">
41+
<el-select
42+
v-model="dialectModel"
43+
class="dialect-select"
44+
size="small"
45+
:disabled="props.loading"
46+
>
47+
<el-option
48+
v-for="option in props.dialectOptions"
49+
:key="option.value"
50+
:label="option.label"
51+
:value="option.value"
52+
/>
53+
</el-select>
54+
</el-tooltip>
4055
<el-tooltip :content="configureTooltip" placement="top">
4156
<el-button
4257
class="footer-btn configure-btn"
4358
type="default"
44-
:disabled="props.disabled"
59+
:disabled="props.loading"
4560
@click="emit('open-settings')"
4661
>
4762
<el-icon><Setting /></el-icon>
@@ -66,22 +81,31 @@
6681
</template>
6782

6883
<script setup lang="ts">
69-
import { ref, inject, computed } from 'vue'
84+
import { ref, inject, computed, withDefaults } from 'vue'
7085
import { Promotion, Setting, WarningFilled, InfoFilled } from '@element-plus/icons-vue'
71-
import type { AppContext } from '../types'
86+
import type { AppContext, DatabaseDialect } from '../types'
7287
7388
interface Props {
7489
loading: boolean
7590
includeExplanation: boolean
7691
provider: string
7792
status: 'connected' | 'disconnected' | 'connecting' | 'setup'
7893
disabled: boolean
79-
}
80-
const props = defineProps<Props>()
94+
databaseDialect: DatabaseDialect
95+
dialectOptions: Array<{ value: DatabaseDialect; label: string }>
96+
}
97+
const props = withDefaults(defineProps<Props>(), {
98+
dialectOptions: () => [
99+
{ value: 'mysql' as DatabaseDialect, label: 'MySQL' },
100+
{ value: 'postgresql' as DatabaseDialect, label: 'PostgreSQL' },
101+
{ value: 'sqlite' as DatabaseDialect, label: 'SQLite' }
102+
]
103+
})
81104
82105
interface Emits {
83-
(e: 'submit', prompt: string, options: { includeExplanation: boolean }): void
106+
(e: 'submit', prompt: string, options: { includeExplanation: boolean; databaseDialect: DatabaseDialect }): void
84107
(e: 'open-settings'): void
108+
(e: 'update:databaseDialect', dialect: DatabaseDialect): void
85109
}
86110
const emit = defineEmits<Emits>()
87111
@@ -94,6 +118,7 @@ const prompt = ref('')
94118
95119
const configureTooltip = computed(() => t('ai.tooltip.configure'))
96120
const generateTooltip = computed(() => (props.loading ? t('ai.message.generating') : t('ai.tooltip.generate')))
121+
const dialectTooltip = computed(() => t('ai.tooltip.dialect'))
97122
98123
const providerKey = computed(() => (props.provider === 'local' ? 'ollama' : props.provider))
99124
const providerLabel = computed(() => {
@@ -131,11 +156,17 @@ const statusIcon = computed(() => {
131156
const showStatusBanner = computed(() => props.status === 'disconnected' || props.status === 'setup')
132157
const showConfigureLink = showStatusBanner
133158
159+
const dialectModel = computed({
160+
get: () => props.databaseDialect,
161+
set: (value: DatabaseDialect) => emit('update:databaseDialect', value)
162+
})
163+
134164
function handleSubmit() {
135165
if (!prompt.value.trim() || props.loading || props.disabled) return
136166
137167
emit('submit', prompt.value, {
138-
includeExplanation: props.includeExplanation
168+
includeExplanation: props.includeExplanation,
169+
databaseDialect: props.databaseDialect
139170
})
140171
141172
// Clear input after submit
@@ -205,6 +236,7 @@ function handleSubmit() {
205236
.footer-right {
206237
display: flex;
207238
align-items: center;
239+
gap: var(--atest-spacing-xs);
208240
}
209241
210242
.footer-left {
@@ -215,6 +247,15 @@ function handleSubmit() {
215247
justify-content: flex-end;
216248
}
217249
250+
.dialect-select {
251+
min-width: 170px;
252+
}
253+
254+
.dialect-select :deep(.el-select__wrapper),
255+
.dialect-select :deep(.el-input__wrapper) {
256+
border-radius: 10px;
257+
}
258+
218259
.footer-btn {
219260
display: inline-flex;
220261
justify-content: center;
@@ -306,6 +347,20 @@ function handleSubmit() {
306347
min-height: 112px;
307348
}
308349
350+
.input-footer {
351+
grid-template-columns: 1fr;
352+
}
353+
354+
.footer-left,
355+
.footer-right {
356+
justify-content: space-between;
357+
}
358+
359+
.dialect-select {
360+
flex: 1;
361+
min-width: 0;
362+
}
363+
309364
.footer-btn {
310365
width: 44px;
311366
height: 44px;

frontend/src/components/AIChatMessages.vue

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,14 @@
4141
<pre class="sql-code">{{ message.sql }}</pre>
4242
</div>
4343
<div v-if="message.meta" class="message-meta">
44-
<el-tag size="small" effect="plain">{{ message.meta.model }}</el-tag>
45-
<span class="meta-time">{{ message.meta.duration }}ms</span>
44+
<el-tag v-if="message.meta.model" size="small" effect="plain">{{ message.meta.model }}</el-tag>
45+
<el-tag
46+
v-if="message.meta.dialect"
47+
size="small"
48+
effect="plain"
49+
class="dialect-tag"
50+
>{{ formatDialect(message.meta.dialect) }}</el-tag>
51+
<span v-if="message.meta.duration" class="meta-time">{{ message.meta.duration }}ms</span>
4652
</div>
4753
</div>
4854
<div class="message-time">
@@ -95,6 +101,16 @@ function formatTime(timestamp: number): string {
95101
return date.toLocaleTimeString()
96102
}
97103
104+
function formatDialect(dialect: string): string {
105+
if (!dialect) {
106+
return ''
107+
}
108+
const normalized = dialect.toLowerCase()
109+
const key = `ai.dialect.${normalized}`
110+
const label = t(key)
111+
return label === key ? normalized : label
112+
}
113+
98114
async function copySQL(sql: string) {
99115
try {
100116
await navigator.clipboard.writeText(sql)
@@ -317,6 +333,10 @@ async function copySQL(sql: string) {
317333
font-size: 12px;
318334
}
319335
336+
.dialect-tag {
337+
letter-spacing: 0.3px;
338+
}
339+
320340
.message-ai .message-meta {
321341
color: var(--atest-text-regular);
322342
}

frontend/src/components/AISettingsPanel.vue

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,6 @@
288288
<el-button @click="emit('update:visible', false)">
289289
{{ t('ai.button.close') }}
290290
</el-button>
291-
<el-button @click="handleTestConnectionClick">
292-
<el-icon><Connection /></el-icon>
293-
{{ t('ai.button.testConnection') }}
294-
</el-button>
295291
<el-button type="primary" @click="handleSave">
296292
<el-icon><Check /></el-icon>
297293
{{ t('ai.button.save') }}
@@ -309,7 +305,6 @@ import {
309305
Cloudy as CloudIcon,
310306
Cpu,
311307
MagicStick,
312-
Connection,
313308
Check
314309
} from '@element-plus/icons-vue'
315310
import { ElMessage } from 'element-plus'
@@ -328,7 +323,6 @@ const props = defineProps<Props>()
328323
interface Emits {
329324
(e: 'update:visible', value: boolean): void
330325
(e: 'save'): void
331-
(e: 'test-connection', value: AIConfig): void
332326
(e: 'refresh-models', provider?: Provider): void
333327
(e: 'update:include-explanation', value: boolean): void
334328
}
@@ -421,10 +415,6 @@ function handleSave() {
421415
Object.assign(props.config, localConfig.value)
422416
emit('save')
423417
}
424-
425-
function handleTestConnectionClick() {
426-
emit('test-connection', { ...localConfig.value })
427-
}
428418
</script>
429419

430420
<style scoped>

frontend/src/composables/useAIChat.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ref, computed, watch } from 'vue'
2-
import type { AppContext, AIConfig, Message, Model } from '@/types'
2+
import type { AppContext, AIConfig, Message, Model, DatabaseDialect } from '@/types'
33
import { loadConfig, loadConfigForProvider, saveConfig, getMockModels, generateId, type Provider } from '@/utils/config'
44
import { aiService } from '@/services/aiService'
55

@@ -106,7 +106,7 @@ export function useAIChat(_context: AppContext) {
106106
/**
107107
* Handle query submission
108108
*/
109-
async function handleQuery(prompt: string, options: { includeExplanation: boolean }) {
109+
async function handleQuery(prompt: string, options: { includeExplanation: boolean; databaseDialect: DatabaseDialect }) {
110110
console.log('🎯 [useAIChat] handleQuery called', {
111111
prompt,
112112
options,
@@ -143,7 +143,8 @@ export function useAIChat(_context: AppContext) {
143143
prompt,
144144
timeout: config.value.timeout,
145145
maxTokens: config.value.maxTokens,
146-
includeExplanation: options.includeExplanation
146+
includeExplanation: options.includeExplanation,
147+
databaseDialect: options.databaseDialect ?? config.value.databaseDialect ?? 'mysql'
147148
})
148149

149150
console.log('✅ [useAIChat] Received response', {

frontend/src/locales/en.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
"save": "Save",
2626
"reset": "Reset",
2727
"refresh": "Refresh",
28-
"testConnection": "Test Connection",
2928
"close": "Close",
3029
"copy": "Copy"
3130
},
3231
"tooltip": {
3332
"configure": "Open AI settings",
34-
"generate": "Generate SQL"
33+
"generate": "Generate SQL",
34+
"dialect": "Select database dialect"
3535
},
3636
"input": {
3737
"placeholder": "Enter your query in natural language..."
@@ -61,6 +61,12 @@
6161
"description": "Powerful reasoning AI"
6262
}
6363
},
64+
"dialect": {
65+
"label": "Database",
66+
"mysql": "MySQL",
67+
"postgresql": "PostgreSQL",
68+
"sqlite": "SQLite"
69+
},
6470
"providerLabel": "Provider",
6571
"message": {
6672
"configSaved": "Configuration saved successfully",

frontend/src/locales/zh.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
"save": "保存",
2626
"reset": "重置",
2727
"refresh": "刷新",
28-
"testConnection": "测试连接",
2928
"close": "关闭",
3029
"copy": "复制"
3130
},
3231
"tooltip": {
3332
"configure": "打开设置",
34-
"generate": "生成 SQL"
33+
"generate": "生成 SQL",
34+
"dialect": "选择目标数据库方言"
3535
},
3636
"input": {
3737
"placeholder": "用自然语言输入您的查询..."
@@ -61,6 +61,12 @@
6161
"description": "强大的推理 AI"
6262
}
6363
},
64+
"dialect": {
65+
"label": "数据库",
66+
"mysql": "MySQL",
67+
"postgresql": "PostgreSQL",
68+
"sqlite": "SQLite"
69+
},
6470
"providerLabel": "当前服务商",
6571
"message": {
6672
"configSaved": "配置保存成功",

0 commit comments

Comments
 (0)