UZ;z{3ALi#0h`KGKsZBJQQVI7iNYg&Bzair{!aPhpPd{w>{Zn
z7qykhP8Gq6F-<=~v^ACjN*iG5+u8LQJ3V)fSM+@)VF&nE-9is
zk$ViTRbUcwCS2Z=#72OcepCJ#yJGlp_Od2jD3n3dxV#n1(r&Yg+4_dpJ
z5mc{Ipxj=MaL+Z6+=@I6A6s$688~
zZ3hk_u9^Kj$Fwrvj}akU9U8+<00)=DBcaf8ThlBsO4q279%3|Zl1<^Hbb?jn;W`7@
z6USzw_L`-I`N(KE34IQa0~FmhK`VEtS;n6WY
zf<|Uou4`ZbcIH>Buz{(hL`GtxXp)g&c%&qn6sk338Q(?Q<=#8_G!X<`gKfdzaa{v^
z3iUZ)BgZ5Zu%}^EJw%NpQ;&ypVN=QvNcALcQsP8jBwaAs{x
zA4+@BOdWB-MIs;vG>stgY8&JuEtKfUhL%WYfK5o{(&N)HM{M0rXbiDx#!d?ZYi5fg-Ekco9z_tWb;`&uBMes;
z=OZn1I7Q_`Q%OlgakAvcFk#BycnOpv5`d&~NB;EzmSIUu^6^}Gst{Q|n-NKjj0~5a
zl5=jsNfkIE@Qd*5GOY@PgnGTbEVmt|>>`43^iZ7ZI{)_XHg#8qtM8ZP0>nB$YoxZ4dl1`9$?D!OXMiE!L7wS08O0
zeg42OT?O1KoE#>~;Tt*@$D(Dr+>9ilxSW>b92KAIB-Fw20F2bxGCyQZj`TG67E-3?
zl~$>dup*+7W+rl?xkg7<2L(Ptfg3~y9=Y;A`N(0*Og)Z6RZzCSb#qb5g3e}f!If<%jehp41;XGCc|GRK5aTbKg|lyFv&NVuN#Mj&sa
z4B&;Wtg@wGHeR`P^6+l*83F(TJk%U*m3dJ2plb?+3@Vy&s+8vpr}E=`BHQ9>UTC19PD_#YOKRy^VwN%ZeiRbSPgCV7AS_Jthb6
za7w6qvI~s%X2x6Q;|Or0ShA1-3*g5s1ko5sPwAs>gIs
z!QvSpoPZt=gjfxB2ze0sC-O@LC&9hLNmA-@;;2OQ5;EZ;*WqHgo+@I}VTw6-M`JH~d
zzq{|nzNLMq_a%MX^^NO&z2oZ6Z}#5Sdv@<(y*u=-*EzN4?Vi8(jJAEZ=ZK!&dN%HU
zzhhZjPxrmu7j+-oy=V94U8}nu>bkV!i*a2)?%JnotB!AVe%kq1=kGgzHfD_5y4IMn
zVB6e1k6!E5CRb`%WBEDie`x&Af~MncUod?bW{NEDqQ`N?Fm$OKa6?j*U57iEP?swI
zT6PW^lN#_eY}RBy=nf%L+IS@^_5F;#0K8ARsz$E8P+k3t8aeF4{V`Zms-HG#3qBt2
z%%S1Qse!~bc(R|*#x
zG0d29R2-OkIS&SzMM%|s{819-=@-cW%!|$^5*jpfngeXFoJW7OD9@)%r;+l(=3-Lj
zz$7f;XI%?_j0hZ7?ELF3m(TaxwABkj5jd+;y9I`mhg**_A3ZjPLQT!$Q
zHKXcr`Otu}k2E^^m@3;8N?(u3yx|=559m9U$JI#cs_;E69!BiNUE+&KJyFFm
zCo&I2WGks$u7*+>2vDaH1o75F0V2AnSUipykL{6@R&wnJo`vWuGt;Gt90*ykgdRiD
z*E0h|U$m80PEAaquDp_bha=
zG}J6I;L0xfPy#I?D4d(t7jaMAWeHtrM7Cgskwn?@5;YPDpJJBCmtY1IsOaxuv*_a_
z<8sLU?DE}eq;GM%$vEg4m`Jn`F@xKtQ7T2#=f8Q-fCYJYy>;p2;l1+@p_L?1*q
zjsYh?5TZX3C*jd-5KyVD$&du7jx>v0hmx$CQF~V8L9{
zoC?EQ{RC}~9#GUACqoeNDzk=jbEW)64V@4q#Y9vTQ7tlV3=JazGEIhaJV{~F`AEvB
z#34HnstF0#s2t;kuyS^|ypbA-MuvFDhKW>&BjK(HZzm<8%0v&Kt(O{8s})#A|JdZ=
z+4+aiwM`T`$O6R0rbPrHfU+EZNaP5~cZ15)Y9uKMMKTUGPcBG*;FR^8_Z3`4M`IZIR;u8j%Vk`6B{{Dz!mkTsHw(=URoCvjdFRNm|0dpe?&!R(yU
z${ZB8L&kumNN+9DCoIF|F1jT5E~0{HOO>yxku;UbQF48_V9;3z9CsmvIvN@5jr5~(
zW}eb#XfK!?CXR_>1kjR@073_^5qZiaMW(piKLNzpCI7BQIe
zTWTb&tu*wJD!JTRmVkR94mq?M;#z2lNbT}Zry?ts@rYt*K6O(mzEP&fDL^$1ktyg_
zggqsjD@>78Wk($j0G7{=a?~2Emka|{-;C1Aqq$xAIt^Wjnr0$FJ%$MQ0ai{2DdiWq
zq4X)`ki+Cn^nZ$^mJ2Uvdl;I*2f4|JKuA$hDd*0a2N$CX#@>jnio2#pEMf{hHH1Iz
zr&)ILZ}$Sjr_EcqIzAXRk1m{?WM=FhM4kUPA^IA5LkhWakaZl=)87H|cLlxMU%I
zHA&Oz_G%~#5>ihS6ebeT`xv0TX-L?J=dQ&EobsG}B=vtwA~R|S4ofm$!L1^2yFTaW
z(aC}cTFnz-+Ay#P)*v8ky=ZEjp#~qL`4oYdM%#*tRh**P(9fBq?f~nc&IDzRrF1Bb
zl@U|{!CY7fst;7PlIC+kMPRwyIoeJ{3BZd>|D$C&aQqZ$#!5?fHb>HNhUw8lgk>>L
zVGS9P1S2;s(k<@Q{e7cly|#}w^0T7iBWLL_0@+MjOg(M}t`$lMy+;Z|#|=m4KMr
zx@`0Z<%@?d8~wSeTAf$`P^5?r{EUXjB6ss}%IG+dm%Mcfd!QXAFF_7nx~Ln3OBb!9
zs#ZTO5V)$AMFl||BeyqaTciyn0$e{N6EZ8+IyH33o!5eJ$(=llk;Z_Z6bO1k?VIp`
zAR$>9z2BTIuGp1Z7P~ac8me+Ov~c;JAS_%yN6q1c0s)y01qc^zV#i!zZaiLs7BX}u
z&;z@uNDp1S;%yKvUa?xu;l~An4;`R4RHu6ngCWdeHS`*g8B#opFepRy>~sxXT>Z5m
zR8{Tm_yPdk2JePTrh+P-Ndz>!6$z8)8}e?3-chxY7fqn{HgwU1S!xkKDiEac-J+<%
z=cJ{PmkZK74LhUH0davaW#?@oLl+J_2*8B{PpLH=R{-F-C`3^_FkowPnPIB|)3_-h-g@*#?pKjGj#q@*MM;TQMU-fjFDps1iHr8
zqA%wiLqq5NlEx+ePUQ32$kD~;9UI|Lx)TiH(-iga6vPOPW;C24b>>J{mE57x-@XIF
z=mmWW;iv+E-coW9&JJN6TM*F$y#p_wgfZ<%5$-eZ@fbSy2FkGfeYx6%BMSi6LN;)*
zBi+KG!oG-Q^wGnKX~QE~LKA8p#DaUb1!2LxJE|!hQ6SK~g(^ubpoE6Q=XOJ6VZhi3
z{e(#phs`OR^Q_pyInOp*3+5I8WErl<^9QI5o|qaoOqdcTmW?08pO8zb9<-sx2N4L3
z55BLa@Ph(DWhejN7-OIDbQ45#JFRFWRg0TysT+9SnPV-rp3$_X-3o23jEf)et=P
zQL;Ju*ilS<=mcR4O-!|D>~h^)5O%rlScQ-k2y`-#-=c3*Ltqh7s=x?SYN&=hMxkC}
zHXBWLzPlZSo$u~d+mIFr*a6uCnqnrJqHW_EVZJyhGl8bsiJRHEMRyJE^vogPqm1psuEgoN!k
zgly51C$01Fb($`C$Ut$%)t8g-q;c7zmmTt{
zCyqMG9X6KMh8;FOS8Zch0GKH?Ig1Li&dWMUz@^Isd5{{C2lFLHB+d4US-Kd*EZtF4
zIJ7{3S*)!Xr8?)ovb)&AS9X6w0erUrz?%iqE&_R?jGTJNI1ssnTfxhip}+KB!L9g9{|%%2
zTeU4`Oem&-1j)B@4#aH$aM;bQL~BPcA?XeBX$<{&^49?P^W?!CKx@4=gV93GQIC)-
zF@lh=&2505vq8{bNv2fe($H;zM9bTPPt+7zugxefr}E?t_aw5UD+Mcg?1ZjEEt@wH
zA$XuUNcfD9b%>NiLm8dR{0|0QdUlg_
z+;=M&PkdV;v|gJ9YcKMsXt(6`w*Xl3MmY!2T93`>f?aFr
zM=_tV&wd5M*k|+A6#5H`=CvB~C6WnpHfJfRJ5LWp!XQqegwYP8%xP@=Q~?-oDuC8&
zGuqZ@3?VNes^McD03vCxjBt))3Ng+&c9
zg++}|)f8H<&1lk~e?WQ}6T&o4OQ6GuzU0TG5gy_#ZMVu4uDwbe!?jmkq!3!K&1hx8
z#d3ZS2Vf-CC(!y@EKTH;0eCXj+{bHf7hAaIc3Ul?_1p~SytaC>atv25djNo|mp!Hc
zYK19~cbQN<0u)9etOEguZWx$DOuYqF*H-U(hOT;BY~iZ6kIDh2R|^0KtBZJFgrd)N
zL{x?@;0j3XLaHMKDzBlB&Fmh!s?;r?FHKMrs1%>4>xoy#(iTdw9BW2HA@JmnF{n}_
z%Gj<->i)34Si&FHAIkx>asfbrk){M%By@Rxi#s44B6-HGAheJ`lL$1A;mYY^30F>6
zmY|gi1Zq&oRS=CV=H+HyC_}Fl??f2prZBOlbG%|FA<}%%N(&NBzI~|7Jvp>
zF*!Ru!g-MS-P~;f_}$zl`}=tTz)D5t%m|bs7D=AYb8a%s7zrSxtrK-cP2uuWi1WJoC#{>Nr|Y&8W5K
zil!_?E+zq+h9{9@kZ7?L6xQZEmS1oh2+J=xMbVSnr=Dwu%hppfT2L(d
z7x{MDb>&*HkJ4^T6qdRt?On|uT^L%nsvU%7t2R^H(0Xbn5~K94NRbhnA~S<_h-#LQ
z{EK+#HXqClE$fhQxvZlZE?aNSJj$E&rXl#^PT5Fl6h=1FU<rHF!3St1>Xbe(Gp
z_iEmYC-xWt;fXyIchUw5vz7-r@Dua}DVy@#O2l9BPpChHDiXYczCC`(6c8Ri#8q=>
zJvGa|M2SjWjK_|>4}`~#eJ)3s-g;|h&>Mj-O7IV)XFQ4_Gm{o0UW4#Jf<_!?RCoB*
zhM`C2eGh;~=N+r2(0Xe|jfEZ=c>$r5l*s{-Qi=Y@OK~&=ybkZPbNBGbLNSI%7WU)_
zGg@!W_{|2iM6@ob8z!JyNXYXO*a(wDw@~lph8|vW1pp5(S)u@1kIi6SFeHjal)3Pd
zd9MLI2wZLp?tqM!hKl$+^q||_wM|d}6S*1nhdJD0Z8$x#+FQaKyX}bCcdL&*x(4#zq)_Q9OanyNZN0QB3Y>tBz
zQ;Jad2=ZX1gT&Q~{|wFfqzN@>;nW
zv+r{c1K~dRc(s%Nd2B|o;@u=IBjOe82w7EL62f0H^bRDYxY)^i_wEGY-n}O(gw`**
zz_@vV1b-gC+bSdb0(>UvJ(nN0`z2ITwQ(
zB#ki8FKJgsHKc|>hdURPZXAn~$c$z}a>pa$8SZ#wxtc@k=Uh@Q6>kS={6>+*N7J51
z#fB~cHh|W5eitKZ?&IyRoD9P4ul!2Qq4k3<6t~vaKBrjdUq0;s;4h!9|DUMc)p~74
zbKGRL)_o*sJ^GG^_q+>jLD-FqXq@}bp9lUd^&%6K9#Q4bFs$HMw=j?TyX~@
zKDpvyHKO$%tZ~}H$qjQ1jE`^o4kJFk?Qk`s_28><=*5#q&z@Bq=vwo{zZtRS$@SHU
z)|0Nr8m1b}17r0Y3mLKcjVslN)|;)y7w0xdeDwKFjQHsDz0`=-o2W
z2M7F)5g#0|NR4PcifUZPrkajy>idW9$B_3AKS&K}y?Sc2GbG=T_fGnp5$_GFjIyis
zs;P1Hxy=zP&$);ZE6=%9VYFT?HIj=aH!feyx2(8&79&<%y}KIGdX?1ZTsV0&+EXU-
z?(O$5paE=3X$laq6v1ZwOE
z;?+}sqDHh{_B74~BR7#(&g*ByE9a@Cp{w=l7>yGOdw+S+4UBks(VrDY>-R4jyMU1&
z-b;7w%ZQimnw^iB(fWN0UMELV#ihjtPDD}-;g@D)bV8g{L~CXUuQS$2+
zE-EK!J?|4J+hzRF6Wxi4TEh!9+3go new Date())
+ .notNull(),
+ },
+ (table) => [
+ index("idx_llm_provider_slug").on(table.slug),
+ index("idx_llm_provider_status").on(table.status),
+ ],
+);
+
+export const llmModels = sqliteTable(
+ "llm_models",
+ {
+ id: integer("id").primaryKey({ autoIncrement: true }),
+ providerId: integer("provider_id")
+ .notNull()
+ .references(() => llmProviders.id, { onDelete: "cascade" }),
+ name: text("name", { length: 200 }).notNull(),
+ modelId: text("model_id", { length: 200 }).notNull(),
+ type: text("type", { enum: LlmModelTypes }).notNull().default("text"),
+ enabled: integer("enabled").default(1).notNull(),
+ description: text("description"),
+ maxTokens: integer("max_tokens"),
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(),
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" })
+ .defaultNow()
+ .$onUpdate(() => new Date())
+ .notNull(),
+ },
+ (table) => [
+ index("idx_llm_model_provider").on(table.providerId),
+ index("idx_llm_model_type").on(table.type),
+ ],
+);
diff --git a/packages/drizzle-pkg/migrations/0012_funny_santa_claus.sql b/packages/drizzle-pkg/migrations/0012_funny_santa_claus.sql
new file mode 100644
index 0000000..cd7d1d3
--- /dev/null
+++ b/packages/drizzle-pkg/migrations/0012_funny_santa_claus.sql
@@ -0,0 +1,33 @@
+CREATE TABLE `llm_models` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `provider_id` integer NOT NULL,
+ `name` text(200) NOT NULL,
+ `model_id` text(200) NOT NULL,
+ `type` text DEFAULT 'text' NOT NULL,
+ `enabled` integer DEFAULT 1 NOT NULL,
+ `description` text,
+ `max_tokens` integer,
+ `created_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL,
+ `updated_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL,
+ FOREIGN KEY (`provider_id`) REFERENCES `llm_providers`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE INDEX `idx_llm_model_provider` ON `llm_models` (`provider_id`);--> statement-breakpoint
+CREATE INDEX `idx_llm_model_type` ON `llm_models` (`type`);--> statement-breakpoint
+CREATE TABLE `llm_providers` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `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 DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL,
+ `updated_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL
+);
+--> statement-breakpoint
+CREATE UNIQUE INDEX `llm_providers_name_unique` ON `llm_providers` (`name`);--> statement-breakpoint
+CREATE UNIQUE INDEX `llm_providers_slug_unique` ON `llm_providers` (`slug`);--> statement-breakpoint
+CREATE INDEX `idx_llm_provider_slug` ON `llm_providers` (`slug`);--> statement-breakpoint
+CREATE INDEX `idx_llm_provider_status` ON `llm_providers` (`status`);
\ No newline at end of file
diff --git a/packages/drizzle-pkg/migrations/meta/0012_snapshot.json b/packages/drizzle-pkg/migrations/meta/0012_snapshot.json
new file mode 100644
index 0000000..50dd863
--- /dev/null
+++ b/packages/drizzle-pkg/migrations/meta/0012_snapshot.json
@@ -0,0 +1,1659 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "20c54319-abd6-41f9-956b-39d5b6d9e222",
+ "prevId": "076d52b7-d536-402e-9602-7cbbf8f8c616",
+ "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
+ },
+ "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": {
+ "llm_providers_name_unique": {
+ "name": "llm_providers_name_unique",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ },
+ "llm_providers_slug_unique": {
+ "name": "llm_providers_slug_unique",
+ "columns": [
+ "slug"
+ ],
+ "isUnique": true
+ },
+ "idx_llm_provider_slug": {
+ "name": "idx_llm_provider_slug",
+ "columns": [
+ "slug"
+ ],
+ "isUnique": false
+ },
+ "idx_llm_provider_status": {
+ "name": "idx_llm_provider_status",
+ "columns": [
+ "status"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "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 2b094c8..5778112 100644
--- a/packages/drizzle-pkg/migrations/meta/_journal.json
+++ b/packages/drizzle-pkg/migrations/meta/_journal.json
@@ -85,6 +85,13 @@
"when": 1782825462705,
"tag": "0011_add_projects_table",
"breakpoints": true
+ },
+ {
+ "idx": 12,
+ "version": "6",
+ "when": 1782963564430,
+ "tag": "0012_funny_santa_claus",
+ "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
new file mode 100644
index 0000000..b9cb88a
--- /dev/null
+++ b/server/api/admin/llm/models/[providerId].get.ts
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 0000000..b0e1633
--- /dev/null
+++ b/server/api/admin/llm/models/[providerId].post.ts
@@ -0,0 +1,29 @@
+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
new file mode 100644
index 0000000..6789d9a
--- /dev/null
+++ b/server/api/admin/llm/models/detail/[id].delete.ts
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 0000000..097fe08
--- /dev/null
+++ b/server/api/admin/llm/models/detail/[id].put.ts
@@ -0,0 +1,24 @@
+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
new file mode 100644
index 0000000..f75adb3
--- /dev/null
+++ b/server/api/admin/llm/providers/[id].delete.ts
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 0000000..a8d03ba
--- /dev/null
+++ b/server/api/admin/llm/providers/[id].get.ts
@@ -0,0 +1,17 @@
+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
new file mode 100644
index 0000000..a6211f8
--- /dev/null
+++ b/server/api/admin/llm/providers/[id].models.get.ts
@@ -0,0 +1,50 @@
+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
new file mode 100644
index 0000000..ec94f99
--- /dev/null
+++ b/server/api/admin/llm/providers/[id].put.ts
@@ -0,0 +1,25 @@
+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
new file mode 100644
index 0000000..93b2044
--- /dev/null
+++ b/server/api/admin/llm/providers/index.get.ts
@@ -0,0 +1,15 @@
+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
new file mode 100644
index 0000000..0938a69
--- /dev/null
+++ b/server/api/admin/llm/providers/index.post.ts
@@ -0,0 +1,24 @@
+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/service/llm/index.ts b/server/service/llm/index.ts
new file mode 100644
index 0000000..dcbaea1
--- /dev/null
+++ b/server/service/llm/index.ts
@@ -0,0 +1,228 @@
+import { dbGlobal } from "drizzle-pkg/lib/db";
+import { llmProviders, llmModels } from "drizzle-pkg/lib/schema/llm";
+import { eq, desc, like, and, sql } from "drizzle-orm";
+import type { LlmParseMode, LlmProviderStatus, LlmModelType } from "drizzle-pkg/lib/schema/llm";
+
+export interface LlmProviderRow {
+ id: number;
+ name: string;
+ slug: string;
+ baseUrl: string | null;
+ parseMode: LlmParseMode;
+ apiKey: string | null;
+ status: LlmProviderStatus;
+ description: string | null;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+export interface LlmModelRow {
+ id: number;
+ providerId: number;
+ name: string;
+ modelId: string;
+ type: LlmModelType;
+ enabled: number;
+ description: string | null;
+ maxTokens: number | null;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+export interface LlmProviderWithModels extends Omit {
+ apiKeySet: boolean;
+ models: LlmModelRow[];
+}
+
+export async function listProviders(
+ page = 1,
+ pageSize = 20,
+ search?: string,
+ status?: LlmProviderStatus,
+) {
+ const conditions = [];
+ if (search) {
+ conditions.push(like(llmProviders.name, `%${search}%`));
+ }
+ if (status) {
+ conditions.push(eq(llmProviders.status, status));
+ }
+
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
+
+ const [countResult] = await dbGlobal
+ .select({ count: sql`count(*)` })
+ .from(llmProviders)
+ .where(where);
+
+ const total = countResult?.count ?? 0;
+ const totalPages = Math.ceil(total / pageSize);
+
+ const rows = await dbGlobal
+ .select()
+ .from(llmProviders)
+ .where(where)
+ .orderBy(desc(llmProviders.createdAt))
+ .limit(pageSize)
+ .offset((page - 1) * pageSize);
+
+ const sanitized = rows.map(({ apiKey, ...rest }) => ({
+ ...rest,
+ apiKeySet: !!apiKey,
+ }));
+
+ return { list: sanitized, total, page, pageSize, totalPages };
+}
+
+export async function getProviderById(id: number) {
+ const [row] = await dbGlobal
+ .select()
+ .from(llmProviders)
+ .where(eq(llmProviders.id, id))
+ .limit(1);
+ return row ?? null;
+}
+
+export async function getProviderWithModels(id: number): Promise {
+ const provider = await getProviderById(id);
+ if (!provider) return null;
+
+ const models = await dbGlobal
+ .select()
+ .from(llmModels)
+ .where(eq(llmModels.providerId, id))
+ .orderBy(desc(llmModels.createdAt));
+
+ const { apiKey, ...rest } = provider;
+ return {
+ ...rest,
+ apiKeySet: !!apiKey,
+ models,
+ };
+}
+
+export async function createProvider(data: {
+ name: string;
+ slug: string;
+ baseUrl?: string;
+ parseMode: LlmParseMode;
+ apiKey?: string;
+ status?: LlmProviderStatus;
+ description?: string;
+}) {
+ const [inserted] = await dbGlobal
+ .insert(llmProviders)
+ .values({
+ name: data.name,
+ slug: data.slug,
+ baseUrl: data.baseUrl || null,
+ parseMode: data.parseMode,
+ apiKey: data.apiKey || null,
+ status: data.status || "active",
+ description: data.description || null,
+ })
+ .returning({ id: llmProviders.id });
+ return inserted!;
+}
+
+export async function updateProvider(
+ id: number,
+ data: {
+ name?: string;
+ slug?: string;
+ baseUrl?: string;
+ parseMode?: LlmParseMode;
+ apiKey?: string;
+ status?: LlmProviderStatus;
+ description?: string;
+ },
+) {
+ const updates: Record = {};
+ if (data.name !== undefined) updates.name = data.name;
+ if (data.slug !== undefined) updates.slug = data.slug;
+ if (data.baseUrl !== undefined) updates.baseUrl = data.baseUrl;
+ if (data.parseMode !== undefined) updates.parseMode = data.parseMode;
+ if (data.status !== undefined) updates.status = data.status;
+ if (data.description !== undefined) updates.description = data.description;
+ if (data.apiKey !== undefined) {
+ if (data.apiKey === "") {
+ updates.apiKey = null;
+ } else if (data.apiKey !== "__unchanged__") {
+ updates.apiKey = data.apiKey;
+ }
+ }
+
+ if (Object.keys(updates).length === 0) return;
+
+ await dbGlobal
+ .update(llmProviders)
+ .set(updates)
+ .where(eq(llmProviders.id, id));
+}
+
+export async function deleteProvider(id: number) {
+ await dbGlobal.delete(llmProviders).where(eq(llmProviders.id, id));
+}
+
+export async function listModelsByProvider(providerId: number) {
+ return dbGlobal
+ .select()
+ .from(llmModels)
+ .where(eq(llmModels.providerId, providerId))
+ .orderBy(desc(llmModels.createdAt));
+}
+
+export async function createModel(data: {
+ providerId: number;
+ name: string;
+ modelId: string;
+ type: LlmModelType;
+ enabled?: number;
+ description?: string;
+ maxTokens?: number;
+}) {
+ const [inserted] = await dbGlobal
+ .insert(llmModels)
+ .values({
+ providerId: data.providerId,
+ name: data.name,
+ modelId: data.modelId,
+ type: data.type,
+ enabled: data.enabled ?? 1,
+ description: data.description || null,
+ maxTokens: data.maxTokens || null,
+ })
+ .returning({ id: llmModels.id });
+ return inserted!;
+}
+
+export async function updateModel(
+ id: number,
+ data: {
+ name?: string;
+ modelId?: string;
+ type?: LlmModelType;
+ enabled?: number;
+ description?: string;
+ maxTokens?: number;
+ },
+) {
+ const updates: Record = {};
+ if (data.name !== undefined) updates.name = data.name;
+ if (data.modelId !== undefined) updates.modelId = data.modelId;
+ if (data.type !== undefined) updates.type = data.type;
+ if (data.enabled !== undefined) updates.enabled = data.enabled;
+ if (data.description !== undefined) updates.description = data.description;
+ if (data.maxTokens !== undefined) updates.maxTokens = data.maxTokens;
+
+ if (Object.keys(updates).length === 0) return;
+
+ await dbGlobal
+ .update(llmModels)
+ .set(updates)
+ .where(eq(llmModels.id, id));
+}
+
+export async function deleteModel(id: number) {
+ await dbGlobal.delete(llmModels).where(eq(llmModels.id, id));
+}