From 1a6cd573cd2cd95651c355c04f5de40d67821428 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Thu, 2 Jul 2026 13:18:00 +0800 Subject: [PATCH] feat(llm): add CRUD operations for models and providers, including user authorization - Implemented GET, POST, PUT, and DELETE endpoints for managing LLM models and providers. - Added user authorization checks to ensure only authorized users can access or modify resources. - Enhanced provider and model retrieval functions to include user-specific data. - Updated database service functions to handle user IDs for better data isolation. - Introduced error handling for invalid inputs and unauthorized actions. --- app/components/TopNav.vue | 3 +- app/pages/admin.vue | 1 - app/pages/admin/llm-config/index.vue | 1446 ----------------- app/pages/settings.vue | 241 +++ app/pages/settings/llm-config/index.vue | 1446 +++++++++++++++++ packages/drizzle-pkg/db.sqlite | Bin 282624 -> 282624 bytes packages/drizzle-pkg/lib/schema/llm.ts | 13 +- .../migrations/0013_flaky_marvel_boy.sql | 24 + .../drizzle-pkg/migrations/meta/0013_snapshot.json | 1674 ++++++++++++++++++++ packages/drizzle-pkg/migrations/meta/_journal.json | 7 + server/api/admin/llm/models/[providerId].get.ts | 14 - server/api/admin/llm/models/[providerId].post.ts | 29 - server/api/admin/llm/models/detail/[id].delete.ts | 14 - server/api/admin/llm/models/detail/[id].put.ts | 24 - server/api/admin/llm/providers/[id].delete.ts | 14 - server/api/admin/llm/providers/[id].get.ts | 17 - server/api/admin/llm/providers/[id].models.get.ts | 50 - server/api/admin/llm/providers/[id].put.ts | 25 - server/api/admin/llm/providers/index.get.ts | 15 - server/api/admin/llm/providers/index.post.ts | 24 - server/api/llm/models/[providerId].get.ts | 14 + server/api/llm/models/[providerId].post.ts | 32 + server/api/llm/models/detail/[id].delete.ts | 14 + server/api/llm/models/detail/[id].put.ts | 24 + server/api/llm/providers/[id].delete.ts | 14 + server/api/llm/providers/[id].get.ts | 17 + server/api/llm/providers/[id].models.get.ts | 50 + server/api/llm/providers/[id].put.ts | 25 + server/api/llm/providers/index.get.ts | 15 + server/api/llm/providers/index.post.ts | 24 + server/service/llm/index.ts | 53 +- 31 files changed, 3672 insertions(+), 1691 deletions(-) delete mode 100644 app/pages/admin/llm-config/index.vue create mode 100644 app/pages/settings.vue create mode 100644 app/pages/settings/llm-config/index.vue create mode 100644 packages/drizzle-pkg/migrations/0013_flaky_marvel_boy.sql create mode 100644 packages/drizzle-pkg/migrations/meta/0013_snapshot.json delete mode 100644 server/api/admin/llm/models/[providerId].get.ts delete mode 100644 server/api/admin/llm/models/[providerId].post.ts delete mode 100644 server/api/admin/llm/models/detail/[id].delete.ts delete mode 100644 server/api/admin/llm/models/detail/[id].put.ts delete mode 100644 server/api/admin/llm/providers/[id].delete.ts delete mode 100644 server/api/admin/llm/providers/[id].get.ts delete mode 100644 server/api/admin/llm/providers/[id].models.get.ts delete mode 100644 server/api/admin/llm/providers/[id].put.ts delete mode 100644 server/api/admin/llm/providers/index.get.ts delete mode 100644 server/api/admin/llm/providers/index.post.ts create mode 100644 server/api/llm/models/[providerId].get.ts create mode 100644 server/api/llm/models/[providerId].post.ts create mode 100644 server/api/llm/models/detail/[id].delete.ts create mode 100644 server/api/llm/models/detail/[id].put.ts create mode 100644 server/api/llm/providers/[id].delete.ts create mode 100644 server/api/llm/providers/[id].get.ts create mode 100644 server/api/llm/providers/[id].models.get.ts create mode 100644 server/api/llm/providers/[id].put.ts create mode 100644 server/api/llm/providers/index.get.ts create mode 100644 server/api/llm/providers/index.post.ts diff --git a/app/components/TopNav.vue b/app/components/TopNav.vue index 3cd6e29..0929731 100644 --- a/app/components/TopNav.vue +++ b/app/components/TopNav.vue @@ -79,7 +79,8 @@ watch(() => route.path, () => { 注册 diff --git a/app/pages/admin.vue b/app/pages/admin.vue index 2f54a53..ba41561 100644 --- a/app/pages/admin.vue +++ b/app/pages/admin.vue @@ -25,7 +25,6 @@ const adminNav: NavItem[] = [ children: [ { label: '全局配置', to: '/admin/global-config' }, { label: '用户管理', to: '/admin/users' }, - { label: '模型配置', to: '/admin/llm-config' }, ] }, ] diff --git a/app/pages/admin/llm-config/index.vue b/app/pages/admin/llm-config/index.vue deleted file mode 100644 index 81830ab..0000000 --- a/app/pages/admin/llm-config/index.vue +++ /dev/null @@ -1,1446 +0,0 @@ - - - - - diff --git a/app/pages/settings.vue b/app/pages/settings.vue new file mode 100644 index 0000000..cfc84a2 --- /dev/null +++ b/app/pages/settings.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/app/pages/settings/llm-config/index.vue b/app/pages/settings/llm-config/index.vue new file mode 100644 index 0000000..ce48700 --- /dev/null +++ b/app/pages/settings/llm-config/index.vue @@ -0,0 +1,1446 @@ + + + + + diff --git a/packages/drizzle-pkg/db.sqlite b/packages/drizzle-pkg/db.sqlite index bd7412c6f550507073bfe1fd96c5d1b9196490bc..c5eb7033d309efe3521807d3445e568c43a02546 100644 GIT binary patch delta 1410 zcmdT@TW=dx5I%d39cR7vPE0PwDTZ}`TqK8NecLscv~KFQQbTByl!pk(UVGP(sofZ_ z6O|W|D5z9CSgkCyJQNWS>LVps5Q!oP51M!77 z&Sk#&X3or_dRrG5et)u`nnF^z(;Tu zE<+Jsf=PI`EDDc<+-@bJ!%BLXOeP!4<)>%Us+QL=+(ZtxuF6iUT1JPPJWD(cn_UH7 zELL7AR+yDy1?z%~&m-2piNFhU{GV`{zX9Ju!oJCm&>&yho2HtZ+zUO%xgG328V<=p zNfP~1KNWva7DM5Fv0n-JwS+I>OZk(MoKhu4l9Hk%g(P_}tjVJI*z>t5y^v3zoub7G z<{vdic5t@|#?f;tvpe|WTX+?f1z`zFhg$CEhcJhjfq`}wA+s49H?ET2Hcw>1NvU+T z(jqaijbQr&2lFeGDtFB`jCZv)1U0JNg4Slz&J?tBg+1U7f;%*qmx0eQHsp3c=F_=Cdiw0YU_6=El!B(lltSqgZo~9(%eb8%eP%)!Jv}mF?L&xEjkPM#>A;dX zBT^&B@XTT3dX)?s*&6W}t{QoS%||RwsfSr;H7ZroabG=+edUT6-;Lo818Rgd)j{J@ zjW}EDr&wBz_bpftS)38d4>$PE22js>pOc}|C%0V&S0i>jqZ_Z*$b+4Kd+;`D6z^LM z)1LM8Fy}?%_(}Xw*Zzid?Q_@l|LDca``cu~Y}%WjpTNcuH>+x9!Y~~EFo%DK4$ewk ajFK#*XSK%opcaInO6TflSg8(}tioS!!J^9m delta 823 zcma)2OK1~O6n!%Th2pea)X+d?t6E;X&O38fh7Ck=?3M#-qf{L|DficJv| zT!cs|<05DUQE<^(2MvgebYT&ZWT6|uABv!26l^tFG{HBClvEU4ocrFr@7{CnJIOgA zIVYsI3VDQ(i-0A-1kTG{yQ%!5?*_DTtO(35NYrAl|IU?X@%#3y+WbrC9uY zs&Fw?pt7j~9TzR&K@9Ik&;?U`1u}ddUcm`I#=8ky4==l`^bYMYz1BL-$)75;H9K3S zqt`V{2$smgS>WuH5ivGf3xiNolbm0`9h=3X-b-Cmqk$|qM&vRteE( z=rCd{eCA>>6|HLj$ z*se3ljuIYVRFg*Hfy62KZ3&xClZ&tO++LljubrCSJ{s~%6RNxGcGg-)5~0y(LfY`) z=s)6e47)_{(BNUOOB@_>x{ivz4K0ncjcR-Ttrj{Sl>VmH_aABvv#Od&s}_8n+1^9+ uq-N%z;p~k2mXN($!Nas>-PA3rseWQ=oS6ED;cVmEeILC7*%h0H6Mq5mDgqM# diff --git a/packages/drizzle-pkg/lib/schema/llm.ts b/packages/drizzle-pkg/lib/schema/llm.ts index fcc33d9..a741128 100644 --- a/packages/drizzle-pkg/lib/schema/llm.ts +++ b/packages/drizzle-pkg/lib/schema/llm.ts @@ -1,4 +1,5 @@ -import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; +import { sqliteTable, text, integer, index, uniqueIndex } from "drizzle-orm/sqlite-core"; +import { users } from "./auth"; export const LlmProviderStatuses = ["active", "disabled"] as const; export type LlmProviderStatus = (typeof LlmProviderStatuses)[number]; @@ -13,8 +14,11 @@ export const llmProviders = sqliteTable( "llm_providers", { id: integer("id").primaryKey({ autoIncrement: true }), - name: text("name", { length: 100 }).notNull().unique(), - slug: text("slug", { length: 100 }).notNull().unique(), + userId: integer("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + name: text("name", { length: 100 }).notNull(), + slug: text("slug", { length: 100 }).notNull(), baseUrl: text("base_url", { length: 500 }), parseMode: text("parse_mode", { enum: LlmParseModes }).notNull().default("openai"), apiKey: text("api_key"), @@ -27,7 +31,8 @@ export const llmProviders = sqliteTable( .notNull(), }, (table) => [ - index("idx_llm_provider_slug").on(table.slug), + uniqueIndex("idx_llm_provider_user_slug").on(table.userId, table.slug), + index("idx_llm_provider_user").on(table.userId), index("idx_llm_provider_status").on(table.status), ], ); diff --git a/packages/drizzle-pkg/migrations/0013_flaky_marvel_boy.sql b/packages/drizzle-pkg/migrations/0013_flaky_marvel_boy.sql new file mode 100644 index 0000000..12a6437 --- /dev/null +++ b/packages/drizzle-pkg/migrations/0013_flaky_marvel_boy.sql @@ -0,0 +1,24 @@ +DROP INDEX `llm_providers_name_unique`;--> statement-breakpoint +DROP INDEX `llm_providers_slug_unique`;--> statement-breakpoint +DROP INDEX `idx_llm_provider_slug`;--> statement-breakpoint +ALTER TABLE `llm_providers` ADD `user_id` integer REFERENCES users(id);--> statement-breakpoint +UPDATE `llm_providers` SET `user_id` = (SELECT MIN(id) FROM `users`) WHERE `user_id` IS NULL;--> statement-breakpoint +CREATE TABLE `_llm_providers_tmp` ( + `id` integer PRIMARY KEY AUTOINCREMENT, + `user_id` integer NOT NULL REFERENCES users(id), + `name` text(100) NOT NULL, + `slug` text(100) NOT NULL, + `base_url` text(500), + `parse_mode` text DEFAULT 'openai' NOT NULL, + `api_key` text, + `status` text DEFAULT 'active' NOT NULL, + `description` text, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL +);--> statement-breakpoint +INSERT INTO `_llm_providers_tmp` SELECT `id`, `user_id`, `name`, `slug`, `base_url`, `parse_mode`, `api_key`, `status`, `description`, `created_at`, `updated_at` FROM `llm_providers`;--> statement-breakpoint +DROP TABLE `llm_providers`;--> statement-breakpoint +ALTER TABLE `_llm_providers_tmp` RENAME TO `llm_providers`;--> statement-breakpoint +CREATE UNIQUE INDEX `idx_llm_provider_user_slug` ON `llm_providers` (`user_id`,`slug`);--> statement-breakpoint +CREATE INDEX `idx_llm_provider_user` ON `llm_providers` (`user_id`);--> statement-breakpoint +CREATE INDEX `idx_llm_provider_status` ON `llm_providers` (`status`); diff --git a/packages/drizzle-pkg/migrations/meta/0013_snapshot.json b/packages/drizzle-pkg/migrations/meta/0013_snapshot.json new file mode 100644 index 0000000..e6448d7 --- /dev/null +++ b/packages/drizzle-pkg/migrations/meta/0013_snapshot.json @@ -0,0 +1,1674 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f4ec8869-c90b-426e-972a-2e1dc8d4e79f", + "prevId": "20c54319-abd6-41f9-956b-39d5b6d9e222", + "tables": { + "oauth_accounts": { + "name": "oauth_accounts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "nickname": { + "name": "nickname", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "app_configs": { + "name": "app_configs", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value_type": { + "name": "value_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_configs": { + "name": "user_configs", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value_type": { + "name": "value_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "user_configs_user_id_idx": { + "name": "user_configs_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_configs_user_id_users_id_fk": { + "name": "user_configs_user_id_users_id_fk", + "tableFrom": "user_configs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_configs_user_id_key_pk": { + "columns": [ + "user_id", + "key" + ], + "name": "user_configs_user_id_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "article_cards": { + "name": "article_cards", + "columns": { + "article_id": { + "name": "article_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_article_card_article": { + "name": "idx_article_card_article", + "columns": [ + "article_id" + ], + "isUnique": false + }, + "idx_article_card_card": { + "name": "idx_article_card_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "article_cards_article_id_articles_id_fk": { + "name": "article_cards_article_id_articles_id_fk", + "tableFrom": "article_cards", + "tableTo": "articles", + "columnsFrom": [ + "article_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "article_cards_card_id_cards_id_fk": { + "name": "article_cards_card_id_cards_id_fk", + "tableFrom": "article_cards", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "article_card_pk": { + "columns": [ + "article_id", + "card_id" + ], + "name": "article_card_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "articles": { + "name": "articles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "title": { + "name": "title", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover": { + "name": "cover", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_article_status": { + "name": "idx_article_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_article_created": { + "name": "idx_article_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "card_images": { + "name": "card_images", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_card_image_card": { + "name": "idx_card_image_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "card_images_card_id_cards_id_fk": { + "name": "card_images_card_id_cards_id_fk", + "tableFrom": "card_images", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "card_tags": { + "name": "card_tags", + "columns": { + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_card_tag_card": { + "name": "idx_card_tag_card", + "columns": [ + "card_id" + ], + "isUnique": false + }, + "idx_card_tag_tag": { + "name": "idx_card_tag_tag", + "columns": [ + "tag_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "card_tags_card_id_cards_id_fk": { + "name": "card_tags_card_id_cards_id_fk", + "tableFrom": "card_tags", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_tags_tag_id_tags_id_fk": { + "name": "card_tags_tag_id_tags_id_fk", + "tableFrom": "card_tags", + "tableTo": "tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "card_tag_pk": { + "columns": [ + "card_id", + "tag_id" + ], + "name": "card_tag_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cards": { + "name": "cards", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "aspect_ratio": { + "name": "aspect_ratio", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_card_category": { + "name": "idx_card_category", + "columns": [ + "category_id" + ], + "isUnique": false + }, + "idx_card_type": { + "name": "idx_card_type", + "columns": [ + "type" + ], + "isUnique": false + }, + "idx_card_created": { + "name": "idx_card_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "cards_category_id_categories_id_fk": { + "name": "cards_category_id_categories_id_fk", + "tableFrom": "cards", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "categories": { + "name": "categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_category_slug": { + "name": "idx_category_slug", + "columns": [ + "slug" + ], + "isUnique": true + }, + "idx_category_parent": { + "name": "idx_category_parent", + "columns": [ + "parent_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "chat_messages": { + "name": "chat_messages", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "nickname": { + "name": "nickname", + "type": "text(20)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_chat_msg_created": { + "name": "idx_chat_msg_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "favorites": { + "name": "favorites", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_favorite_user": { + "name": "idx_favorite_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_favorite_card": { + "name": "idx_favorite_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "favorites_user_id_users_id_fk": { + "name": "favorites_user_id_users_id_fk", + "tableFrom": "favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "favorites_card_id_cards_id_fk": { + "name": "favorites_card_id_cards_id_fk", + "tableFrom": "favorites", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "favorite_pk": { + "columns": [ + "user_id", + "card_id" + ], + "name": "favorite_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ideas": { + "name": "ideas", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "author": { + "name": "author", + "type": "text(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_idea_created": { + "name": "idx_idea_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text(1000)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_project_created": { + "name": "idx_project_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tag_slug": { + "name": "idx_tag_slug", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tools": { + "name": "tools", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "llm_models": { + "name": "llm_models", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(200)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text(200)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_llm_model_provider": { + "name": "idx_llm_model_provider", + "columns": [ + "provider_id" + ], + "isUnique": false + }, + "idx_llm_model_type": { + "name": "idx_llm_model_type", + "columns": [ + "type" + ], + "isUnique": false + } + }, + "foreignKeys": { + "llm_models_provider_id_llm_providers_id_fk": { + "name": "llm_models_provider_id_llm_providers_id_fk", + "tableFrom": "llm_models", + "tableTo": "llm_providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "llm_providers": { + "name": "llm_providers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_url": { + "name": "base_url", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parse_mode": { + "name": "parse_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'openai'" + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_llm_provider_user_slug": { + "name": "idx_llm_provider_user_slug", + "columns": [ + "user_id", + "slug" + ], + "isUnique": true + }, + "idx_llm_provider_user": { + "name": "idx_llm_provider_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_llm_provider_status": { + "name": "idx_llm_provider_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "llm_providers_user_id_users_id_fk": { + "name": "llm_providers_user_id_users_id_fk", + "tableFrom": "llm_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "scheduled_tasks": { + "name": "scheduled_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "function_name": { + "name": "function_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "function_payload": { + "name": "function_payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_method": { + "name": "http_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_url": { + "name": "http_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_headers": { + "name": "http_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_body": { + "name": "http_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "catch_up": { + "name": "catch_up", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "max_retries": { + "name": "max_retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "retry_delay_seconds": { + "name": "retry_delay_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 60 + }, + "timeout_seconds": { + "name": "timeout_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_execution_logs": { + "name": "task_execution_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "task_execution_logs_task_id_scheduled_tasks_id_fk": { + "name": "task_execution_logs_task_id_scheduled_tasks_id_fk", + "tableFrom": "task_execution_logs", + "tableTo": "scheduled_tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/drizzle-pkg/migrations/meta/_journal.json b/packages/drizzle-pkg/migrations/meta/_journal.json index 5778112..0d0ec79 100644 --- a/packages/drizzle-pkg/migrations/meta/_journal.json +++ b/packages/drizzle-pkg/migrations/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1782963564430, "tag": "0012_funny_santa_claus", "breakpoints": true + }, + { + "idx": 13, + "version": "6", + "when": 1782969066349, + "tag": "0013_flaky_marvel_boy", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/api/admin/llm/models/[providerId].get.ts b/server/api/admin/llm/models/[providerId].get.ts deleted file mode 100644 index b9cb88a..0000000 --- a/server/api/admin/llm/models/[providerId].get.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { listModelsByProvider } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const providerId = Number(getRouterParam(event, "providerId")); - if (!providerId) { - return R.throwError(400, "无效的供应商ID", null); - } - - const result = await listModelsByProvider(providerId); - return R.success(result); -}); diff --git a/server/api/admin/llm/models/[providerId].post.ts b/server/api/admin/llm/models/[providerId].post.ts deleted file mode 100644 index b0e1633..0000000 --- a/server/api/admin/llm/models/[providerId].post.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { createModel } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const providerId = Number(getRouterParam(event, "providerId")); - if (!providerId) { - return R.throwError(400, "无效的供应商ID", null); - } - - const body = await readBody(event); - const { name, modelId, type, enabled, description, maxTokens } = body; - - if (!name || !modelId) { - return R.throwError(422, "模型名称和模型ID不能为空", null); - } - - const result = await createModel({ - providerId, - name, - modelId, - type: type || "text", - enabled: enabled ?? 1, - description, - maxTokens, - }); - return R.success(result); -}); diff --git a/server/api/admin/llm/models/detail/[id].delete.ts b/server/api/admin/llm/models/detail/[id].delete.ts deleted file mode 100644 index 6789d9a..0000000 --- a/server/api/admin/llm/models/detail/[id].delete.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { deleteModel } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const id = Number(getRouterParam(event, "id")); - if (!id) { - return R.throwError(400, "无效的模型ID", null); - } - - await deleteModel(id); - return R.success(null); -}); diff --git a/server/api/admin/llm/models/detail/[id].put.ts b/server/api/admin/llm/models/detail/[id].put.ts deleted file mode 100644 index 097fe08..0000000 --- a/server/api/admin/llm/models/detail/[id].put.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { updateModel } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const id = Number(getRouterParam(event, "id")); - if (!id) { - return R.throwError(400, "无效的模型ID", null); - } - - const body = await readBody(event); - const { name, modelId, type, enabled, description, maxTokens } = body; - - await updateModel(id, { - name, - modelId, - type, - enabled, - description, - maxTokens, - }); - return R.success(null); -}); diff --git a/server/api/admin/llm/providers/[id].delete.ts b/server/api/admin/llm/providers/[id].delete.ts deleted file mode 100644 index f75adb3..0000000 --- a/server/api/admin/llm/providers/[id].delete.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { deleteProvider } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const id = Number(getRouterParam(event, "id")); - if (!id) { - return R.throwError(400, "无效的供应商ID", null); - } - - await deleteProvider(id); - return R.success(null); -}); diff --git a/server/api/admin/llm/providers/[id].get.ts b/server/api/admin/llm/providers/[id].get.ts deleted file mode 100644 index a8d03ba..0000000 --- a/server/api/admin/llm/providers/[id].get.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { getProviderWithModels } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const id = Number(getRouterParam(event, "id")); - if (!id) { - return R.throwError(400, "无效的供应商ID", null); - } - - const result = await getProviderWithModels(id); - if (!result) { - return R.throwError(404, "供应商不存在", null); - } - return R.success(result); -}); diff --git a/server/api/admin/llm/providers/[id].models.get.ts b/server/api/admin/llm/providers/[id].models.get.ts deleted file mode 100644 index a6211f8..0000000 --- a/server/api/admin/llm/providers/[id].models.get.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { getProviderById } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const id = Number(getRouterParam(event, "id")); - if (!id) { - return R.throwError(400, "无效的供应商 ID", null); - } - - const provider = await getProviderById(id); - if (!provider) { - return R.throwError(404, "供应商不存在", null); - } - - const baseUrl = provider.baseUrl?.replace(/\/+$/, "") || ""; - if (!baseUrl) { - return R.success([]); - } - - const headers: Record = { - "Content-Type": "application/json", - }; - - if (provider.parseMode === "anthropic") { - if (provider.apiKey) { - headers["x-api-key"] = provider.apiKey; - } - headers["anthropic-version"] = "2023-06-01"; - } else { - if (provider.apiKey) { - headers["Authorization"] = `Bearer ${provider.apiKey}`; - } - } - - try { - const res = await $fetch<{ data?: { id: string; name?: string }[] }>( - `${baseUrl}/models`, - { headers, timeout: 8000 }, - ); - const models = (res?.data ?? []).map((m) => ({ - id: m.id, - name: m.name || m.id, - })); - return R.success(models); - } catch { - return R.success([]); - } -}); diff --git a/server/api/admin/llm/providers/[id].put.ts b/server/api/admin/llm/providers/[id].put.ts deleted file mode 100644 index ec94f99..0000000 --- a/server/api/admin/llm/providers/[id].put.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { updateProvider } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const id = Number(getRouterParam(event, "id")); - if (!id) { - return R.throwError(400, "无效的供应商ID", null); - } - - const body = await readBody(event); - const { name, slug, baseUrl, parseMode, apiKey, status, description } = body; - - await updateProvider(id, { - name, - slug, - baseUrl, - parseMode, - apiKey, - status, - description, - }); - return R.success(null); -}); diff --git a/server/api/admin/llm/providers/index.get.ts b/server/api/admin/llm/providers/index.get.ts deleted file mode 100644 index 93b2044..0000000 --- a/server/api/admin/llm/providers/index.get.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { listProviders } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const query = getQuery(event); - const page = Number(query.page) || 1; - const pageSize = Number(query.pageSize) || 20; - const search = query.search as string | undefined; - const status = query.status as string | undefined; - - const result = await listProviders(page, pageSize, search, status as any); - return R.success(result); -}); diff --git a/server/api/admin/llm/providers/index.post.ts b/server/api/admin/llm/providers/index.post.ts deleted file mode 100644 index 0938a69..0000000 --- a/server/api/admin/llm/providers/index.post.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { requireAdmin } from "#server/utils/admin-guard"; -import { createProvider } from "#server/service/llm"; - -export default defineWrappedResponseHandler(async (event) => { - await requireAdmin(event); - - const body = await readBody(event); - const { name, slug, baseUrl, parseMode, apiKey, status, description } = body; - - if (!name || !slug) { - return R.throwError(422, "供应商名称和标识不能为空", null); - } - - const result = await createProvider({ - name, - slug, - baseUrl, - parseMode: parseMode || "openai", - apiKey, - status: status || "active", - description, - }); - return R.success(result); -}); diff --git a/server/api/llm/models/[providerId].get.ts b/server/api/llm/models/[providerId].get.ts new file mode 100644 index 0000000..986b649 --- /dev/null +++ b/server/api/llm/models/[providerId].get.ts @@ -0,0 +1,14 @@ +import { requireUser } from "#server/utils/context"; +import { listModelsByProvider } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const providerId = Number(getRouterParam(event, "providerId")); + if (!providerId) { + return R.throwError(400, "无效的供应商ID", null); + } + + const result = await listModelsByProvider(providerId, user.id); + return R.success(result); +}); diff --git a/server/api/llm/models/[providerId].post.ts b/server/api/llm/models/[providerId].post.ts new file mode 100644 index 0000000..73d859f --- /dev/null +++ b/server/api/llm/models/[providerId].post.ts @@ -0,0 +1,32 @@ +import { requireUser } from "#server/utils/context"; +import { createModel } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const providerId = Number(getRouterParam(event, "providerId")); + if (!providerId) { + return R.throwError(400, "无效的供应商ID", null); + } + + const body = await readBody(event); + const { name, modelId, type, enabled, description, maxTokens } = body; + + if (!name || !modelId) { + return R.throwError(422, "模型名称和模型ID不能为空", null); + } + + const result = await createModel(user.id, { + providerId, + name, + modelId, + type: type || "text", + enabled: enabled ?? 1, + description, + maxTokens, + }); + if (!result) { + return R.throwError(403, "无权操作该供应商", null); + } + return R.success(result); +}); diff --git a/server/api/llm/models/detail/[id].delete.ts b/server/api/llm/models/detail/[id].delete.ts new file mode 100644 index 0000000..8632e05 --- /dev/null +++ b/server/api/llm/models/detail/[id].delete.ts @@ -0,0 +1,14 @@ +import { requireUser } from "#server/utils/context"; +import { deleteModel } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const id = Number(getRouterParam(event, "id")); + if (!id) { + return R.throwError(400, "无效的模型ID", null); + } + + await deleteModel(id, user.id); + return R.success(null); +}); diff --git a/server/api/llm/models/detail/[id].put.ts b/server/api/llm/models/detail/[id].put.ts new file mode 100644 index 0000000..610f7c1 --- /dev/null +++ b/server/api/llm/models/detail/[id].put.ts @@ -0,0 +1,24 @@ +import { requireUser } from "#server/utils/context"; +import { updateModel } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const id = Number(getRouterParam(event, "id")); + if (!id) { + return R.throwError(400, "无效的模型ID", null); + } + + const body = await readBody(event); + const { name, modelId, type, enabled, description, maxTokens } = body; + + await updateModel(id, user.id, { + name, + modelId, + type, + enabled, + description, + maxTokens, + }); + return R.success(null); +}); diff --git a/server/api/llm/providers/[id].delete.ts b/server/api/llm/providers/[id].delete.ts new file mode 100644 index 0000000..dcec566 --- /dev/null +++ b/server/api/llm/providers/[id].delete.ts @@ -0,0 +1,14 @@ +import { requireUser } from "#server/utils/context"; +import { deleteProvider } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const id = Number(getRouterParam(event, "id")); + if (!id) { + return R.throwError(400, "无效的供应商ID", null); + } + + await deleteProvider(id, user.id); + return R.success(null); +}); diff --git a/server/api/llm/providers/[id].get.ts b/server/api/llm/providers/[id].get.ts new file mode 100644 index 0000000..040820a --- /dev/null +++ b/server/api/llm/providers/[id].get.ts @@ -0,0 +1,17 @@ +import { requireUser } from "#server/utils/context"; +import { getProviderWithModels } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const id = Number(getRouterParam(event, "id")); + if (!id) { + return R.throwError(400, "无效的供应商ID", null); + } + + const result = await getProviderWithModels(id, user.id); + if (!result) { + return R.throwError(404, "供应商不存在", null); + } + return R.success(result); +}); diff --git a/server/api/llm/providers/[id].models.get.ts b/server/api/llm/providers/[id].models.get.ts new file mode 100644 index 0000000..b1e1406 --- /dev/null +++ b/server/api/llm/providers/[id].models.get.ts @@ -0,0 +1,50 @@ +import { requireUser } from "#server/utils/context"; +import { getProviderById } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const id = Number(getRouterParam(event, "id")); + if (!id) { + return R.throwError(400, "无效的供应商 ID", null); + } + + const provider = await getProviderById(id, user.id); + if (!provider) { + return R.throwError(404, "供应商不存在", null); + } + + const baseUrl = provider.baseUrl?.replace(/\/+$/, "") || ""; + if (!baseUrl) { + return R.success([]); + } + + const headers: Record = { + "Content-Type": "application/json", + }; + + if (provider.parseMode === "anthropic") { + if (provider.apiKey) { + headers["x-api-key"] = provider.apiKey; + } + headers["anthropic-version"] = "2023-06-01"; + } else { + if (provider.apiKey) { + headers["Authorization"] = `Bearer ${provider.apiKey}`; + } + } + + try { + const res = await $fetch<{ data?: { id: string; name?: string }[] }>( + `${baseUrl}/models`, + { headers, timeout: 8000 }, + ); + const models = (res?.data ?? []).map((m) => ({ + id: m.id, + name: m.name || m.id, + })); + return R.success(models); + } catch { + return R.success([]); + } +}); diff --git a/server/api/llm/providers/[id].put.ts b/server/api/llm/providers/[id].put.ts new file mode 100644 index 0000000..35e6385 --- /dev/null +++ b/server/api/llm/providers/[id].put.ts @@ -0,0 +1,25 @@ +import { requireUser } from "#server/utils/context"; +import { updateProvider } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const id = Number(getRouterParam(event, "id")); + if (!id) { + return R.throwError(400, "无效的供应商ID", null); + } + + const body = await readBody(event); + const { name, slug, baseUrl, parseMode, apiKey, status, description } = body; + + await updateProvider(id, user.id, { + name, + slug, + baseUrl, + parseMode, + apiKey, + status, + description, + }); + return R.success(null); +}); diff --git a/server/api/llm/providers/index.get.ts b/server/api/llm/providers/index.get.ts new file mode 100644 index 0000000..69e759b --- /dev/null +++ b/server/api/llm/providers/index.get.ts @@ -0,0 +1,15 @@ +import { requireUser } from "#server/utils/context"; +import { listProviders } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const query = getQuery(event); + const page = Number(query.page) || 1; + const pageSize = Number(query.pageSize) || 20; + const search = query.search as string | undefined; + const status = query.status as string | undefined; + + const result = await listProviders(user.id, page, pageSize, search, status as any); + return R.success(result); +}); diff --git a/server/api/llm/providers/index.post.ts b/server/api/llm/providers/index.post.ts new file mode 100644 index 0000000..88f729f --- /dev/null +++ b/server/api/llm/providers/index.post.ts @@ -0,0 +1,24 @@ +import { requireUser } from "#server/utils/context"; +import { createProvider } from "#server/service/llm"; + +export default defineWrappedResponseHandler(async (event) => { + const user = await requireUser(event); + + const body = await readBody(event); + const { name, slug, baseUrl, parseMode, apiKey, status, description } = body; + + if (!name || !slug) { + return R.throwError(422, "供应商名称和标识不能为空", null); + } + + const result = await createProvider(user.id, { + name, + slug, + baseUrl, + parseMode: parseMode || "openai", + apiKey, + status: status || "active", + description, + }); + return R.success(result); +}); diff --git a/server/service/llm/index.ts b/server/service/llm/index.ts index dcbaea1..b972d26 100644 --- a/server/service/llm/index.ts +++ b/server/service/llm/index.ts @@ -5,6 +5,7 @@ import type { LlmParseMode, LlmProviderStatus, LlmModelType } from "drizzle-pkg/ export interface LlmProviderRow { id: number; + userId: number; name: string; slug: string; baseUrl: string | null; @@ -35,12 +36,13 @@ export interface LlmProviderWithModels extends Omit { } export async function listProviders( + userId: number, page = 1, pageSize = 20, search?: string, status?: LlmProviderStatus, ) { - const conditions = []; + const conditions = [eq(llmProviders.userId, userId)]; if (search) { conditions.push(like(llmProviders.name, `%${search}%`)); } @@ -48,7 +50,7 @@ export async function listProviders( conditions.push(eq(llmProviders.status, status)); } - const where = conditions.length > 0 ? and(...conditions) : undefined; + const where = and(...conditions); const [countResult] = await dbGlobal .select({ count: sql`count(*)` }) @@ -74,17 +76,17 @@ export async function listProviders( return { list: sanitized, total, page, pageSize, totalPages }; } -export async function getProviderById(id: number) { +export async function getProviderById(id: number, userId: number) { const [row] = await dbGlobal .select() .from(llmProviders) - .where(eq(llmProviders.id, id)) + .where(and(eq(llmProviders.id, id), eq(llmProviders.userId, userId))) .limit(1); return row ?? null; } -export async function getProviderWithModels(id: number): Promise { - const provider = await getProviderById(id); +export async function getProviderWithModels(id: number, userId: number): Promise { + const provider = await getProviderById(id, userId); if (!provider) return null; const models = await dbGlobal @@ -101,7 +103,7 @@ export async function getProviderWithModels(id: number): Promise = {}; if (data.name !== undefined) updates.name = data.name; if (data.modelId !== undefined) updates.modelId = data.modelId; @@ -223,6 +247,9 @@ export async function updateModel( .where(eq(llmModels.id, id)); } -export async function deleteModel(id: number) { +export async function deleteModel(id: number, userId: number) { + const model = await getModelById(id, userId); + if (!model) return; + await dbGlobal.delete(llmModels).where(eq(llmModels.id, id)); }