feat(db): 添加 Drizzle ORM 数据库配置

- 添加 drizzle.config.ts 配置文件
- 添加数据库 schema 定义(会话、消息、设置等表)
- 添加数据库连接配置
- 添加数据库迁移文件
- 添加种子数据脚本
This commit is contained in:
gaoziman 2025-12-18 11:27:49 +08:00
parent 0d43d78317
commit 77ef569d34
9 changed files with 1700 additions and 0 deletions

15
drizzle.config.ts Normal file
View File

@ -0,0 +1,15 @@
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/drizzle/schema.ts',
out: './src/drizzle/migrations',
dialect: 'postgresql',
dbCredentials: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '35433'),
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
database: process.env.DB_NAME || 'cchcode_ui',
ssl: false,
},
});

35
src/drizzle/db.ts Normal file
View File

@ -0,0 +1,35 @@
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';
// 创建连接池
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '35433'),
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
database: process.env.DB_NAME || 'cchcode_ui',
max: 10, // 最大连接数
idleTimeoutMillis: 30000, // 空闲超时
connectionTimeoutMillis: 5000, // 连接超时
ssl: false, // 禁用 SSL
});
// 创建 Drizzle 实例
export const db = drizzle(pool, { schema });
// 导出 pool 以便需要时直接使用
export { pool };
// 健康检查函数
export async function checkDatabaseConnection(): Promise<boolean> {
try {
const client = await pool.connect();
await client.query('SELECT 1');
client.release();
return true;
} catch (error) {
console.error('Database connection check failed:', error);
return false;
}
}

View File

@ -0,0 +1,87 @@
CREATE TABLE "conversations" (
"id" serial PRIMARY KEY NOT NULL,
"conversation_id" varchar(64) NOT NULL,
"title" varchar(255) DEFAULT '新对话' NOT NULL,
"summary" text,
"model" varchar(64) NOT NULL,
"tools" jsonb DEFAULT '[]'::jsonb,
"enable_thinking" boolean DEFAULT false,
"message_count" integer DEFAULT 0,
"total_tokens" integer DEFAULT 0,
"is_archived" boolean DEFAULT false,
"is_pinned" boolean DEFAULT false,
"created_at" timestamp with time zone DEFAULT now(),
"updated_at" timestamp with time zone DEFAULT now(),
"last_message_at" timestamp with time zone DEFAULT now(),
CONSTRAINT "conversations_conversation_id_unique" UNIQUE("conversation_id")
);
--> statement-breakpoint
CREATE TABLE "messages" (
"id" serial PRIMARY KEY NOT NULL,
"message_id" varchar(64) NOT NULL,
"conversation_id" varchar(64) NOT NULL,
"role" varchar(20) NOT NULL,
"content" text NOT NULL,
"thinking_content" text,
"thinking_collapsed" boolean DEFAULT true,
"tool_calls" jsonb,
"tool_results" jsonb,
"input_tokens" integer DEFAULT 0,
"output_tokens" integer DEFAULT 0,
"status" varchar(20) DEFAULT 'completed',
"error_message" text,
"feedback" varchar(10),
"created_at" timestamp with time zone DEFAULT now(),
"updated_at" timestamp with time zone DEFAULT now(),
CONSTRAINT "messages_message_id_unique" UNIQUE("message_id")
);
--> statement-breakpoint
CREATE TABLE "models" (
"id" serial PRIMARY KEY NOT NULL,
"model_id" varchar(128) NOT NULL,
"name" varchar(64) NOT NULL,
"display_name" varchar(128) NOT NULL,
"description" text,
"supports_tools" boolean DEFAULT true,
"supports_thinking" boolean DEFAULT true,
"supports_vision" boolean DEFAULT false,
"max_tokens" integer DEFAULT 8192,
"context_window" integer DEFAULT 200000,
"is_enabled" boolean DEFAULT true,
"is_default" boolean DEFAULT false,
"sort_order" integer DEFAULT 0,
"created_at" timestamp with time zone DEFAULT now(),
"updated_at" timestamp with time zone DEFAULT now(),
CONSTRAINT "models_model_id_unique" UNIQUE("model_id")
);
--> statement-breakpoint
CREATE TABLE "tools" (
"id" serial PRIMARY KEY NOT NULL,
"tool_id" varchar(64) NOT NULL,
"name" varchar(64) NOT NULL,
"display_name" varchar(128) NOT NULL,
"description" text,
"icon" varchar(64),
"input_schema" jsonb NOT NULL,
"is_enabled" boolean DEFAULT true,
"is_default" boolean DEFAULT false,
"sort_order" integer DEFAULT 0,
"created_at" timestamp with time zone DEFAULT now(),
"updated_at" timestamp with time zone DEFAULT now(),
CONSTRAINT "tools_tool_id_unique" UNIQUE("tool_id")
);
--> statement-breakpoint
CREATE TABLE "user_settings" (
"id" serial PRIMARY KEY NOT NULL,
"cch_url" varchar(512) DEFAULT 'http://localhost:13500' NOT NULL,
"cch_api_key" varchar(512),
"cch_api_key_configured" boolean DEFAULT false,
"default_model" varchar(64) DEFAULT 'claude-sonnet-4-20250514',
"default_tools" jsonb DEFAULT '["web_search","code_execution","web_fetch"]'::jsonb,
"theme" varchar(20) DEFAULT 'light',
"language" varchar(10) DEFAULT 'zh-CN',
"enable_thinking" boolean DEFAULT false,
"save_chat_history" boolean DEFAULT true,
"created_at" timestamp with time zone DEFAULT now(),
"updated_at" timestamp with time zone DEFAULT now()
);

View File

@ -0,0 +1,4 @@
ALTER TABLE "conversations" ADD COLUMN "system_prompt" text;--> statement-breakpoint
ALTER TABLE "conversations" ADD COLUMN "temperature" varchar(10);--> statement-breakpoint
ALTER TABLE "user_settings" ADD COLUMN "system_prompt" text;--> statement-breakpoint
ALTER TABLE "user_settings" ADD COLUMN "temperature" varchar(10) DEFAULT '0.7';

View File

@ -0,0 +1,571 @@
{
"id": "03e6937f-d493-44f6-8109-704693f38906",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.conversations": {
"name": "conversations",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"conversation_id": {
"name": "conversation_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "'新对话'"
},
"summary": {
"name": "summary",
"type": "text",
"primaryKey": false,
"notNull": false
},
"model": {
"name": "model",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"tools": {
"name": "tools",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'[]'::jsonb"
},
"enable_thinking": {
"name": "enable_thinking",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"message_count": {
"name": "message_count",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"total_tokens": {
"name": "total_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"is_archived": {
"name": "is_archived",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"is_pinned": {
"name": "is_pinned",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"last_message_at": {
"name": "last_message_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"conversations_conversation_id_unique": {
"name": "conversations_conversation_id_unique",
"nullsNotDistinct": false,
"columns": [
"conversation_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.messages": {
"name": "messages",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"message_id": {
"name": "message_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"conversation_id": {
"name": "conversation_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "varchar(20)",
"primaryKey": false,
"notNull": true
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true
},
"thinking_content": {
"name": "thinking_content",
"type": "text",
"primaryKey": false,
"notNull": false
},
"thinking_collapsed": {
"name": "thinking_collapsed",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"tool_calls": {
"name": "tool_calls",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"tool_results": {
"name": "tool_results",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"input_tokens": {
"name": "input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"output_tokens": {
"name": "output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"status": {
"name": "status",
"type": "varchar(20)",
"primaryKey": false,
"notNull": false,
"default": "'completed'"
},
"error_message": {
"name": "error_message",
"type": "text",
"primaryKey": false,
"notNull": false
},
"feedback": {
"name": "feedback",
"type": "varchar(10)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"messages_message_id_unique": {
"name": "messages_message_id_unique",
"nullsNotDistinct": false,
"columns": [
"message_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.models": {
"name": "models",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"model_id": {
"name": "model_id",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"supports_tools": {
"name": "supports_tools",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"supports_thinking": {
"name": "supports_thinking",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"supports_vision": {
"name": "supports_vision",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"max_tokens": {
"name": "max_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 8192
},
"context_window": {
"name": "context_window",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 200000
},
"is_enabled": {
"name": "is_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"is_default": {
"name": "is_default",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"sort_order": {
"name": "sort_order",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"models_model_id_unique": {
"name": "models_model_id_unique",
"nullsNotDistinct": false,
"columns": [
"model_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tools": {
"name": "tools",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"tool_id": {
"name": "tool_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"icon": {
"name": "icon",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"input_schema": {
"name": "input_schema",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"is_enabled": {
"name": "is_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"is_default": {
"name": "is_default",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"sort_order": {
"name": "sort_order",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"tools_tool_id_unique": {
"name": "tools_tool_id_unique",
"nullsNotDistinct": false,
"columns": [
"tool_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user_settings": {
"name": "user_settings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"cch_url": {
"name": "cch_url",
"type": "varchar(512)",
"primaryKey": false,
"notNull": true,
"default": "'http://localhost:13500'"
},
"cch_api_key": {
"name": "cch_api_key",
"type": "varchar(512)",
"primaryKey": false,
"notNull": false
},
"cch_api_key_configured": {
"name": "cch_api_key_configured",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"default_model": {
"name": "default_model",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false,
"default": "'claude-sonnet-4-20250514'"
},
"default_tools": {
"name": "default_tools",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'[\"web_search\",\"code_execution\",\"web_fetch\"]'::jsonb"
},
"theme": {
"name": "theme",
"type": "varchar(20)",
"primaryKey": false,
"notNull": false,
"default": "'light'"
},
"language": {
"name": "language",
"type": "varchar(10)",
"primaryKey": false,
"notNull": false,
"default": "'zh-CN'"
},
"enable_thinking": {
"name": "enable_thinking",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"save_chat_history": {
"name": "save_chat_history",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,596 @@
{
"id": "dc7ae80d-12dc-4e13-9c7b-101b9f79b198",
"prevId": "03e6937f-d493-44f6-8109-704693f38906",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.conversations": {
"name": "conversations",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"conversation_id": {
"name": "conversation_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"title": {
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "'新对话'"
},
"summary": {
"name": "summary",
"type": "text",
"primaryKey": false,
"notNull": false
},
"model": {
"name": "model",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"tools": {
"name": "tools",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'[]'::jsonb"
},
"enable_thinking": {
"name": "enable_thinking",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"system_prompt": {
"name": "system_prompt",
"type": "text",
"primaryKey": false,
"notNull": false
},
"temperature": {
"name": "temperature",
"type": "varchar(10)",
"primaryKey": false,
"notNull": false
},
"message_count": {
"name": "message_count",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"total_tokens": {
"name": "total_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"is_archived": {
"name": "is_archived",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"is_pinned": {
"name": "is_pinned",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"last_message_at": {
"name": "last_message_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"conversations_conversation_id_unique": {
"name": "conversations_conversation_id_unique",
"nullsNotDistinct": false,
"columns": [
"conversation_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.messages": {
"name": "messages",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"message_id": {
"name": "message_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"conversation_id": {
"name": "conversation_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "varchar(20)",
"primaryKey": false,
"notNull": true
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true
},
"thinking_content": {
"name": "thinking_content",
"type": "text",
"primaryKey": false,
"notNull": false
},
"thinking_collapsed": {
"name": "thinking_collapsed",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"tool_calls": {
"name": "tool_calls",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"tool_results": {
"name": "tool_results",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"input_tokens": {
"name": "input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"output_tokens": {
"name": "output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"status": {
"name": "status",
"type": "varchar(20)",
"primaryKey": false,
"notNull": false,
"default": "'completed'"
},
"error_message": {
"name": "error_message",
"type": "text",
"primaryKey": false,
"notNull": false
},
"feedback": {
"name": "feedback",
"type": "varchar(10)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"messages_message_id_unique": {
"name": "messages_message_id_unique",
"nullsNotDistinct": false,
"columns": [
"message_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.models": {
"name": "models",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"model_id": {
"name": "model_id",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"supports_tools": {
"name": "supports_tools",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"supports_thinking": {
"name": "supports_thinking",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"supports_vision": {
"name": "supports_vision",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"max_tokens": {
"name": "max_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 8192
},
"context_window": {
"name": "context_window",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 200000
},
"is_enabled": {
"name": "is_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"is_default": {
"name": "is_default",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"sort_order": {
"name": "sort_order",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"models_model_id_unique": {
"name": "models_model_id_unique",
"nullsNotDistinct": false,
"columns": [
"model_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tools": {
"name": "tools",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"tool_id": {
"name": "tool_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"icon": {
"name": "icon",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"input_schema": {
"name": "input_schema",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"is_enabled": {
"name": "is_enabled",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"is_default": {
"name": "is_default",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"sort_order": {
"name": "sort_order",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"tools_tool_id_unique": {
"name": "tools_tool_id_unique",
"nullsNotDistinct": false,
"columns": [
"tool_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user_settings": {
"name": "user_settings",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"cch_url": {
"name": "cch_url",
"type": "varchar(512)",
"primaryKey": false,
"notNull": true,
"default": "'http://localhost:13500'"
},
"cch_api_key": {
"name": "cch_api_key",
"type": "varchar(512)",
"primaryKey": false,
"notNull": false
},
"cch_api_key_configured": {
"name": "cch_api_key_configured",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"default_model": {
"name": "default_model",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false,
"default": "'claude-sonnet-4-20250514'"
},
"default_tools": {
"name": "default_tools",
"type": "jsonb",
"primaryKey": false,
"notNull": false,
"default": "'[\"web_search\",\"code_execution\",\"web_fetch\"]'::jsonb"
},
"system_prompt": {
"name": "system_prompt",
"type": "text",
"primaryKey": false,
"notNull": false
},
"temperature": {
"name": "temperature",
"type": "varchar(10)",
"primaryKey": false,
"notNull": false,
"default": "'0.7'"
},
"theme": {
"name": "theme",
"type": "varchar(20)",
"primaryKey": false,
"notNull": false,
"default": "'light'"
},
"language": {
"name": "language",
"type": "varchar(10)",
"primaryKey": false,
"notNull": false,
"default": "'zh-CN'"
},
"enable_thinking": {
"name": "enable_thinking",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"save_chat_history": {
"name": "save_chat_history",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,20 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1765985557199,
"tag": "0000_white_warlock",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1765991206886,
"tag": "0001_daffy_paladin",
"breakpoints": true
}
]
}

190
src/drizzle/schema.ts Normal file
View File

@ -0,0 +1,190 @@
import {
pgTable,
serial,
varchar,
text,
boolean,
integer,
timestamp,
jsonb,
} from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
// ============================================
// 用户设置表
// ============================================
export const userSettings = pgTable('user_settings', {
id: serial('id').primaryKey(),
// CCH 配置
cchUrl: varchar('cch_url', { length: 512 }).notNull().default('http://localhost:13500'),
cchApiKey: varchar('cch_api_key', { length: 512 }),
cchApiKeyConfigured: boolean('cch_api_key_configured').default(false),
// 默认设置
defaultModel: varchar('default_model', { length: 64 }).default('claude-sonnet-4-20250514'),
defaultTools: jsonb('default_tools').$type<string[]>().default(['web_search', 'code_execution', 'web_fetch']),
// AI 行为设置
systemPrompt: text('system_prompt'), // 系统提示词
temperature: varchar('temperature', { length: 10 }).default('0.7'), // 温度参数 (0-1)
// 偏好设置
theme: varchar('theme', { length: 20 }).default('light'),
language: varchar('language', { length: 10 }).default('zh-CN'),
enableThinking: boolean('enable_thinking').default(false),
saveChatHistory: boolean('save_chat_history').default(true),
// 时间戳
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
});
// ============================================
// 对话表
// ============================================
export const conversations = pgTable('conversations', {
id: serial('id').primaryKey(),
// 对话唯一标识
conversationId: varchar('conversation_id', { length: 64 }).notNull().unique(),
// 对话信息
title: varchar('title', { length: 255 }).notNull().default('新对话'),
summary: text('summary'),
// 模型和工具配置
model: varchar('model', { length: 64 }).notNull(),
tools: jsonb('tools').$type<string[]>().default([]),
enableThinking: boolean('enable_thinking').default(false),
// AI 行为设置(对话级别,可覆盖全局设置)
systemPrompt: text('system_prompt'), // 对话专属系统提示词
temperature: varchar('temperature', { length: 10 }), // 对话专属温度参数
// 统计信息
messageCount: integer('message_count').default(0),
totalTokens: integer('total_tokens').default(0),
// 状态
isArchived: boolean('is_archived').default(false),
isPinned: boolean('is_pinned').default(false),
// 时间戳
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
lastMessageAt: timestamp('last_message_at', { withTimezone: true }).defaultNow(),
});
// ============================================
// 消息表
// ============================================
export const messages = pgTable('messages', {
id: serial('id').primaryKey(),
// 消息唯一标识
messageId: varchar('message_id', { length: 64 }).notNull().unique(),
// 关联对话
conversationId: varchar('conversation_id', { length: 64 }).notNull(),
// 消息内容
role: varchar('role', { length: 20 }).notNull(), // user, assistant, system
content: text('content').notNull(),
// 思考内容
thinkingContent: text('thinking_content'),
thinkingCollapsed: boolean('thinking_collapsed').default(true),
// 工具调用记录
toolCalls: jsonb('tool_calls').$type<ToolCall[]>(),
toolResults: jsonb('tool_results').$type<ToolResult[]>(),
// Token 统计
inputTokens: integer('input_tokens').default(0),
outputTokens: integer('output_tokens').default(0),
// 状态
status: varchar('status', { length: 20 }).default('completed'), // pending, streaming, completed, error
errorMessage: text('error_message'),
// 用户反馈
feedback: varchar('feedback', { length: 10 }), // thumbs_up, thumbs_down
// 时间戳
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
});
// ============================================
// 工具表
// ============================================
export const tools = pgTable('tools', {
id: serial('id').primaryKey(),
// 工具唯一标识
toolId: varchar('tool_id', { length: 64 }).notNull().unique(),
// 工具信息
name: varchar('name', { length: 64 }).notNull(),
displayName: varchar('display_name', { length: 128 }).notNull(),
description: text('description'),
icon: varchar('icon', { length: 64 }),
// Claude Tool Schema
inputSchema: jsonb('input_schema').notNull(),
// 配置
isEnabled: boolean('is_enabled').default(true),
isDefault: boolean('is_default').default(false),
sortOrder: integer('sort_order').default(0),
// 时间戳
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
});
// ============================================
// 模型表
// ============================================
export const models = pgTable('models', {
id: serial('id').primaryKey(),
// 模型唯一标识
modelId: varchar('model_id', { length: 128 }).notNull().unique(),
// 模型信息
name: varchar('name', { length: 64 }).notNull(),
displayName: varchar('display_name', { length: 128 }).notNull(),
description: text('description'),
// 模型特性
supportsTools: boolean('supports_tools').default(true),
supportsThinking: boolean('supports_thinking').default(true),
supportsVision: boolean('supports_vision').default(false),
maxTokens: integer('max_tokens').default(8192),
contextWindow: integer('context_window').default(200000),
// 配置
isEnabled: boolean('is_enabled').default(true),
isDefault: boolean('is_default').default(false),
sortOrder: integer('sort_order').default(0),
// 时间戳
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
});
// ============================================
// 关系定义
// ============================================
export const conversationsRelations = relations(conversations, ({ many }) => ({
messages: many(messages),
}));
export const messagesRelations = relations(messages, ({ one }) => ({
conversation: one(conversations, {
fields: [messages.conversationId],
references: [conversations.conversationId],
}),
}));
// ============================================
// 类型定义
// ============================================
export interface ToolCall {
id: string;
name: string;
input: Record<string, unknown>;
}
export interface ToolResult {
toolUseId: string;
content: string;
isError?: boolean;
}
// 导出类型
export type UserSettings = typeof userSettings.$inferSelect;
export type NewUserSettings = typeof userSettings.$inferInsert;
export type Conversation = typeof conversations.$inferSelect;
export type NewConversation = typeof conversations.$inferInsert;
export type Message = typeof messages.$inferSelect;
export type NewMessage = typeof messages.$inferInsert;
export type Tool = typeof tools.$inferSelect;
export type NewTool = typeof tools.$inferInsert;
export type Model = typeof models.$inferSelect;
export type NewModel = typeof models.$inferInsert;

182
src/drizzle/seed.ts Normal file
View File

@ -0,0 +1,182 @@
import { db } from './db';
import { userSettings, tools, models } from './schema';
import { eq } from 'drizzle-orm';
// 初始化用户设置
async function seedUserSettings() {
const existing = await db.query.userSettings.findFirst({
where: eq(userSettings.id, 1),
});
if (!existing) {
await db.insert(userSettings).values({
id: 1,
cchUrl: 'http://localhost:13500',
cchApiKeyConfigured: false,
defaultModel: 'claude-sonnet-4-5-20250929',
defaultTools: ['web_search', 'code_execution', 'web_fetch'],
theme: 'light',
language: 'zh-CN',
enableThinking: false,
saveChatHistory: true,
});
console.log('✓ User settings initialized');
} else {
console.log('→ User settings already exist');
}
}
// 初始化工具
async function seedTools() {
const toolsData = [
{
toolId: 'web_search',
name: 'web_search',
displayName: 'Web Search',
description: '搜索互联网获取最新信息',
icon: 'Search',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索查询' },
},
required: ['query'],
},
isEnabled: true,
isDefault: true,
sortOrder: 1,
},
{
toolId: 'code_execution',
name: 'code_execution',
displayName: 'Code Execution',
description: '执行代码片段并返回结果',
icon: 'Terminal',
inputSchema: {
type: 'object',
properties: {
code: { type: 'string', description: '要执行的代码' },
language: { type: 'string', description: '编程语言' },
},
required: ['code', 'language'],
},
isEnabled: true,
isDefault: true,
sortOrder: 2,
},
{
toolId: 'web_fetch',
name: 'web_fetch',
displayName: 'Web Fetch',
description: '获取网页内容',
icon: 'Globe',
inputSchema: {
type: 'object',
properties: {
url: { type: 'string', description: '要获取的URL' },
},
required: ['url'],
},
isEnabled: true,
isDefault: true,
sortOrder: 3,
},
];
for (const tool of toolsData) {
const existing = await db.query.tools.findFirst({
where: eq(tools.toolId, tool.toolId),
});
if (!existing) {
await db.insert(tools).values(tool);
console.log(`✓ Tool "${tool.displayName}" initialized`);
} else {
console.log(`→ Tool "${tool.displayName}" already exists`);
}
}
}
// 初始化模型
async function seedModels() {
const modelsData = [
{
modelId: 'claude-sonnet-4-5-20250929',
name: 'claude-sonnet-4-5-20250929',
displayName: 'Default (recommended)',
description: '使用默认模型Sonnet 4.5$3/$15 per Mtok',
supportsTools: true,
supportsThinking: true,
supportsVision: true,
maxTokens: 8192,
contextWindow: 200000,
isEnabled: true,
isDefault: true,
sortOrder: 1,
},
{
modelId: 'claude-opus-4-5-20251101',
name: 'claude-opus-4-5-20251101',
displayName: 'Opus',
description: 'Opus 4.5 - 最强大的复杂工作,$5/$25 per Mtok',
supportsTools: true,
supportsThinking: true,
supportsVision: true,
maxTokens: 8192,
contextWindow: 200000,
isEnabled: true,
isDefault: false,
sortOrder: 2,
},
{
modelId: 'claude-haiku-4-5-20251001',
name: 'claude-haiku-4-5-20251001',
displayName: 'Haiku',
description: 'Haiku 4.5 - 快速回答,$1/$5 per Mtok',
supportsTools: true,
supportsThinking: false,
supportsVision: false,
maxTokens: 8192,
contextWindow: 200000,
isEnabled: true,
isDefault: false,
sortOrder: 3,
},
];
for (const model of modelsData) {
const existing = await db.query.models.findFirst({
where: eq(models.modelId, model.modelId),
});
if (!existing) {
await db.insert(models).values(model);
console.log(`✓ Model "${model.displayName}" initialized`);
} else {
console.log(`→ Model "${model.displayName}" already exists`);
}
}
}
// 主函数
export async function seed() {
console.log('\n🌱 Starting database seed...\n');
try {
await seedUserSettings();
await seedTools();
await seedModels();
console.log('\n✅ Database seed completed!\n');
} catch (error) {
console.error('\n❌ Database seed failed:', error);
throw error;
}
}
// 如果直接运行此文件
if (require.main === module) {
seed()
.then(() => process.exit(0))
.catch(() => process.exit(1));
}