feat: initial release
Multi-database management tool built with Tauri + Vue 3. Supports MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server.
This commit is contained in:
commit
b2e6dfad7e
156 files changed
+21979
No files matched your search
@@ -0,0 +1,465 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import type { ConnectionConfig, TreeNode } from "@/types/database";
|
||||
import * as api from "@/lib/tauri";
|
||||
|
||||
export const useConnectionStore = defineStore("connection", () => {
|
||||
const connections = ref<ConnectionConfig[]>([]);
|
||||
const activeConnectionId = ref<string | null>(null);
|
||||
const treeNodes = ref<TreeNode[]>([]);
|
||||
const connectedIds = ref<Set<string>>(new Set());
|
||||
const editingConnectionId = ref<string | null>(null);
|
||||
|
||||
function startEditing(id: string) {
|
||||
editingConnectionId.value = id;
|
||||
}
|
||||
|
||||
function stopEditing() {
|
||||
editingConnectionId.value = null;
|
||||
}
|
||||
|
||||
function getConfig(connectionId: string) {
|
||||
return connections.value.find((c) => c.id === connectionId);
|
||||
}
|
||||
|
||||
function addConnection(config: ConnectionConfig) {
|
||||
connections.value.push(config);
|
||||
persistConnections();
|
||||
}
|
||||
|
||||
function removeConnection(id: string) {
|
||||
connections.value = connections.value.filter((c) => c.id !== id);
|
||||
treeNodes.value = treeNodes.value.filter((n) => n.id !== id);
|
||||
if (activeConnectionId.value === id) {
|
||||
activeConnectionId.value = null;
|
||||
}
|
||||
persistConnections();
|
||||
}
|
||||
|
||||
function updateConnection(config: ConnectionConfig) {
|
||||
const idx = connections.value.findIndex((c) => c.id === config.id);
|
||||
if (idx >= 0) connections.value[idx] = config;
|
||||
const node = findNode(treeNodes.value, config.id);
|
||||
if (node) {
|
||||
node.label = config.name;
|
||||
node.isExpanded = false;
|
||||
node.children = [];
|
||||
}
|
||||
connectedIds.value.delete(config.id);
|
||||
persistConnections();
|
||||
}
|
||||
|
||||
async function connect(config: ConnectionConfig) {
|
||||
const id = await api.connectDb(config);
|
||||
activeConnectionId.value = id;
|
||||
connectedIds.value.add(id);
|
||||
|
||||
const node: TreeNode = {
|
||||
id,
|
||||
label: config.name,
|
||||
type: "connection",
|
||||
connectionId: id,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
};
|
||||
const existing = treeNodes.value.findIndex((n) => n.id === id);
|
||||
if (existing >= 0) {
|
||||
treeNodes.value[existing] = node;
|
||||
} else {
|
||||
treeNodes.value.push(node);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
async function disconnect(connectionId: string) {
|
||||
await api.disconnectDb(connectionId);
|
||||
connectedIds.value.delete(connectionId);
|
||||
treeNodes.value = treeNodes.value.filter((n) => n.id !== connectionId);
|
||||
if (activeConnectionId.value === connectionId) {
|
||||
activeConnectionId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureConnected(connectionId: string) {
|
||||
if (connectedIds.value.has(connectionId)) return;
|
||||
const config = getConfig(connectionId);
|
||||
if (!config) throw new Error("Connection config not found");
|
||||
await api.connectDb(config);
|
||||
connectedIds.value.add(connectionId);
|
||||
activeConnectionId.value = connectionId;
|
||||
}
|
||||
|
||||
async function loadDatabases(connectionId: string) {
|
||||
const node = findNode(treeNodes.value, connectionId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
await ensureConnected(connectionId);
|
||||
const databases = await api.listDatabases(connectionId);
|
||||
node.children = databases.map((db) => ({
|
||||
id: `${connectionId}:${db.name}`,
|
||||
label: db.name,
|
||||
type: "database" as const,
|
||||
connectionId,
|
||||
database: db.name,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRedisDatabases(connectionId: string) {
|
||||
const node = findNode(treeNodes.value, connectionId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
await ensureConnected(connectionId);
|
||||
const dbs = await api.redisListDatabases(connectionId);
|
||||
node.children = dbs.map((db) => ({
|
||||
id: `${connectionId}:db${db}`,
|
||||
label: `db${db}`,
|
||||
type: "redis-db" as const,
|
||||
connectionId,
|
||||
database: String(db),
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRedisKeys(connectionId: string, db: number, nodeId: string) {
|
||||
const node = findNode(treeNodes.value, nodeId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
const keys = await api.redisScanKeys(connectionId, db, "*", 500);
|
||||
node.children = keys.map((k) => ({
|
||||
id: `${nodeId}:${k.key}`,
|
||||
label: `${k.key} [${k.key_type}]${k.ttl > 0 ? ` TTL:${k.ttl}` : ""}`,
|
||||
type: "redis-key" as const,
|
||||
connectionId,
|
||||
database: String(db),
|
||||
isExpanded: false,
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMongoDatabases(connectionId: string) {
|
||||
const node = findNode(treeNodes.value, connectionId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
await ensureConnected(connectionId);
|
||||
const dbs = await api.mongoListDatabases(connectionId);
|
||||
node.children = dbs.map((db) => ({
|
||||
id: `${connectionId}:${db}`,
|
||||
label: db,
|
||||
type: "mongo-db" as const,
|
||||
connectionId,
|
||||
database: db,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMongoCollections(connectionId: string, database: string) {
|
||||
const nodeId = `${connectionId}:${database}`;
|
||||
const node = findNode(treeNodes.value, nodeId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
const collections = await api.mongoListCollections(connectionId, database);
|
||||
node.children = collections.map((col) => ({
|
||||
id: `${nodeId}:${col}`,
|
||||
label: col,
|
||||
type: "mongo-collection" as const,
|
||||
connectionId,
|
||||
database,
|
||||
isExpanded: false,
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSchemas(connectionId: string, database: string) {
|
||||
const nodeId = `${connectionId}:${database}`;
|
||||
const node = findNode(treeNodes.value, nodeId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
const schemas = await api.listSchemas(connectionId, database);
|
||||
node.children = schemas.map((s) => ({
|
||||
id: `${connectionId}:${database}:${s}`,
|
||||
label: s,
|
||||
type: "schema" as const,
|
||||
connectionId,
|
||||
database,
|
||||
schema: s,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTables(connectionId: string, database: string, schema?: string) {
|
||||
const nodeId = schema
|
||||
? `${connectionId}:${database}:${schema}`
|
||||
: `${connectionId}:${database}`;
|
||||
const node = findNode(treeNodes.value, nodeId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
const querySchema = schema || database;
|
||||
const tables = await api.listTables(connectionId, database, querySchema);
|
||||
node.children = tables.map((t) => ({
|
||||
id: `${nodeId}:${t.name}`,
|
||||
label: t.name,
|
||||
type: (t.table_type === "VIEW" ? "view" : "table") as "view" | "table",
|
||||
connectionId,
|
||||
database,
|
||||
schema,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTableGroups(connectionId: string, database: string, table: string, schema?: string) {
|
||||
const parentId = schema
|
||||
? `${connectionId}:${database}:${schema}:${table}`
|
||||
: `${connectionId}:${database}:${table}`;
|
||||
const node = findNode(treeNodes.value, parentId);
|
||||
if (!node) return;
|
||||
|
||||
node.children = [
|
||||
{ id: `${parentId}:__columns`, label: "tree.columns", type: "group-columns", connectionId, database, schema, tableName: table, isExpanded: false, children: [] },
|
||||
{ id: `${parentId}:__indexes`, label: "tree.indexes", type: "group-indexes", connectionId, database, schema, tableName: table, isExpanded: false, children: [] },
|
||||
{ id: `${parentId}:__fkeys`, label: "tree.foreignKeys", type: "group-fkeys", connectionId, database, schema, tableName: table, isExpanded: false, children: [] },
|
||||
{ id: `${parentId}:__triggers`, label: "tree.triggers", type: "group-triggers", connectionId, database, schema, tableName: table, isExpanded: false, children: [] },
|
||||
];
|
||||
node.isExpanded = true;
|
||||
}
|
||||
|
||||
async function loadColumns(connectionId: string, database: string, table: string, schema?: string) {
|
||||
const parentId = schema
|
||||
? `${connectionId}:${database}:${schema}:${table}:__columns`
|
||||
: `${connectionId}:${database}:${table}:__columns`;
|
||||
const node = findNode(treeNodes.value, parentId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
const querySchema = schema || database;
|
||||
const columns = await api.getColumns(connectionId, database, querySchema, table);
|
||||
node.children = columns.map((col) => ({
|
||||
id: `${parentId}:${col.name}`,
|
||||
label: `${col.name} (${col.data_type})`,
|
||||
type: "column" as const,
|
||||
connectionId,
|
||||
database,
|
||||
schema,
|
||||
meta: col,
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadIndexes(connectionId: string, database: string, table: string, schema?: string) {
|
||||
const parentId = schema
|
||||
? `${connectionId}:${database}:${schema}:${table}:__indexes`
|
||||
: `${connectionId}:${database}:${table}:__indexes`;
|
||||
const node = findNode(treeNodes.value, parentId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
const querySchema = schema || database;
|
||||
const indexes = await api.listIndexes(connectionId, database, querySchema, table);
|
||||
node.children = indexes.map((idx) => ({
|
||||
id: `${parentId}:${idx.name}`,
|
||||
label: `${idx.name} (${idx.columns.join(", ")})`,
|
||||
type: "index" as const,
|
||||
connectionId,
|
||||
database,
|
||||
schema,
|
||||
meta: idx,
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadForeignKeys(connectionId: string, database: string, table: string, schema?: string) {
|
||||
const parentId = schema
|
||||
? `${connectionId}:${database}:${schema}:${table}:__fkeys`
|
||||
: `${connectionId}:${database}:${table}:__fkeys`;
|
||||
const node = findNode(treeNodes.value, parentId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
const querySchema = schema || database;
|
||||
const fkeys = await api.listForeignKeys(connectionId, database, querySchema, table);
|
||||
node.children = fkeys.map((fk) => ({
|
||||
id: `${parentId}:${fk.name}`,
|
||||
label: `${fk.column} → ${fk.ref_table}.${fk.ref_column}`,
|
||||
type: "fkey" as const,
|
||||
connectionId,
|
||||
database,
|
||||
schema,
|
||||
meta: fk,
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTriggers(connectionId: string, database: string, table: string, schema?: string) {
|
||||
const parentId = schema
|
||||
? `${connectionId}:${database}:${schema}:${table}:__triggers`
|
||||
: `${connectionId}:${database}:${table}:__triggers`;
|
||||
const node = findNode(treeNodes.value, parentId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
const querySchema = schema || database;
|
||||
const triggers = await api.listTriggers(connectionId, database, querySchema, table);
|
||||
node.children = triggers.map((tr) => ({
|
||||
id: `${parentId}:${tr.name}`,
|
||||
label: `${tr.name} (${tr.timing} ${tr.event})`,
|
||||
type: "trigger" as const,
|
||||
connectionId,
|
||||
database,
|
||||
schema,
|
||||
meta: tr,
|
||||
}));
|
||||
node.isExpanded = true;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function findNode(nodes: TreeNode[], id: string): TreeNode | null {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) return node;
|
||||
if (node.children) {
|
||||
const found = findNode(node.children, id);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function persistConnections() {
|
||||
api.saveConnections(connections.value).catch(() => {});
|
||||
}
|
||||
|
||||
async function exportConnectionsToFile() {
|
||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
||||
const { writeTextFile } = await import("@tauri-apps/plugin-fs");
|
||||
const path = await save({ filters: [{ name: "JSON", extensions: ["json"] }], defaultPath: "dbx-connections.json" });
|
||||
if (!path) return;
|
||||
const data = connections.value.map((c) => ({ ...c, password: "" }));
|
||||
await writeTextFile(path, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
async function importConnectionsFromFile() {
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const { readTextFile } = await import("@tauri-apps/plugin-fs");
|
||||
const path = await open({ filters: [{ name: "JSON", extensions: ["json"] }], multiple: false });
|
||||
if (!path) return;
|
||||
const json = await readTextFile(path as string);
|
||||
const imported: ConnectionConfig[] = JSON.parse(json);
|
||||
for (const config of imported) {
|
||||
if (!connections.value.find((c) => c.id === config.id)) {
|
||||
config.id = crypto.randomUUID();
|
||||
addConnection(config);
|
||||
treeNodes.value.push({
|
||||
id: config.id,
|
||||
label: config.name,
|
||||
type: "connection" as const,
|
||||
connectionId: config.id,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function initFromDisk() {
|
||||
const saved = await api.loadConnections();
|
||||
connections.value = saved;
|
||||
treeNodes.value = saved.map((config) => ({
|
||||
id: config.id,
|
||||
label: config.name,
|
||||
type: "connection" as const,
|
||||
connectionId: config.id,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
connections,
|
||||
activeConnectionId,
|
||||
treeNodes,
|
||||
getConfig,
|
||||
addConnection,
|
||||
updateConnection,
|
||||
removeConnection,
|
||||
editingConnectionId,
|
||||
startEditing,
|
||||
stopEditing,
|
||||
connect,
|
||||
disconnect,
|
||||
ensureConnected,
|
||||
initFromDisk,
|
||||
loadDatabases,
|
||||
loadRedisDatabases,
|
||||
loadRedisKeys,
|
||||
loadMongoDatabases,
|
||||
loadMongoCollections,
|
||||
loadSchemas,
|
||||
loadTables,
|
||||
loadTableGroups,
|
||||
loadColumns,
|
||||
loadIndexes,
|
||||
loadForeignKeys,
|
||||
loadTriggers,
|
||||
exportConnectionsToFile,
|
||||
importConnectionsFromFile,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import * as api from "@/lib/tauri";
|
||||
import type { HistoryEntry } from "@/lib/tauri";
|
||||
|
||||
export const useHistoryStore = defineStore("history", () => {
|
||||
const entries = ref<HistoryEntry[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
entries.value = await api.loadHistory(200, 0);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function add(entry: Omit<HistoryEntry, "id" | "executed_at">) {
|
||||
const full: HistoryEntry = {
|
||||
...entry,
|
||||
id: crypto.randomUUID(),
|
||||
executed_at: new Date().toISOString(),
|
||||
};
|
||||
await api.saveHistory(full);
|
||||
entries.value.unshift(full);
|
||||
if (entries.value.length > 200) entries.value.pop();
|
||||
}
|
||||
|
||||
async function remove(id: string) {
|
||||
await api.deleteHistoryEntry(id);
|
||||
entries.value = entries.value.filter((e) => e.id !== id);
|
||||
}
|
||||
|
||||
async function clear() {
|
||||
await api.clearHistory();
|
||||
entries.value = [];
|
||||
}
|
||||
|
||||
return { entries, loading, load, add, remove, clear };
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import type { QueryTab } from "@/types/database";
|
||||
import * as api from "@/lib/tauri";
|
||||
|
||||
export const useQueryStore = defineStore("query", () => {
|
||||
const tabs = ref<QueryTab[]>([]);
|
||||
const activeTabId = ref<string | null>(null);
|
||||
const MAX_CACHED_RESULTS = 10;
|
||||
|
||||
function findTabByTitle(connectionId: string, title: string) {
|
||||
return tabs.value.find((t) => t.connectionId === connectionId && t.title === title);
|
||||
}
|
||||
|
||||
function createTab(connectionId: string, database: string, title?: string, mode: "data" | "query" | "redis" | "mongo" = "query") {
|
||||
if (title) {
|
||||
const existing = findTabByTitle(connectionId, title);
|
||||
if (existing) {
|
||||
activeTabId.value = existing.id;
|
||||
return existing.id;
|
||||
}
|
||||
}
|
||||
|
||||
const id = crypto.randomUUID();
|
||||
const tab: QueryTab = {
|
||||
id,
|
||||
title: title || `Query ${tabs.value.length + 1}`,
|
||||
connectionId,
|
||||
database,
|
||||
sql: "",
|
||||
isExecuting: false,
|
||||
mode,
|
||||
};
|
||||
tabs.value.push(tab);
|
||||
activeTabId.value = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
function closeTab(id: string) {
|
||||
const idx = tabs.value.findIndex((t) => t.id === id);
|
||||
if (idx < 0) return;
|
||||
tabs.value.splice(idx, 1);
|
||||
if (activeTabId.value === id) {
|
||||
activeTabId.value = tabs.value[Math.min(idx, tabs.value.length - 1)]?.id ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateSql(id: string, sql: string) {
|
||||
const tab = tabs.value.find((t) => t.id === id);
|
||||
if (tab) tab.sql = sql;
|
||||
}
|
||||
|
||||
function setTableMeta(id: string, meta: NonNullable<QueryTab["tableMeta"]>) {
|
||||
const tab = tabs.value.find((t) => t.id === id);
|
||||
if (tab) tab.tableMeta = meta;
|
||||
}
|
||||
|
||||
async function executeCurrentTab() {
|
||||
const tab = tabs.value.find((t) => t.id === activeTabId.value);
|
||||
if (!tab || !tab.sql.trim()) return;
|
||||
|
||||
tab.isExecuting = true;
|
||||
try {
|
||||
tab.result = await api.executeQuery(tab.connectionId, tab.database, tab.sql);
|
||||
} catch (e: any) {
|
||||
tab.result = {
|
||||
columns: ["Error"],
|
||||
rows: [[String(e)]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 0,
|
||||
};
|
||||
} finally {
|
||||
tab.isExecuting = false;
|
||||
}
|
||||
trimResultCache();
|
||||
}
|
||||
|
||||
function trimResultCache() {
|
||||
const inactive = tabs.value.filter((t) => t.id !== activeTabId.value && t.result);
|
||||
if (inactive.length > MAX_CACHED_RESULTS) {
|
||||
const toEvict = inactive.slice(0, inactive.length - MAX_CACHED_RESULTS);
|
||||
toEvict.forEach((t) => { t.result = undefined; });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tabs,
|
||||
activeTabId,
|
||||
createTab,
|
||||
closeTab,
|
||||
updateSql,
|
||||
setTableMeta,
|
||||
executeCurrentTab,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
export type AiProvider = "claude" | "openai" | "custom";
|
||||
|
||||
export interface AiConfig {
|
||||
provider: AiProvider;
|
||||
apiKey: string;
|
||||
endpoint: string;
|
||||
model: string;
|
||||
}
|
||||
|
||||
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: "" },
|
||||
};
|
||||
|
||||
export const useSettingsStore = defineStore("settings", () => {
|
||||
const saved = localStorage.getItem("dbx-ai-config");
|
||||
const aiConfig = ref<AiConfig>(saved ? JSON.parse(saved) : { ...defaultConfigs.claude, apiKey: "" });
|
||||
|
||||
function updateAiConfig(config: Partial<AiConfig>) {
|
||||
Object.assign(aiConfig.value, config);
|
||||
if (config.provider && config.provider !== aiConfig.value.provider) {
|
||||
const defaults = defaultConfigs[config.provider];
|
||||
aiConfig.value.endpoint = defaults.endpoint;
|
||||
aiConfig.value.model = defaults.model;
|
||||
}
|
||||
localStorage.setItem("dbx-ai-config", JSON.stringify(aiConfig.value));
|
||||
}
|
||||
|
||||
function isConfigured(): boolean {
|
||||
return !!aiConfig.value.apiKey && !!aiConfig.value.endpoint;
|
||||
}
|
||||
|
||||
return { aiConfig, updateAiConfig, isConfigured };
|
||||
});
|
||||
Reference in new issue
Block a user