feat(db): 添加 Drizzle ORM 数据库配置
- 添加 drizzle.config.ts 配置文件 - 添加数据库 schema 定义(会话、消息、设置等表) - 添加数据库连接配置 - 添加数据库迁移文件 - 添加种子数据脚本
This commit is contained in:
parent
0d43d78317
commit
77ef569d34
15
drizzle.config.ts
Normal file
15
drizzle.config.ts
Normal 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
35
src/drizzle/db.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/drizzle/migrations/0000_white_warlock.sql
Normal file
87
src/drizzle/migrations/0000_white_warlock.sql
Normal 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()
|
||||||
|
);
|
||||||
4
src/drizzle/migrations/0001_daffy_paladin.sql
Normal file
4
src/drizzle/migrations/0001_daffy_paladin.sql
Normal 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';
|
||||||
571
src/drizzle/migrations/meta/0000_snapshot.json
Normal file
571
src/drizzle/migrations/meta/0000_snapshot.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
596
src/drizzle/migrations/meta/0001_snapshot.json
Normal file
596
src/drizzle/migrations/meta/0001_snapshot.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/drizzle/migrations/meta/_journal.json
Normal file
20
src/drizzle/migrations/meta/_journal.json
Normal 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
190
src/drizzle/schema.ts
Normal 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
182
src/drizzle/seed.ts
Normal 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));
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user