commit
ab1b4ae227
|
|
@ -26,6 +26,7 @@ Thumbs.db
|
|||
*.swo
|
||||
*~
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# GitHub (keep workflows)
|
||||
.github/*
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import {
|
|||
MessageSquarePlus,
|
||||
Replace,
|
||||
Server,
|
||||
Settings,
|
||||
Play,
|
||||
Square,
|
||||
Trash2,
|
||||
|
|
@ -27,7 +26,6 @@ import {
|
|||
TestTube,
|
||||
} from "lucide-vue-next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -36,10 +34,8 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { useSettingsStore, type AiProvider, type AiApiStyle } from "@/stores/settingsStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { connectionIconType } from "@/lib/connectionPresentation";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
|
|
@ -49,7 +45,6 @@ import { buildAiContext, runAiStream, type AiAction } from "@/lib/ai";
|
|||
import { extractFirstSqlCodeBlock, shouldAttemptAiAutoExecute } from "@/lib/aiSqlExecutionPolicy";
|
||||
import { Marked } from "marked";
|
||||
import {
|
||||
aiTestConnection,
|
||||
aiCancelStream,
|
||||
saveAiConversation,
|
||||
loadAiConversations,
|
||||
|
|
@ -89,7 +84,6 @@ const emit = defineEmits<{
|
|||
const prompt = ref("");
|
||||
const messages = ref<ChatMessage[]>([]);
|
||||
const isGenerating = ref(false);
|
||||
const showSettings = ref(false);
|
||||
const scrollRef = ref<InstanceType<typeof ScrollArea> | null>(null);
|
||||
const activeAction = ref<AiAction>("generate");
|
||||
const currentSessionId = ref("");
|
||||
|
|
@ -168,20 +162,6 @@ function changeDatabase(database: string) {
|
|||
queryStore.updateDatabase(tab.id, database);
|
||||
}
|
||||
|
||||
const tempProvider = ref<AiProvider>(settings.aiConfig.provider);
|
||||
const tempApiKey = ref(settings.aiConfig.apiKey);
|
||||
const tempEndpoint = ref(settings.aiConfig.endpoint);
|
||||
const tempModel = ref(settings.aiConfig.model);
|
||||
const tempApiStyle = ref<AiApiStyle>(settings.aiConfig.apiStyle || "completions");
|
||||
const tempProxyEnabled = ref(!!settings.aiConfig.proxyEnabled);
|
||||
const tempProxyUrl = ref(settings.aiConfig.proxyUrl || "");
|
||||
|
||||
const providerDefaults: Record<AiProvider, { endpoint: string; model: string }> = {
|
||||
claude: { endpoint: "https://api.anthropic.com/v1/messages", model: "claude-sonnet-4-20250514" },
|
||||
openai: { endpoint: "https://api.openai.com/v1/chat/completions", model: "gpt-4o" },
|
||||
custom: { endpoint: "", model: "" },
|
||||
};
|
||||
|
||||
function appendAssistantDelta(assistantIdx: number, delta: string) {
|
||||
const msg = messages.value[assistantIdx];
|
||||
if (msg.isThinking) msg.isThinking = false;
|
||||
|
|
@ -209,63 +189,6 @@ function toggleReasoning(index: number) {
|
|||
expandedReasoning.value = next;
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
tempProvider.value = settings.aiConfig.provider;
|
||||
tempApiKey.value = settings.aiConfig.apiKey;
|
||||
tempEndpoint.value = settings.aiConfig.endpoint;
|
||||
tempModel.value = settings.aiConfig.model;
|
||||
tempApiStyle.value = settings.aiConfig.apiStyle || "completions";
|
||||
tempProxyEnabled.value = !!settings.aiConfig.proxyEnabled;
|
||||
tempProxyUrl.value = settings.aiConfig.proxyUrl || "";
|
||||
showSettings.value = true;
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
settings.updateAiConfig({
|
||||
provider: tempProvider.value,
|
||||
apiKey: tempApiKey.value,
|
||||
endpoint: tempEndpoint.value,
|
||||
model: tempModel.value,
|
||||
apiStyle: tempApiStyle.value,
|
||||
proxyEnabled: tempProxyEnabled.value,
|
||||
proxyUrl: tempProxyUrl.value,
|
||||
});
|
||||
showSettings.value = false;
|
||||
}
|
||||
|
||||
const testingAi = ref(false);
|
||||
const testResult = ref<"" | "success" | "error">("");
|
||||
const testError = ref("");
|
||||
|
||||
async function testAiConnection() {
|
||||
testingAi.value = true;
|
||||
testResult.value = "";
|
||||
testError.value = "";
|
||||
try {
|
||||
await aiTestConnection({
|
||||
provider: tempProvider.value,
|
||||
apiKey: tempApiKey.value,
|
||||
endpoint: tempEndpoint.value,
|
||||
model: tempModel.value,
|
||||
apiStyle: tempApiStyle.value,
|
||||
proxyEnabled: tempProxyEnabled.value,
|
||||
proxyUrl: tempProxyUrl.value,
|
||||
});
|
||||
testResult.value = "success";
|
||||
} catch (e: any) {
|
||||
testResult.value = "error";
|
||||
testError.value = e?.message || String(e);
|
||||
} finally {
|
||||
testingAi.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function selectProvider(provider: AiProvider) {
|
||||
tempProvider.value = provider;
|
||||
tempEndpoint.value = providerDefaults[provider].endpoint;
|
||||
tempModel.value = providerDefaults[provider].model;
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
nextTick(() => {
|
||||
const root = scrollRef.value?.$el as HTMLElement | undefined;
|
||||
|
|
@ -283,7 +206,7 @@ async function send() {
|
|||
|
||||
if (!props.connection || !props.tab) return;
|
||||
if (!settings.isConfigured()) {
|
||||
openSettings();
|
||||
toast(t("ai.noConfig"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -531,9 +454,6 @@ function formatInlineText(text: string): string {
|
|||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="clearMessages" :title="t('ai.clear')">
|
||||
<Trash2 class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="openSettings">
|
||||
<Settings class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="emit('close')">
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
|
|
@ -747,107 +667,6 @@ function formatInlineText(text: string): string {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog v-model:open="showSettings">
|
||||
<DialogContent class="sm:max-w-[420px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("ai.settings") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="grid gap-3 py-2">
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.provider") }}</Label>
|
||||
<Select :model-value="tempProvider" @update:model-value="(v: any) => selectProvider(v)">
|
||||
<SelectTrigger class="col-span-2 h-8 text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="claude">Claude</SelectItem>
|
||||
<SelectItem value="openai">OpenAI</SelectItem>
|
||||
<SelectItem value="custom">Custom</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">API Key</Label>
|
||||
<Input v-model="tempApiKey" type="password" autocomplete="off" class="col-span-2 h-8 text-xs" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">Endpoint</Label>
|
||||
<Input
|
||||
v-model="tempEndpoint"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
autocomplete="off"
|
||||
class="col-span-2 h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">Model</Label>
|
||||
<Input v-model="tempModel" autocomplete="off" class="col-span-2 h-8 text-xs" />
|
||||
</div>
|
||||
<div v-if="tempProvider !== 'claude'" class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">API</Label>
|
||||
<div class="col-span-2 flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="h-8 flex-1 text-xs"
|
||||
:class="{
|
||||
'border-blue-300 border-2 ring-2 ring-blue-300/50': tempApiStyle === 'completions',
|
||||
}"
|
||||
@click="tempApiStyle = 'completions'"
|
||||
>/chat/completions</Button
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="h-8 flex-1 text-xs"
|
||||
:class="{
|
||||
'border-blue-300 border-2 ring-2 ring-blue-300/50': tempApiStyle === 'responses',
|
||||
}"
|
||||
@click="tempApiStyle = 'responses'"
|
||||
>/responses</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.proxy") }}</Label>
|
||||
<label class="col-span-2 flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<input v-model="tempProxyEnabled" type="checkbox" class="h-4 w-4 shrink-0 accent-primary" />
|
||||
{{ t("ai.proxyEnable") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.proxyUrl") }}</Label>
|
||||
<Input
|
||||
v-model="tempProxyUrl"
|
||||
autocomplete="off"
|
||||
class="col-span-2 h-8 text-xs"
|
||||
placeholder="socks5://127.0.0.1:7890"
|
||||
:disabled="!tempProxyEnabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter class="flex items-center gap-2">
|
||||
<div class="flex-1 flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
:disabled="testingAi || !tempApiKey?.trim() || !tempEndpoint?.trim() || !tempModel?.trim()"
|
||||
@click="testAiConnection"
|
||||
>
|
||||
<Loader2 v-if="testingAi" class="h-3 w-3 animate-spin mr-1" />
|
||||
{{ t("connection.test") }}
|
||||
</Button>
|
||||
<span v-if="testResult === 'success'" class="text-xs text-green-500">{{ t("connection.testSuccess") }}</span>
|
||||
<span
|
||||
v-else-if="testResult === 'error'"
|
||||
class="text-xs text-destructive truncate max-w-[200px]"
|
||||
:title="testError"
|
||||
>{{ testError }}</span
|
||||
>
|
||||
</div>
|
||||
<Button size="sm" @click="saveSettings">{{ t("grid.save") }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,27 @@
|
|||
import { ref, watch, shallowRef, computed } from "vue";
|
||||
import type { EditorView as EditorViewType } from "@codemirror/view";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { FolderOpen, Settings, Trash2 } from "lucide-vue-next";
|
||||
import { FolderOpen, Loader2, Settings, Trash2 } from "lucide-vue-next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useSettingsStore, EDITOR_THEMES, FONT_FAMILIES, DEFAULT_EDITOR_SETTINGS } from "@/stores/settingsStore";
|
||||
import {
|
||||
useSettingsStore,
|
||||
EDITOR_THEMES,
|
||||
FONT_FAMILIES,
|
||||
DEFAULT_EDITOR_SETTINGS,
|
||||
type AiProvider,
|
||||
type AiApiStyle,
|
||||
} from "@/stores/settingsStore";
|
||||
import { loadEditorTheme, editorFontTheme } from "@/lib/editorThemes";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import type { JdbcDriverInfo, JdbcPluginStatus } from "@/types/database";
|
||||
import * as api from "@/lib/api";
|
||||
import { aiTestConnection } from "@/lib/api";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
|
@ -23,6 +31,7 @@ const { toast } = useToast();
|
|||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
initialTab?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -120,13 +129,15 @@ const jdbcDriverPathInput = ref("");
|
|||
|
||||
watch(
|
||||
() => props.open,
|
||||
(open) => {
|
||||
async (open) => {
|
||||
if (open) {
|
||||
activeSettingsTab.value = "editor";
|
||||
activeSettingsTab.value = props.initialTab || "editor";
|
||||
passwordMessage.value = "";
|
||||
oldPassword.value = "";
|
||||
newPassword.value = "";
|
||||
confirmNewPassword.value = "";
|
||||
await settingsStore.initAiConfig();
|
||||
syncAiEditState();
|
||||
void loadJdbcDrivers();
|
||||
void loadJdbcPluginStatus();
|
||||
}
|
||||
|
|
@ -273,6 +284,91 @@ async function deleteJdbcDriver(path: string) {
|
|||
}
|
||||
}
|
||||
|
||||
// ---------- AI Settings ----------
|
||||
const aiProviderDefaults: Record<AiProvider, { endpoint: string; model: string }> = {
|
||||
claude: { endpoint: "https://api.anthropic.com/v1/messages", model: "claude-sonnet-4-20250514" },
|
||||
openai: { endpoint: "https://api.openai.com/v1/chat/completions", model: "gpt-4o" },
|
||||
custom: { endpoint: "", model: "" },
|
||||
};
|
||||
|
||||
const aiEditProvider = ref<AiProvider>(settingsStore.aiConfig.provider);
|
||||
const aiEditApiKey = ref(settingsStore.aiConfig.apiKey);
|
||||
const aiEditEndpoint = ref(settingsStore.aiConfig.endpoint);
|
||||
const aiEditModel = ref(settingsStore.aiConfig.model);
|
||||
const aiEditApiStyle = ref<AiApiStyle>(settingsStore.aiConfig.apiStyle || "completions");
|
||||
const aiEditProxyEnabled = ref(!!settingsStore.aiConfig.proxyEnabled);
|
||||
const aiEditProxyUrl = ref(settingsStore.aiConfig.proxyUrl || "");
|
||||
|
||||
const aiTesting = ref(false);
|
||||
const aiTestResult = ref<"" | "success" | "error">("");
|
||||
const aiTestError = ref("");
|
||||
|
||||
function syncAiEditState() {
|
||||
aiEditProvider.value = settingsStore.aiConfig.provider;
|
||||
aiEditApiKey.value = settingsStore.aiConfig.apiKey;
|
||||
aiEditEndpoint.value = settingsStore.aiConfig.endpoint;
|
||||
aiEditModel.value = settingsStore.aiConfig.model;
|
||||
aiEditApiStyle.value = settingsStore.aiConfig.apiStyle || "completions";
|
||||
aiEditProxyEnabled.value = !!settingsStore.aiConfig.proxyEnabled;
|
||||
aiEditProxyUrl.value = settingsStore.aiConfig.proxyUrl || "";
|
||||
aiTestResult.value = "";
|
||||
aiTestError.value = "";
|
||||
}
|
||||
|
||||
function aiSelectProvider(provider: AiProvider) {
|
||||
aiEditProvider.value = provider;
|
||||
aiEditEndpoint.value = aiProviderDefaults[provider].endpoint;
|
||||
aiEditModel.value = aiProviderDefaults[provider].model;
|
||||
}
|
||||
|
||||
function aiHasChanges(): boolean {
|
||||
return (
|
||||
aiEditProvider.value !== settingsStore.aiConfig.provider ||
|
||||
aiEditApiKey.value !== settingsStore.aiConfig.apiKey ||
|
||||
aiEditEndpoint.value !== settingsStore.aiConfig.endpoint ||
|
||||
aiEditModel.value !== settingsStore.aiConfig.model ||
|
||||
aiEditApiStyle.value !== (settingsStore.aiConfig.apiStyle || "completions") ||
|
||||
aiEditProxyEnabled.value !== !!settingsStore.aiConfig.proxyEnabled ||
|
||||
aiEditProxyUrl.value !== (settingsStore.aiConfig.proxyUrl || "")
|
||||
);
|
||||
}
|
||||
|
||||
function aiApplySettings() {
|
||||
settingsStore.updateAiConfig({
|
||||
provider: aiEditProvider.value,
|
||||
apiKey: aiEditApiKey.value,
|
||||
endpoint: aiEditEndpoint.value,
|
||||
model: aiEditModel.value,
|
||||
apiStyle: aiEditApiStyle.value,
|
||||
proxyEnabled: aiEditProxyEnabled.value,
|
||||
proxyUrl: aiEditProxyUrl.value,
|
||||
});
|
||||
}
|
||||
|
||||
async function aiTestConn() {
|
||||
if (!aiEditApiKey.value.trim() || !aiEditEndpoint.value.trim() || !aiEditModel.value.trim()) return;
|
||||
aiTesting.value = true;
|
||||
aiTestResult.value = "";
|
||||
aiTestError.value = "";
|
||||
try {
|
||||
await aiTestConnection({
|
||||
provider: aiEditProvider.value,
|
||||
apiKey: aiEditApiKey.value,
|
||||
endpoint: aiEditEndpoint.value,
|
||||
model: aiEditModel.value,
|
||||
apiStyle: aiEditApiStyle.value,
|
||||
proxyEnabled: aiEditProxyEnabled.value,
|
||||
proxyUrl: aiEditProxyUrl.value,
|
||||
});
|
||||
aiTestResult.value = "success";
|
||||
} catch (e: any) {
|
||||
aiTestResult.value = "error";
|
||||
aiTestError.value = e?.message || String(e);
|
||||
} finally {
|
||||
aiTesting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- CodeMirror preview ----------
|
||||
const previewRef = ref<HTMLDivElement>();
|
||||
const previewView = shallowRef<EditorViewType | null>(null);
|
||||
|
|
@ -381,6 +477,7 @@ watch(
|
|||
<TabsList class="w-full">
|
||||
<TabsTrigger value="editor" class="flex-1">{{ t("settings.editorTab") }}</TabsTrigger>
|
||||
<TabsTrigger value="appearance" class="flex-1">{{ t("settings.appearanceTab") }}</TabsTrigger>
|
||||
<TabsTrigger value="ai" class="flex-1">{{ t("settings.aiTab") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="!isWeb" value="jdbc" class="flex-1">{{ t("settings.jdbcTab") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="isWeb" value="security" class="flex-1">{{ t("settings.securityTab") }}</TabsTrigger>
|
||||
<TabsTrigger value="about" class="flex-1">{{ t("settings.aboutTab") }}</TabsTrigger>
|
||||
|
|
@ -525,7 +622,7 @@ watch(
|
|||
type="button"
|
||||
variant="outline"
|
||||
class="h-auto justify-start p-3"
|
||||
:class="editAppLayout === 'separated' ? 'border-primary bg-primary/10' : ''"
|
||||
:class="editAppLayout === 'separated' ? 'border-blue-300 border-2 ring-2 ring-blue-300/50' : ''"
|
||||
@click="setAppLayout('separated')"
|
||||
>
|
||||
<div class="text-left">
|
||||
|
|
@ -537,7 +634,7 @@ watch(
|
|||
type="button"
|
||||
variant="outline"
|
||||
class="h-auto justify-start p-3"
|
||||
:class="editAppLayout === 'classic' ? 'border-primary bg-primary/10' : ''"
|
||||
:class="editAppLayout === 'classic' ? 'border-blue-300 border-2 ring-2 ring-blue-300/50' : ''"
|
||||
@click="setAppLayout('classic')"
|
||||
>
|
||||
<div class="text-left">
|
||||
|
|
@ -562,6 +659,111 @@ watch(
|
|||
</DialogFooter>
|
||||
</TabsContent>
|
||||
|
||||
<!-- AI Settings Tab -->
|
||||
<TabsContent value="ai" class="space-y-5 py-2">
|
||||
<p class="text-xs text-muted-foreground">{{ t("ai.settingsHint") }}</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.provider") }}</Label>
|
||||
<Select :model-value="aiEditProvider" @update:model-value="(v: any) => aiSelectProvider(v)">
|
||||
<SelectTrigger class="col-span-2 h-8 text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="claude">Claude</SelectItem>
|
||||
<SelectItem value="openai">OpenAI</SelectItem>
|
||||
<SelectItem value="custom">Custom</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">API Key</Label>
|
||||
<Input v-model="aiEditApiKey" type="password" autocomplete="off" class="col-span-2 h-8 text-xs" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">Endpoint</Label>
|
||||
<Input
|
||||
v-model="aiEditEndpoint"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
autocomplete="off"
|
||||
class="col-span-2 h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">Model</Label>
|
||||
<Input v-model="aiEditModel" autocomplete="off" class="col-span-2 h-8 text-xs" />
|
||||
</div>
|
||||
|
||||
<div v-if="aiEditProvider !== 'claude'" class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">API</Label>
|
||||
<div class="col-span-2 flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="h-8 flex-1 text-xs"
|
||||
:class="{ 'border-blue-300 border-2 ring-2 ring-blue-300/50': aiEditApiStyle === 'completions' }"
|
||||
@click="aiEditApiStyle = 'completions'"
|
||||
>/chat/completions</Button
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="h-8 flex-1 text-xs"
|
||||
:class="{ 'border-blue-300 border-2 ring-2 ring-blue-300/50': aiEditApiStyle === 'responses' }"
|
||||
@click="aiEditApiStyle = 'responses'"
|
||||
>/responses</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.proxy") }}</Label>
|
||||
<label class="col-span-2 flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<input v-model="aiEditProxyEnabled" type="checkbox" class="h-4 w-4 shrink-0 accent-primary" />
|
||||
{{ t("ai.proxyEnable") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.proxyUrl") }}</Label>
|
||||
<Input
|
||||
v-model="aiEditProxyUrl"
|
||||
autocomplete="off"
|
||||
class="col-span-2 h-8 text-xs"
|
||||
placeholder="socks5://127.0.0.1:7890"
|
||||
:disabled="!aiEditProxyEnabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="flex items-center gap-2">
|
||||
<div class="flex-1 flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
:disabled="aiTesting || !aiEditApiKey?.trim() || !aiEditEndpoint?.trim() || !aiEditModel?.trim()"
|
||||
@click="aiTestConn"
|
||||
>
|
||||
<Loader2 v-if="aiTesting" class="h-3 w-3 animate-spin mr-1" />
|
||||
{{ t("connection.test") }}
|
||||
</Button>
|
||||
<span v-if="aiTestResult === 'success'" class="text-xs text-green-500">{{
|
||||
t("connection.testSuccess")
|
||||
}}</span>
|
||||
<span
|
||||
v-else-if="aiTestResult === 'error'"
|
||||
class="text-xs text-destructive truncate max-w-[200px]"
|
||||
:title="aiTestError"
|
||||
>{{ aiTestError }}</span
|
||||
>
|
||||
</div>
|
||||
<Button variant="outline" @click="emit('update:open', false)">{{ t("common.close") }}</Button>
|
||||
<Button :disabled="!aiHasChanges()" @click="aiApplySettings">{{ t("settings.apply") }}</Button>
|
||||
</DialogFooter>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent v-if="!isWeb" value="jdbc" class="space-y-5 py-2">
|
||||
<div class="rounded-md border bg-muted/20 p-4">
|
||||
<div class="flex min-h-12 items-center justify-between gap-3">
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { useDialogSources } from "@/composables/useDialogSources";
|
|||
const props = defineProps<{
|
||||
showConnectionDialog: boolean;
|
||||
showSettingsDialog: boolean;
|
||||
settingsInitialTab?: string;
|
||||
showDangerDialog: boolean;
|
||||
dangerSql: string;
|
||||
}>();
|
||||
|
|
@ -98,7 +99,11 @@ watch(
|
|||
@connect-succeeded="emit('connectSucceeded', $event)"
|
||||
@connect-failed="emit('connectFailed', $event)"
|
||||
/>
|
||||
<EditorSettingsDialog :open="showSettingsDialog" @update:open="emit('update:showSettingsDialog', $event)" />
|
||||
<EditorSettingsDialog
|
||||
:open="showSettingsDialog"
|
||||
:initial-tab="settingsInitialTab || 'editor'"
|
||||
@update:open="emit('update:showSettingsDialog', $event)"
|
||||
/>
|
||||
<DangerConfirmDialog
|
||||
:open="showDangerDialog"
|
||||
:sql="dangerSql"
|
||||
|
|
|
|||
|
|
@ -452,6 +452,7 @@ export default {
|
|||
ai: {
|
||||
placeholder: "Describe your query in natural language...",
|
||||
settings: "AI Settings",
|
||||
noConfig: "AI is not configured. Please set up your AI provider in Settings > AI.",
|
||||
provider: "Provider",
|
||||
run: "Run",
|
||||
readingSchema: "Reading schema",
|
||||
|
|
@ -896,6 +897,7 @@ export default {
|
|||
title: "Settings",
|
||||
editorTab: "Editor",
|
||||
appearanceTab: "Appearance",
|
||||
aiTab: "AI",
|
||||
jdbcTab: "JDBC Drivers",
|
||||
securityTab: "Security",
|
||||
aboutTab: "About",
|
||||
|
|
|
|||
|
|
@ -443,6 +443,7 @@ export default {
|
|||
ai: {
|
||||
placeholder: "描述你想查什么...",
|
||||
settings: "AI 设置",
|
||||
noConfig: "AI 尚未配置。请在 设置 > AI 中配置你的 AI 服务。",
|
||||
provider: "提供商",
|
||||
run: "执行",
|
||||
readingSchema: "读取结构",
|
||||
|
|
@ -881,6 +882,7 @@ export default {
|
|||
title: "设置",
|
||||
editorTab: "编辑器",
|
||||
appearanceTab: "外观",
|
||||
aiTab: "AI",
|
||||
jdbcTab: "JDBC 驱动",
|
||||
securityTab: "安全",
|
||||
aboutTab: "关于我们",
|
||||
|
|
|
|||
Loading…
Reference in New Issue