diff --git a/src/components/ui/select/select-item.svelte b/src/components/ui/select/select-item.svelte index b83ac54..1c875be 100644 --- a/src/components/ui/select/select-item.svelte +++ b/src/components/ui/select/select-item.svelte @@ -31,7 +31,7 @@ on:pointermove > {#if image} - {label + {label} {:else} diff --git a/src/database/migrations.test.ts b/src/database/migrations.test.ts index 8960179..55127b1 100644 --- a/src/database/migrations.test.ts +++ b/src/database/migrations.test.ts @@ -968,12 +968,12 @@ describe("Migration Tests", () => { // Verify that the initial data was inserted correctly const aiSdkCount = db.get<{ count: number }>(sql`SELECT COUNT(*) as count FROM aiSdk`).count; - expect(aiSdkCount).toBe(10); // 10 SDKs were inserted + expect(aiSdkCount).toBe(11); // 11 SDKs were inserted const aiServiceCount = db.get<{ count: number }>( sql`SELECT COUNT(*) as count FROM aiService`, ).count; - expect(aiServiceCount).toBe(10); // 10 services were inserted + expect(aiServiceCount).toBe(11); // 11 services were inserted // Check some specific entries to ensure data integrity const openaiSdk = db.get<{ id: string; slug: string; name: string }>( diff --git a/src/database/migrations/0011_easy_slapstick.sql b/src/database/migrations/0011_easy_slapstick.sql index a0da06d..198f4f0 100644 --- a/src/database/migrations/0011_easy_slapstick.sql +++ b/src/database/migrations/0011_easy_slapstick.sql @@ -5,25 +5,10 @@ ALTER TABLE `aiService` RENAME TO `service`;--> statement-breakpoint ALTER TABLE `key` RENAME COLUMN `aiServiceId` TO `serviceId`;--> statement-breakpoint ALTER TABLE `model` RENAME COLUMN `aiAccountId` TO `keyId`;--> statement-breakpoint ALTER TABLE `service` RENAME COLUMN `aiSdkId` TO `sdkId`;--> statement-breakpoint -/* - SQLite does not support "Dropping foreign key" out of the box, we do not generate automatic migration for that, so it has to be done manually - Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php - https://www.sqlite.org/lang_altertable.html - - Due to that we don't generate migration automatically and it has to be done manually -*/--> statement-breakpoint DROP INDEX IF EXISTS `accountId_idx`;--> statement-breakpoint DROP INDEX IF EXISTS `accountId_unique`;--> statement-breakpoint CREATE INDEX `accountId_idx` ON `model` (`keyId`);--> statement-breakpoint CREATE UNIQUE INDEX `accountId_unique` ON `model` (`keyId`,`name`);--> statement-breakpoint -/* - SQLite does not support "Creating foreign key on existing column" out of the box, we do not generate automatic migration for that, so it has to be done manually - Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php - https://www.sqlite.org/lang_altertable.html - - Due to that we don't generate migration automatically and it has to be done manually -*/ ---> statement-breakpoint ALTER TABLE `model` RENAME TO `model_old`; --> statement-breakpoint CREATE TABLE `model` ( diff --git a/src/database/schema.ts b/src/database/schema.ts index 67f7293..f410449 100644 --- a/src/database/schema.ts +++ b/src/database/schema.ts @@ -151,7 +151,7 @@ export const chatRelations = relations(chatTable, ({ many }) => ({ })); export const modelRelations = relations(modelTable, ({ one }) => ({ - service: one(keyTable, { + key: one(keyTable, { fields: [modelTable.keyId], references: [keyTable.id], }), diff --git a/src/routes/(app)/$data.test.ts b/src/routes/(app)/$data.test.ts index 4daa310..a292e70 100644 --- a/src/routes/(app)/$data.test.ts +++ b/src/routes/(app)/$data.test.ts @@ -37,19 +37,26 @@ describe("(app)/$data", () => { await db.delete(schema.keyTable); // Insert dummy data - await db.insert(schema.keyTable).values([ - { - id: "service1", - name: "Test Service", - providerId: "provider1", - baseURL: "https://api.test.com", - apiKey: "test-api-key", - }, - ]); - + await db.insert(schema.sdkTable).values([{ id: "sdk1", name: "Test SDK", slug: "test-sdk" }]); + await db + .insert(schema.serviceTable) + .values([ + { id: "service1", name: "Test Service", sdkId: "sdk1", baseURL: "https://api.test.com" }, + ]); + await db + .insert(schema.keyTable) + .values([ + { + id: "key1", + name: "Test Key", + serviceId: "service1", + baseURL: "https://api.test.com", + apiKey: "test-api-key", + }, + ]); await db .insert(schema.modelTable) - .values([{ id: "model1", serviceId: "service1", name: "Test Model", visible: 1 }]); + .values([{ id: "model1", keyId: "key1", name: "Test Model", visible: 1 }]); await db.insert(schema.chatTable).values([ { id: "chat1", name: "Chat 1", prompt: "Prompt 1" }, diff --git a/src/routes/(app)/api/chat/+server.ts b/src/routes/(app)/api/chat/+server.ts index 756d540..2602550 100644 --- a/src/routes/(app)/api/chat/+server.ts +++ b/src/routes/(app)/api/chat/+server.ts @@ -6,22 +6,24 @@ import { createMistral } from "@ai-sdk/mistral"; import { createGoogleGenerativeAI } from "@ai-sdk/google"; export const POST = (async ({ request }) => { - const { messages, providerId, apiKey, baseURL, modelName } = (await request.json()) as { + let { messages, sdkId, apiKey, baseURL, modelName } = (await request.json()) as { messages: any[]; - providerId: string; + sdkId: string; baseURL: string; apiKey: string; modelName: string; }; - if (!providerId || !baseURL || !modelName) { + baseURL = baseURL ?? undefined; + + if (!sdkId || !modelName) { return new Response(`Malformed request`, { status: 400, }); } let provider; - switch (providerId) { + switch (sdkId) { case "openai": provider = createOpenAI({ apiKey, baseURL }); break; @@ -35,7 +37,7 @@ export const POST = (async ({ request }) => { provider = createGoogleGenerativeAI({ apiKey, baseURL }); break; default: - return new Response(`Unsupported provider ${providerId}`, { + return new Response(`Unsupported sdk ${sdkId}`, { status: 400, }); } diff --git a/src/routes/(app)/chat/[id]/$data.test.ts b/src/routes/(app)/chat/[id]/$data.test.ts index 39602ab..064d7c3 100644 --- a/src/routes/(app)/chat/[id]/$data.test.ts +++ b/src/routes/(app)/chat/[id]/$data.test.ts @@ -3,11 +3,11 @@ import { appendMessages, createRevision, getLatestRevision, - getModelService, + getModelKey, getRevision, interpolateDocuments, isTab, - loadServices, + getKeys, tabRouteId, updateChat, } from "./$data"; @@ -54,11 +54,28 @@ beforeEach(async () => { await runMigrations(true); // Insert test data - await db.insert(schema.keyTable).values([ + await db.insert(schema.sdkTable).values([ + { + id: "sdk1", + name: "Test SDK", + slug: "test-sdk", + }, + ]); + + await db.insert(schema.serviceTable).values([ { id: "service1", name: "Test Service", - providerId: "provider1", + sdkId: "sdk1", + baseURL: "https://api.test.com", + }, + ]); + + await db.insert(schema.keyTable).values([ + { + id: "key1", + name: "Test Key", + serviceId: "service1", baseURL: "https://api.test.com", apiKey: "test-api-key", }, @@ -66,7 +83,7 @@ beforeEach(async () => { await db .insert(schema.modelTable) - .values([{ id: "model1", serviceId: "service1", name: "Test Model", visible: 1 }]); + .values([{ id: "model1", keyId: "key1", name: "Test Model", visible: 1 }]); await db .insert(schema.chatTable) @@ -144,11 +161,11 @@ describe("interpolateDocuments", () => { describe("loadServices", () => { it("should load services with their models", async () => { - const services = await loadServices(); - expect(services).toHaveLength(1); - expect(services[0]).toMatchObject({ - id: "service1", - name: "Test Service", + const keys = await getKeys(); + expect(keys).toHaveLength(1); + expect(keys[0]).toMatchObject({ + id: "key1", + name: "Test Key", models: [{ id: "model1", name: "Test Model" }], }); }); @@ -186,11 +203,11 @@ describe("getLatestRevision", () => { describe("getModelService", () => { it("should get a model with its service", async () => { - const modelService = await getModelService("model1"); - expect(modelService).toMatchObject({ + const modelKey = await getModelKey("model1"); + expect(modelKey).toMatchObject({ id: "model1", name: "Test Model", - service: { id: "service1", name: "Test Service" }, + key: { id: "key1", name: "Test Key" }, }); }); }); @@ -210,7 +227,7 @@ describe("appendMessage", () => { it("should append a message to a revision", async () => { const message = { id: "new-message", - role: "user", + role: "user" as const, content: "New message content", attachments: [], }; diff --git a/src/routes/(app)/chat/[id]/$data.ts b/src/routes/(app)/chat/[id]/$data.ts index 151002d..25a1dd7 100644 --- a/src/routes/(app)/chat/[id]/$data.ts +++ b/src/routes/(app)/chat/[id]/$data.ts @@ -17,7 +17,7 @@ import { sql } from "drizzle-orm/sql"; import type { RouteId } from "$lib/route"; import type { MessageAttachment, ChatMessage } from "$lib/chat-service.svelte"; -export type ServicesView = Awaited>; +export type KeysView = Awaited>; export type RevisionView = NonNullable>>; @@ -31,10 +31,11 @@ export function tabRouteId(tab: Tab): RouteId { return tab === "chat" ? `/chat/[id]` : `/chat/[id]/${tab}`; } -export function loadServices() { +export function getKeys() { return useDb().query.keyTable.findMany({ with: { models: true, + service: true, }, }); } @@ -95,11 +96,28 @@ export function getLatestRevision(chatId: string) { }); } -export function getModelService(modelId: string) { +export function getModelKey(modelId: string) { return useDb().query.modelTable.findFirst({ where: eq(modelTable.id, modelId), with: { - service: true, + key: { + with: { + service: { + columns: { + id: true, + name: true, + }, + with: { + sdk: { + columns: { + id: true, + name: true, + }, + }, + }, + }, + }, + }, }, }); } diff --git a/src/routes/(app)/chat/[id]/+layout.svelte b/src/routes/(app)/chat/[id]/+layout.svelte index 0afa3a2..ec2aa37 100644 --- a/src/routes/(app)/chat/[id]/+layout.svelte +++ b/src/routes/(app)/chat/[id]/+layout.svelte @@ -10,7 +10,7 @@ import { toast } from "svelte-french-toast"; let { data } = $props(); - let services = $derived(data.services); + let keys = $derived(data.keys); let chat = $derived(data.chat); let revision = $derived(data.revision); @@ -52,7 +52,7 @@
- + diff --git a/src/routes/(app)/chat/[id]/+layout.ts b/src/routes/(app)/chat/[id]/+layout.ts index ea18247..d34c435 100644 --- a/src/routes/(app)/chat/[id]/+layout.ts +++ b/src/routes/(app)/chat/[id]/+layout.ts @@ -1,22 +1,7 @@ import { eq } from "drizzle-orm"; -import { - chatTable, - registerModel, - type Revision, - type Message, - revisionTable, - keyTable, - useDb, -} from "@/database"; +import { chatTable, keyTable, registerModel, revisionTable, useDb } from "@/database"; import { error } from "@sveltejs/kit"; -import { - createRevision, - getLatestRevision, - getRevision, - loadServices, - type RevisionView, - type Tab, -} from "./$data"; +import { getKeys, getRevision, type Tab } from "./$data"; import { match } from "$lib/route"; export async function load({ route, url, params, depends }) { @@ -44,8 +29,8 @@ export async function load({ route, url, params, depends }) { } registerModel(chatTable, chat, depends); - const services = await loadServices(); - registerModel(keyTable, services, depends); + const keys = await getKeys(); + registerModel(keyTable, keys, depends); const version = Number(url.searchParams.get("version")) || null; const revision = await getRevision(params.id, version); @@ -55,5 +40,5 @@ export async function load({ route, url, params, depends }) { registerModel(revisionTable, revision, depends); depends("view:messages"); depends("view:chat"); - return { chat, services, tab, revision, version }; + return { chat, keys, tab, revision, version }; } diff --git a/src/routes/(app)/chat/[id]/ChatPage.svelte b/src/routes/(app)/chat/[id]/ChatPage.svelte index 021147a..1332d12 100644 --- a/src/routes/(app)/chat/[id]/ChatPage.svelte +++ b/src/routes/(app)/chat/[id]/ChatPage.svelte @@ -6,7 +6,7 @@ import { appendMessages, createRevision, - getModelService, + getModelKey, type RevisionView, toChatMessage, } from "./$data"; @@ -69,7 +69,7 @@ toast.error("No model selected"); return false; } - const model = await getModelService(store.selected.modelId); + const model = await getModelKey(store.selected.modelId); if (!model) { toast.error("Selected model not found"); return false; @@ -90,10 +90,10 @@ chatService.submit({ options: { body: { - providerId: model.service.providerId, + sdkId: model.key.service.sdk.id, modelName: model.name, - baseURL: model.service.baseURL, - apiKey: model.service.apiKey, + baseURL: model.key.baseURL, + apiKey: model.key.apiKey, }, }, }); diff --git a/src/routes/(app)/chat/[id]/SelectModel.svelte b/src/routes/(app)/chat/[id]/SelectModel.svelte index 58f0dea..f8b832b 100644 --- a/src/routes/(app)/chat/[id]/SelectModel.svelte +++ b/src/routes/(app)/chat/[id]/SelectModel.svelte @@ -9,18 +9,17 @@ SelectValue, } from "@/components/ui/select"; import { store } from "@/lib/store.svelte"; - import { providersById } from "@/lib/providers"; - import type { ServicesView } from "./$data"; + import type { KeysView } from "./$data"; import { modelTable, useDb } from "@/database"; - import { asc, desc, eq } from "drizzle-orm"; + import { asc, eq } from "drizzle-orm"; - let { services }: { services: ServicesView } = $props(); + let { keys }: { keys: KeysView } = $props(); - let visibleModels = $derived(services.map((s) => s.models.filter((m) => m.visible)).flat()); + let visibleModels = $derived(keys.map((key) => key.models.filter((m) => m.visible)).flat()); let modelsById = $derived( - services - .map((s) => s.models) + keys + .map((key) => key.models) .flat() .reduce>((acc, model) => { acc[model.id] = model; @@ -86,11 +85,10 @@ - {#each services as service (service.id)} - {@const provider = providersById[service.providerId]} + {#each keys as key (key.id)} - {provider.name} {service.name ? `(${service.name})` : ""} - {#each service.models as model (model.id)} + {key.service.name} {key.name ? `(${key.name})` : ""} + {#each key.models as model (model.id)} {#if model.visible} {model.name} diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/$data.test.ts b/src/routes/(app)/chat/[id]/[...rest]/config/$data.test.ts deleted file mode 100644 index c702b9c..0000000 --- a/src/routes/(app)/chat/[id]/[...rest]/config/$data.test.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - loadServices, - addService, - deleteService, - updateService, - replaceModels, - toggleVisible, - toggleAllVisible, -} from "./$data"; -import Database from "better-sqlite3"; -import { type BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3"; -import * as schema from "@/database/schema"; -import { runMigrations } from "@/database/migrator"; -import { eq } from "drizzle-orm"; -import { invalidate } from "$app/navigation"; -import { nanoid } from "nanoid"; - -let sqlite: Database.Database; -let db: BetterSQLite3Database; - -beforeEach(async () => { - // Set up database - sqlite = new Database(":memory:"); - db = drizzle(sqlite, { schema }); - - // Set up mocks - vi.mock("$app/navigation", () => ({ - invalidate: vi.fn(), - })); - - vi.mock("nanoid", () => ({ - nanoid: vi.fn(() => "mockedNanoId"), - })); - - // Mock the useDb function and other imports - vi.mock("@/database/client", () => ({ - useDb: vi.fn(() => db), - })); - - await runMigrations(true); - - // Insert test data - await db.insert(schema.keyTable).values([ - { - id: "service1", - name: "Test Service", - aiServiceId: "aiService1", - baseURL: "https://api.test.com", - apiKey: "test-api-key", - }, - ]); - - await db.insert(schema.modelTable).values([ - { id: "model1", serviceId: "service1", name: "Test Model 1", visible: 1 }, - { id: "model2", serviceId: "service1", name: "Test Model 2", visible: 0 }, - ]); -}); - -afterEach(() => { - sqlite.close(); - vi.restoreAllMocks(); -}); - -describe("loadServices", () => { - it("should load all services with their models", async () => { - const services = await loadServices(); - expect(services).toHaveLength(1); - expect(services[0].id).toBe("service1"); - expect(services[0].models).toHaveLength(2); - }); -}); - -describe("addService", () => { - it("should add a new service", async () => { - const provider = { - id: "newProvider", - name: "New Provider", - defaultBaseURL: "https://api.newprovider.com", - }; - const newService = await addService(provider); - expect(newService.id).toBe("mockedNanoId"); - expect(newService.providerId).toBe("newProvider"); - expect(invalidate).toHaveBeenCalledWith("view:services"); - }); -}); - -describe("deleteService", () => { - it("should delete a service and its models", async () => { - const service = { - id: "service1", - name: "Test Service", - providerId: "provider1", - baseURL: "https://api.test.com", - apiKey: "test-api-key", - createdAt: new Date().toISOString(), - }; - await deleteService(service); - const remainingServices = await db.query.keyTable.findMany(); - const remainingModels = await db.query.modelTable.findMany(); - expect(remainingServices).toHaveLength(0); - expect(remainingModels).toHaveLength(0); - }); -}); - -describe("updateService", () => { - it("should update a service", async () => { - const updatedService = { - id: "service1", - name: "Updated Service", - providerId: "provider1", - baseURL: "https://updated.api.com", - apiKey: "new-api-key", - createdAt: new Date().toISOString(), - }; - await updateService(updatedService); - const service = await db.query.keyTable.findFirst({ - where: eq(schema.keyTable.id, "service1"), - }); - expect(service).toEqual({ ...updatedService, createdAt: expect.any(String) }); - }); -}); - -describe("replaceModels", () => { - it("should replace models for a service", async () => { - // Local mock for nanoid - let idCounter = 0; - vi.mocked(nanoid).mockImplementation(() => `uniqueId${idCounter++}`); - - const service = { - id: "service1", - name: "Updated Service", - providerId: "provider1", - baseURL: "https://updated.api.com", - apiKey: "new-api-key", - createdAt: new Date().toISOString(), - }; - const newModels = [{ name: "New Model 1" }, { name: "New Model 2" }, { name: "Test Model 1" }]; - await replaceModels(service, newModels); - - const models = await db.query.modelTable.findMany({ - where: eq(schema.modelTable.serviceId, "service1"), - }); - - expect(models).toHaveLength(3); - expect(models.map((m) => m.name)).toEqual( - expect.arrayContaining(["New Model 1", "New Model 2", "Test Model 1"]), - ); - expect(models.map((m) => m.id)).toEqual( - expect.arrayContaining(["uniqueId0", "uniqueId1", "model1"]), - ); - - // Reset the mock after the test - vi.mocked(nanoid).mockReset(); - }); - - it("should not create duplicate models", async () => { - // Local mock for nanoid - let idCounter = 0; - vi.mocked(nanoid).mockImplementation(() => `uniqueId${idCounter++}`); - - const service = { - id: "service1", - name: "Updated Service", - providerId: "provider1", - baseURL: "https://updated.api.com", - apiKey: "new-api-key", - createdAt: new Date().toISOString(), - }; - const newModels = [ - { name: "New Model 1" }, - { name: "New Model 2" }, - { name: "Test Model 1" }, - { name: "Test Model 1" }, // Duplicate - ]; - await replaceModels(service, newModels); - - const models = await db.query.modelTable.findMany({ - where: eq(schema.modelTable.serviceId, "service1"), - }); - - expect(models).toHaveLength(3); // Should still be 3, not 4 - expect(models.map((m) => m.name)).toEqual( - expect.arrayContaining(["New Model 1", "New Model 2", "Test Model 1"]), - ); - - // Reset the mock after the test - vi.mocked(nanoid).mockReset(); - }); -}); - -describe("toggleVisible", () => { - it("should toggle the visibility of a model", async () => { - const service = { - id: "service1", - name: "Updated Service", - providerId: "provider1", - baseURL: "https://updated.api.com", - apiKey: "new-api-key", - createdAt: new Date().toISOString(), - }; - const model = { - id: "model1", - visible: 1, - name: "Test Model 1", - serviceId: "service1", - createdAt: new Date().toISOString(), - }; - await toggleVisible(service, model); - const updatedModel = await db.query.modelTable.findFirst({ - where: eq(schema.modelTable.id, "model1"), - }); - expect(updatedModel?.visible).toBe(0); - }); -}); - -describe("toggleAllVisible", () => { - it("should toggle visibility for all models of a service", async () => { - const service = { - id: "service1", - name: "Updated Service", - providerId: "provider1", - baseURL: "https://updated.api.com", - apiKey: "new-api-key", - createdAt: new Date().toISOString(), - }; - await toggleAllVisible(service, 0); - const models = await db.query.modelTable.findMany({ - where: eq(schema.modelTable.serviceId, "service1"), - }); - expect(models.every((m) => m.visible === 0)).toBe(true); - }); -}); diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/+layout.svelte b/src/routes/(app)/chat/[id]/[...rest]/config/+layout.svelte deleted file mode 100644 index 124ff33..0000000 --- a/src/routes/(app)/chat/[id]/[...rest]/config/+layout.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - - { - if (!isOpen) { - goto( - route(`/chat/[id]`, { id: $page.params.id, $query: { version: data.revision.version } }), - ); - } - }} -> - - - - - diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/+layout.ts b/src/routes/(app)/chat/[id]/[...rest]/config/+layout.ts deleted file mode 100644 index e0ebe3a..0000000 --- a/src/routes/(app)/chat/[id]/[...rest]/config/+layout.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { loadServices } from "./$data"; -import { registerModel, keyTable } from "@/database"; - -export async function load({ depends, route }) { - const services = await loadServices(); - registerModel(keyTable, services, depends); - depends("view:services"); - return { - services, - }; -} diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/+page.svelte b/src/routes/(app)/chat/[id]/[...rest]/config/+page.svelte deleted file mode 100644 index 2540586..0000000 --- a/src/routes/(app)/chat/[id]/[...rest]/config/+page.svelte +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/[serviceId]/+page.svelte b/src/routes/(app)/chat/[id]/[...rest]/config/[serviceId]/+page.svelte deleted file mode 100644 index 165924b..0000000 --- a/src/routes/(app)/chat/[id]/[...rest]/config/[serviceId]/+page.svelte +++ /dev/null @@ -1,224 +0,0 @@ - - - - Update AI account - -
-
-
- - - -
-
- {#if service} -
- - { - if (selected) { - service.providerId = selected.value; - save(); - } - }} - > - - - - - {#each Providers as provider (provider.id)} - {provider.name} - {/each} - - -
-
- - -
-
- - -
- {#if showOptions} -
- -
-
- - -
- {:else} -
- -
- {/if} - - - {/if} - {#if error}{/if} - {#if service.models.length > 0} - -
-
- -
- - -
-
- -
- - - {#each service.models as model (model.id)} - toggleVisible(service, model)} - > - {model.name} - - - - - - - - {/each} - -
-
-
- - - - {/if} -
diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/[serviceId]/+page.ts b/src/routes/(app)/chat/[id]/[...rest]/config/[serviceId]/+page.ts deleted file mode 100644 index e643fa0..0000000 --- a/src/routes/(app)/chat/[id]/[...rest]/config/[serviceId]/+page.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { registerModel, keyTable, useDb } from "@/database"; -import { eq } from "drizzle-orm"; -import { error } from "@sveltejs/kit"; - -export async function load({ depends, params }) { - const service = await useDb().query.keyTable.findFirst({ - with: { - models: true, - }, - where: eq(keyTable.id, params.serviceId), - }); - if (!service) { - return error(404, "Service not found"); - } - registerModel(keyTable, service, depends); - depends("view:service"); - return { - service, - }; -} diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/create/+page.svelte b/src/routes/(app)/chat/[id]/[...rest]/config/create/+page.svelte deleted file mode 100644 index dd4503e..0000000 --- a/src/routes/(app)/chat/[id]/[...rest]/config/create/+page.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - - - Select AI provider - -
- - - {#each Providers as provider (provider.id)} - onSelect(provider)}> - {provider.name} - - {/each} - -
-
diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/select/+page.svelte b/src/routes/(app)/chat/[id]/[...rest]/config/select/+page.svelte deleted file mode 100644 index af2f33c..0000000 --- a/src/routes/(app)/chat/[id]/[...rest]/config/select/+page.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - - - Your AI accounts - -
- - - Provider - Key name - - - {#if data.services.length === 0} - - No accounts configured - - {/if} - {#each data.services as service (service.id)} - {@const provider = providersById[service.providerId]} - onSelect(service)}> - {provider.name} - {service.name || "-"} - - {/each} - -
- -
diff --git a/src/routes/(app)/chat/[id]/revise/RevisePage.svelte b/src/routes/(app)/chat/[id]/revise/RevisePage.svelte index bb9e009..cc38b8a 100644 --- a/src/routes/(app)/chat/[id]/revise/RevisePage.svelte +++ b/src/routes/(app)/chat/[id]/revise/RevisePage.svelte @@ -3,7 +3,7 @@ import { appendMessages, createRevision, - getModelService, + getModelKey, type RevisionView, toChatMessage, } from "../$data"; @@ -87,7 +87,7 @@ toast.error("No model selected"); return false; } - const model = await getModelService(store.selected.modelId); + const model = await getModelKey(store.selected.modelId); if (!model) { toast.error("Selected model not found"); return false; @@ -100,10 +100,10 @@ chatService.submit({ options: { body: { - providerId: model.service.providerId, + sdkId: model.key.service.sdk.id, modelName: model.name, - baseURL: model.service.baseURL, - apiKey: model.service.apiKey, + baseURL: model.key.baseURL, + apiKey: model.key.apiKey, }, }, }); diff --git a/src/routes/(app)/settings/$data.test.ts b/src/routes/(app)/settings/$data.test.ts deleted file mode 100644 index 89f3830..0000000 --- a/src/routes/(app)/settings/$data.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useDb } from "@/database"; - -export async function loadKeys() { - return useDb().query.keyTable.findMany({ with: { service: { with: { sdk: true } } } }); -} - -export async function loadServices() { - return useDb().query.serviceTable.findMany({ with: { sdk: true } }); -} diff --git a/src/routes/(app)/settings/+page.ts b/src/routes/(app)/settings/+page.ts index 83f8f40..34b8925 100644 --- a/src/routes/(app)/settings/+page.ts +++ b/src/routes/(app)/settings/+page.ts @@ -1,11 +1,6 @@ -import { keyTable, registerModel } from "@/database"; -import { loadKeys } from "./$data.test"; +import { redirect } from "@sveltejs/kit"; +import { route } from "$lib/route"; -export async function load({ depends, route }) { - const keys = await loadKeys(); - registerModel(keyTable, keys, depends); - depends("view:services"); - return { - keys, - }; +export async function load() { + return redirect(301, route("/settings/keys")); } diff --git a/src/routes/(app)/settings/Nav.svelte b/src/routes/(app)/settings/Nav.svelte index 25dc611..090bd1f 100644 --- a/src/routes/(app)/settings/Nav.svelte +++ b/src/routes/(app)/settings/Nav.svelte @@ -8,10 +8,16 @@ class="grid gap-4 text-sm text-muted-foreground" data-x-chunk-container="chunk-container after:right-0" > - + API Keys - + Title Generation diff --git a/src/routes/(app)/settings/keys/$data.ts b/src/routes/(app)/settings/keys/$data.ts index 46252c1..3a1754a 100644 --- a/src/routes/(app)/settings/keys/$data.ts +++ b/src/routes/(app)/settings/keys/$data.ts @@ -12,6 +12,14 @@ export const formSchema = z.object({ baseURL: z.string().nullable(), }); +export async function loadKeys() { + return useDb().query.keyTable.findMany({ with: { service: { with: { sdk: true } } } }); +} + +export async function loadServices() { + return useDb().query.serviceTable.findMany({ with: { sdk: true } }); +} + export async function refreshModels( tx: SQLiteTransaction, key: Key, diff --git a/src/routes/(app)/settings/+page.svelte b/src/routes/(app)/settings/keys/+page.svelte similarity index 100% rename from src/routes/(app)/settings/+page.svelte rename to src/routes/(app)/settings/keys/+page.svelte diff --git a/src/routes/(app)/settings/keys/+page.ts b/src/routes/(app)/settings/keys/+page.ts new file mode 100644 index 0000000..435ba64 --- /dev/null +++ b/src/routes/(app)/settings/keys/+page.ts @@ -0,0 +1,11 @@ +import { keyTable, registerModel } from "@/database"; +import { loadKeys } from "./$data"; + +export async function load({ depends, route }) { + const keys = await loadKeys(); + registerModel(keyTable, keys, depends); + depends("view:services"); + return { + keys, + }; +} diff --git a/src/routes/(app)/settings/keys/[id]/+page.svelte b/src/routes/(app)/settings/keys/[id]/+page.svelte index 0b37dec..81a5c01 100644 --- a/src/routes/(app)/settings/keys/[id]/+page.svelte +++ b/src/routes/(app)/settings/keys/[id]/+page.svelte @@ -26,9 +26,8 @@ FormFieldErrors, FormLabel, } from "@/components/ui/form"; - import { invalidateModel, keyTable, modelTable, serviceTable, useDb } from "@/database"; - import { and, eq, notInArray } from "drizzle-orm"; - import { nanoid } from "nanoid"; + import { invalidateModel, keyTable, serviceTable, useDb } from "@/database"; + import { eq } from "drizzle-orm"; import { goto, invalidate } from "$app/navigation"; import { EyeIcon, LoaderCircle, RefreshCwIcon, TriangleAlertIcon } from "lucide-svelte"; import { cn } from "$lib/cn"; @@ -37,7 +36,6 @@ import { route } from "$lib/route"; import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table"; import { Toggle } from "@/components/ui/toggle"; - import { page } from "$app/stores"; let { data } = $props(); @@ -45,7 +43,7 @@ await useDb().delete(keyTable).where(eq(keyTable.id, data.key.id)); await invalidateModel(keyTable, data.key); toast.success("Key deleted"); - await goto(route(`/settings`)); + await goto(route(`/settings/keys`)); } const formHandle = superForm(data.form, { diff --git a/src/routes/(app)/settings/keys/[id]/+page.ts b/src/routes/(app)/settings/keys/[id]/+page.ts index a68dd23..1abd9bf 100644 --- a/src/routes/(app)/settings/keys/[id]/+page.ts +++ b/src/routes/(app)/settings/keys/[id]/+page.ts @@ -2,9 +2,8 @@ import { keyTable, registerModel, useDb } from "@/database"; import { eq } from "drizzle-orm"; import { superValidate } from "sveltekit-superforms"; import { zod } from "sveltekit-superforms/adapters"; -import { formSchema } from "../$data"; +import { formSchema, loadServices } from "../$data"; import { error } from "@sveltejs/kit"; -import { loadServices } from "../../$data.test"; export async function load({ params, depends }) { const key = await useDb().query.keyTable.findFirst({ diff --git a/src/routes/(app)/settings/keys/add/+page.ts b/src/routes/(app)/settings/keys/add/+page.ts index 6b1518e..03d29ee 100644 --- a/src/routes/(app)/settings/keys/add/+page.ts +++ b/src/routes/(app)/settings/keys/add/+page.ts @@ -1,8 +1,7 @@ -import { loadServices } from "../../$data.test"; -import { serviceTable, registerModel } from "@/database"; +import { formSchema, loadServices } from "../$data"; +import { registerModel, serviceTable } from "@/database"; import { superValidate } from "sveltekit-superforms"; import { zod } from "sveltekit-superforms/adapters"; -import { formSchema } from "../$data"; export async function load({ depends, route }) { const form = await superValidate(zod(formSchema)); diff --git a/src/routes/(app)/chat/[id]/[...rest]/config/SelectAccount.svelte b/src/routes/(app)/settings/titles/+page.svelte similarity index 100% rename from src/routes/(app)/chat/[id]/[...rest]/config/SelectAccount.svelte rename to src/routes/(app)/settings/titles/+page.svelte