-
+
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 @@
-
-
-
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