mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-28 16:09:17 +00:00
feat: add AiApiStyle support and integrate into AI completion and streaming functions
This commit is contained in:
@@ -15,11 +15,11 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { useSettingsStore, type AiProvider } from "@/stores/settingsStore";
|
||||
import { useSettingsStore, type AiProvider, type AiApiStyle } from "@/stores/settingsStore";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { buildAiContext, runAiStream } from "@/lib/ai";
|
||||
import { listDatabases, redisListDatabases, mongoListDatabases } from "@/lib/tauri";
|
||||
import { listDatabases, redisListDatabases, mongoListDatabases, aiTestConnection } from "@/lib/tauri";
|
||||
import type { AiMessage } from "@/lib/tauri";
|
||||
import type { ConnectionConfig, QueryTab } from "@/types/database";
|
||||
|
||||
@@ -102,6 +102,7 @@ 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 providerDefaults: Record<AiProvider, { endpoint: string; model: string }> = {
|
||||
claude: { endpoint: "https://api.anthropic.com/v1/messages", model: "claude-sonnet-4-20250514" },
|
||||
@@ -119,6 +120,7 @@ function openSettings() {
|
||||
tempApiKey.value = settings.aiConfig.apiKey;
|
||||
tempEndpoint.value = settings.aiConfig.endpoint;
|
||||
tempModel.value = settings.aiConfig.model;
|
||||
tempApiStyle.value = settings.aiConfig.apiStyle || "completions";
|
||||
showSettings.value = true;
|
||||
}
|
||||
|
||||
@@ -128,10 +130,36 @@ function saveSettings() {
|
||||
apiKey: tempApiKey.value,
|
||||
endpoint: tempEndpoint.value,
|
||||
model: tempModel.value,
|
||||
apiStyle: tempApiStyle.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,
|
||||
});
|
||||
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;
|
||||
@@ -279,7 +307,7 @@ function formatInlineText(text: string): string {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="flex">
|
||||
<div v-else-if="msg.content" class="flex">
|
||||
<div class="max-w-[95%] rounded-lg bg-muted px-3 py-2 text-xs leading-relaxed">
|
||||
<template v-for="(seg, j) in parseMessage(msg.content)" :key="j">
|
||||
<div v-if="seg.type === 'text'" class="whitespace-normal">
|
||||
@@ -350,12 +378,12 @@ function formatInlineText(text: string): string {
|
||||
rows="4"
|
||||
class="flex-1 resize-none bg-transparent text-xs outline-none placeholder:text-muted-foreground"
|
||||
:placeholder="t('ai.placeholder')"
|
||||
:disabled="isGenerating"
|
||||
:disabled="isGenerating || !props.tab?.database"
|
||||
@keydown.enter.exact="send"
|
||||
/>
|
||||
<button
|
||||
class="h-7 w-7 shrink-0 rounded-full bg-foreground text-background flex items-center justify-center disabled:opacity-30"
|
||||
:disabled="isGenerating || !prompt.trim()"
|
||||
:disabled="isGenerating || !prompt.trim() || !props.tab?.database"
|
||||
@click="send"
|
||||
>
|
||||
<ArrowUp class="h-4 w-4" />
|
||||
@@ -388,14 +416,29 @@ function formatInlineText(text: string): string {
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">Endpoint</Label>
|
||||
<Input v-model="tempEndpoint" class="col-span-2 h-8 text-xs" />
|
||||
<Input v-model="tempEndpoint" placeholder="https://api.openai.com/v1" 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" 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="{ 'bg-accent': tempApiStyle === 'completions' }" @click="tempApiStyle = 'completions'">/chat/completions</Button>
|
||||
<Button size="sm" variant="outline" class="h-8 flex-1 text-xs" :class="{ 'bg-accent': tempApiStyle === 'responses' }" @click="tempApiStyle = 'responses'">/responses</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<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>
|
||||
|
||||
@@ -22,6 +22,8 @@ const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
data-slot="input"
|
||||
:class="cn(
|
||||
'dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
|
||||
@@ -58,6 +58,10 @@ export async function saveAiConfig(config: AiConfig): Promise<void> {
|
||||
return invoke("save_ai_config", { config });
|
||||
}
|
||||
|
||||
export async function aiTestConnection(config: AiConfig): Promise<string> {
|
||||
return invoke("ai_test_connection", { config });
|
||||
}
|
||||
|
||||
export async function loadAiConfig(): Promise<AiConfig | null> {
|
||||
return invoke("load_ai_config");
|
||||
}
|
||||
|
||||
@@ -3,22 +3,24 @@ import { ref } from "vue";
|
||||
import * as api from "@/lib/tauri";
|
||||
|
||||
export type AiProvider = "claude" | "openai" | "custom";
|
||||
export type AiApiStyle = "completions" | "responses";
|
||||
|
||||
export interface AiConfig {
|
||||
provider: AiProvider;
|
||||
apiKey: string;
|
||||
endpoint: string;
|
||||
model: string;
|
||||
apiStyle: AiApiStyle;
|
||||
}
|
||||
|
||||
const defaultConfigs: Record<AiProvider, Omit<AiConfig, "apiKey">> = {
|
||||
claude: { provider: "claude", endpoint: "https://api.anthropic.com/v1/messages", model: "claude-sonnet-4-20250514" },
|
||||
openai: { provider: "openai", endpoint: "https://api.openai.com/v1/chat/completions", model: "gpt-4o" },
|
||||
custom: { provider: "custom", endpoint: "", model: "" },
|
||||
claude: { provider: "claude", endpoint: "https://api.anthropic.com/v1/messages", model: "claude-sonnet-4-20250514", apiStyle: "completions" },
|
||||
openai: { provider: "openai", endpoint: "https://api.openai.com/v1/chat/completions", model: "gpt-4o", apiStyle: "completions" },
|
||||
custom: { provider: "custom", endpoint: "", model: "", apiStyle: "completions" },
|
||||
};
|
||||
|
||||
export const useSettingsStore = defineStore("settings", () => {
|
||||
const aiConfig = ref<AiConfig>({ ...defaultConfigs.claude, apiKey: "" });
|
||||
const aiConfig = ref<AiConfig>({ ...defaultConfigs.claude, apiKey: "", apiStyle: "completions" });
|
||||
const isAiConfigLoaded = ref(false);
|
||||
|
||||
async function initAiConfig() {
|
||||
|
||||
Reference in New Issue
Block a user