create table public.chat_messages ( id serial primary key, thread_id varchar(255) not null, checkpoint_id varchar(255) not null, message_index integer not null, role varchar(20) not null, content text not null, injected_content text, has_files boolean default false, metadata jsonb, created_at timestamp with time zone default CURRENT_TIMESTAMP, name varchar(255), constraint uk_checkpoint_message unique (checkpoint_id, message_index) ); comment on table public.chat_messages is '聊天消息表,存储用户原始消息和AI响应的关键信息'; comment on column public.chat_messages.thread_id is '会话线程ID'; comment on column public.chat_messages.checkpoint_id is '关联的checkpoint ID'; comment on column public.chat_messages.message_index is '消息在checkpoint中的索引(从0开始)'; comment on column public.chat_messages.role is '消息角色:user、assistant、system、tool'; comment on column public.chat_messages.content is '用户的原始问题或AI的响应'; comment on column public.chat_messages.injected_content is '注入给AI的完整内容(包含文件内容)'; comment on column public.chat_messages.has_files is '是否关联了文件'; comment on column public.chat_messages.metadata is '额外信息:token、模型、推理内容等'; comment on column public.chat_messages.name is '工具消息时的工具名称(如 internet_search、text_to_image)'; alter table public.chat_messages owner to zuoleiroot; create index idx_chat_messages_thread_id on public.chat_messages (thread_id); create index idx_chat_messages_checkpoint_id on public.chat_messages (checkpoint_id); create index idx_chat_messages_thread_created on public.chat_messages (thread_id asc, created_at desc); create index idx_chat_messages_role on public.chat_messages (role); create index idx_chat_messages_has_files on public.chat_messages (has_files); create index idx_chat_messages_metadata on public.chat_messages using gin (metadata); create index idx_chat_messages_content_search on public.chat_messages using gin (to_tsvector('simple'::regconfig, content)); create table public.checkpoint_migrations ( v integer not null primary key ); alter table public.checkpoint_migrations owner to zuoleiroot; create table public.checkpoints ( thread_id text not null, checkpoint_ns text default ''::text not null, checkpoint_id text not null, parent_checkpoint_id text, type text, checkpoint jsonb not null, metadata jsonb default '{}'::jsonb not null, primary key (thread_id, checkpoint_ns, checkpoint_id) ); alter table public.checkpoints owner to zuoleiroot; create index checkpoints_thread_id_idx on public.checkpoints (thread_id); create table public.checkpoint_blobs ( thread_id text not null, checkpoint_ns text default ''::text not null, channel text not null, version text not null, type text not null, blob bytea, primary key (thread_id, checkpoint_ns, channel, version) ); alter table public.checkpoint_blobs owner to zuoleiroot; create index checkpoint_blobs_thread_id_idx on public.checkpoint_blobs (thread_id); create table public.checkpoint_writes ( thread_id text not null, checkpoint_ns text default ''::text not null, checkpoint_id text not null, task_id text not null, idx integer not null, channel text not null, type text, blob bytea not null, task_path text default ''::text not null, primary key (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) ); alter table public.checkpoint_writes owner to zuoleiroot; create index checkpoint_writes_thread_id_idx on public.checkpoint_writes (thread_id); create table public.enterprise ( id serial primary key, name varchar(255) not null, code varchar(64) unique, created_at timestamp with time zone default CURRENT_TIMESTAMP not null, updated_at timestamp with time zone default CURRENT_TIMESTAMP not null ); comment on table public.enterprise is '企业(单租户部署通常仅一条记录)'; alter table public.enterprise owner to zuoleiroot; create table public.department ( id serial primary key, enterprise_id integer not null references public.enterprise on delete cascade, name varchar(255) not null, parent_id integer references public.department on delete set null, created_at timestamp with time zone default CURRENT_TIMESTAMP not null, updated_at timestamp with time zone default CURRENT_TIMESTAMP not null, constraint uq_department_enterprise_name unique (enterprise_id, name) ); comment on table public.department is '部门'; alter table public.department owner to zuoleiroot; create table public.user_list ( id serial primary key, username varchar(50) not null unique, email varchar(255) not null constraint unique_email unique, phone varchar(255) not null unique, github_id varchar(100) constraint unique_github_id unique, github_username varchar(100), github_avatar_url text, github_access_token text, github_token_expires_at timestamp with time zone, display_name varchar(100), avatar_url text, bio text, is_active boolean default true, email_verified boolean default false, created_at timestamp with time zone default CURRENT_TIMESTAMP, updated_at timestamp with time zone default CURRENT_TIMESTAMP, last_login_at timestamp with time zone, hashed_password varchar(255), is_search boolean default false, is_reasoner boolean default false, enterprise_id integer default 1 not null references public.enterprise, department_id integer references public.department on delete set null, role varchar(32) default 'employee'::character varying not null, is_first_login boolean default true not null ); comment on column public.user_list.role is 'admin | leader | employee'; comment on column public.user_list.is_first_login is '首次登录可强制改密(可选业务)'; alter table public.user_list owner to zuoleiroot; create index idx_user_list_github_id on public.user_list (github_id); create index idx_user_list_email on public.user_list (email); create index idx_user_list_username on public.user_list (username); create index idx_user_list_created_at on public.user_list (created_at); create index idx_user_list_enterprise_id on public.user_list (enterprise_id); create index idx_user_list_department_id on public.user_list (department_id); create index idx_user_list_role on public.user_list (role); create table public.knowledge_base ( id serial primary key, user_id integer not null, name varchar(255) not null, description text, created_at timestamp with time zone default CURRENT_TIMESTAMP, updated_at timestamp with time zone default CURRENT_TIMESTAMP, is_deleted boolean default false, deleted_at timestamp with time zone, enterprise_id integer default 1 not null references public.enterprise, department_id integer references public.department on delete set null, creator_id integer not null references public.user_list on delete set null, visibility varchar(32) default 'private'::character varying not null constraint ck_knowledge_base_visibility check ((visibility)::text = ANY ((ARRAY ['private'::character varying, 'department'::character varying, 'enterprise'::character varying])::text[])) ); comment on column public.knowledge_base.creator_id is '创建者(与 user_id 通常一致,用于权限判断)'; comment on column public.knowledge_base.visibility is 'private | department | enterprise'; alter table public.knowledge_base owner to zuoleiroot; create index idx_knowledge_base_user_id on public.knowledge_base (user_id); create index idx_knowledge_base_user_name on public.knowledge_base (user_id, name); create index idx_knowledge_base_created_at on public.knowledge_base (created_at); create index idx_knowledge_base_is_deleted on public.knowledge_base (is_deleted); create index idx_knowledge_base_user_deleted on public.knowledge_base (user_id, is_deleted); create unique index uk_user_knowledge_base_name_active on public.knowledge_base (user_id, name) where (is_deleted = false); create index idx_knowledge_base_enterprise on public.knowledge_base (enterprise_id); create index idx_knowledge_base_creator on public.knowledge_base (creator_id); create index idx_knowledge_base_ent_vis on public.knowledge_base (enterprise_id, visibility) where (is_deleted = false); create table public.knowledge_base_file ( id serial primary key, knowledge_base_id integer not null constraint fk_knowledge_base references public.knowledge_base on delete cascade, user_id integer not null, file_name varchar(255) not null, file_path varchar(500) not null, file_size bigint not null, file_type varchar(50) default 'pdf'::character varying not null, status varchar(20) default 'processing'::character varying not null, chunk_count integer default 0, created_at timestamp with time zone default CURRENT_TIMESTAMP, updated_at timestamp with time zone default CURRENT_TIMESTAMP, is_deleted boolean default false, deleted_at timestamp with time zone ); alter table public.knowledge_base_file owner to zuoleiroot; create index idx_kb_file_kb_id on public.knowledge_base_file (knowledge_base_id); create index idx_kb_file_user_id on public.knowledge_base_file (user_id); create index idx_kb_file_status on public.knowledge_base_file (status); create index idx_kb_file_created_at on public.knowledge_base_file (created_at); create unique index idx_kb_file_unique_active on public.knowledge_base_file (knowledge_base_id, file_name) where (is_deleted = false); create table public.knowledge_base_chunk ( id serial primary key, file_id integer not null constraint fk_kb_file references public.knowledge_base_file on delete cascade, knowledge_base_id integer not null constraint fk_kb references public.knowledge_base on delete cascade, chunk_index integer not null, content text not null, metadata jsonb, vector_id varchar(255), created_at timestamp with time zone default CURRENT_TIMESTAMP, summary text ); alter table public.knowledge_base_chunk owner to zuoleiroot; create index idx_kb_chunk_file_id on public.knowledge_base_chunk (file_id); create index idx_kb_chunk_kb_id on public.knowledge_base_chunk (knowledge_base_id); create index idx_kb_chunk_vector_id on public.knowledge_base_chunk (vector_id); create index idx_kb_chunk_metadata on public.knowledge_base_chunk using gin (metadata); create table public.chat_thread_file ( id serial primary key, thread_id varchar(255) not null, user_id integer not null constraint fk_chat_thread_file_user references public.user_list on delete cascade, file_name varchar(255) not null, file_path varchar(500) not null, file_size integer default 0, file_type varchar(50) default 'pdf'::character varying, status varchar(20) default 'processing'::character varying, chunk_count integer default 0, created_at timestamp with time zone default CURRENT_TIMESTAMP, updated_at timestamp with time zone default CURRENT_TIMESTAMP, is_deleted boolean default false, deleted_at timestamp with time zone ); alter table public.chat_thread_file owner to zuoleiroot; create index idx_chat_thread_file_thread_id on public.chat_thread_file (thread_id); create index idx_chat_thread_file_user_id on public.chat_thread_file (user_id); create index idx_chat_thread_file_thread_user on public.chat_thread_file (thread_id, user_id); create index idx_chat_thread_file_status on public.chat_thread_file (status); create index idx_chat_thread_file_is_deleted on public.chat_thread_file (is_deleted); create index idx_chat_thread_file_created_at on public.chat_thread_file (created_at); create index idx_chat_thread_file_thread_deleted on public.chat_thread_file (thread_id, is_deleted); create unique index uk_chat_thread_file_thread_name_active on public.chat_thread_file (thread_id, file_name) where (is_deleted = false); create table public.chat_thread_chunk ( id serial primary key, file_id integer not null constraint fk_chat_thread_chunk_file references public.chat_thread_file on delete cascade, thread_id varchar(255) not null, chunk_index integer not null, content text not null, metadata jsonb, vector_id varchar(255), created_at timestamp with time zone default CURRENT_TIMESTAMP, summary text ); alter table public.chat_thread_chunk owner to zuoleiroot; create index idx_chat_thread_chunk_file_id on public.chat_thread_chunk (file_id); create index idx_chat_thread_chunk_thread_id on public.chat_thread_chunk (thread_id); create index idx_chat_thread_chunk_file_thread on public.chat_thread_chunk (file_id, thread_id); create index idx_chat_thread_chunk_vector_id on public.chat_thread_chunk (vector_id); create index idx_chat_thread_chunk_created_at on public.chat_thread_chunk (created_at); create table public.chat_message_file ( id serial primary key, thread_id varchar(255) not null, checkpoint_id varchar(255) not null, message_index integer not null, file_id integer not null constraint fk_chat_message_file_file references public.chat_thread_file on delete cascade, created_at timestamp with time zone default CURRENT_TIMESTAMP, constraint uk_message_file unique (checkpoint_id, message_index, file_id) ); alter table public.chat_message_file owner to zuoleiroot; create index idx_chat_message_file_thread_id on public.chat_message_file (thread_id); create index idx_chat_message_file_checkpoint on public.chat_message_file (checkpoint_id, message_index); create index idx_chat_message_file_file_id on public.chat_message_file (file_id); create index idx_chat_message_file_thread_checkpoint on public.chat_message_file (thread_id, checkpoint_id); create table public.graphs ( id serial primary key, user_id integer not null constraint fk_graphs_user references public.user_list on delete cascade, name varchar(255) not null, description text, csv_file_name varchar(255), node_count integer default 0, edge_count integer default 0, neo4j_graph_id varchar(100) not null unique, graph_type varchar(20) default 'knowledge'::character varying not null, build_status varchar(20), build_error text, rag_chunk_count integer default 0 not null, created_at timestamp with time zone default CURRENT_TIMESTAMP, updated_at timestamp with time zone default CURRENT_TIMESTAMP, enterprise_id integer not null references public.enterprise, department_id integer references public.department on delete set null, creator_id integer not null references public.user_list on delete set null, visibility varchar(32) default 'private'::character varying not null constraint ck_graphs_visibility check ((visibility)::text = ANY ((ARRAY ['private'::character varying, 'department'::character varying, 'enterprise'::character varying])::text[])) ); comment on table public.graphs is '知识图谱元数据表,图数据在 Neo4j;向量块数量见 rag_chunk_count'; comment on column public.graphs.neo4j_graph_id is 'Neo4j 中图谱唯一标识'; comment on column public.graphs.graph_type is '兼容字段,默认 knowledge'; comment on column public.graphs.build_status is '构建状态:pending/processing/completed/failed'; comment on column public.graphs.build_error is '构建失败时的错误信息'; comment on column public.graphs.rag_chunk_count is 'Chroma 中知识图谱 RAG 分块数量'; comment on column public.graphs.creator_id is '创建者(与 user_id 通常一致,用于权限判断)'; comment on column public.graphs.visibility is 'private | department | enterprise'; alter table public.graphs owner to zuoleiroot; create index idx_graphs_user_id on public.graphs (user_id); create index idx_graphs_created_at on public.graphs (created_at desc); create index idx_graphs_neo4j_id on public.graphs (neo4j_graph_id); create index idx_graphs_graph_type on public.graphs (user_id, graph_type); create index idx_graphs_enterprise on public.graphs (enterprise_id); create index idx_graphs_creator on public.graphs (creator_id); create index idx_graphs_ent_vis on public.graphs (enterprise_id, visibility); create table public.chat_threads ( id serial primary key, thread_id varchar(255) not null constraint uk_thread_id unique, user_id integer not null constraint fk_user_id references public.user_list on delete cascade, title varchar(50) not null, first_query text not null, created_at timestamp with time zone default CURRENT_TIMESTAMP, updated_at timestamp with time zone default CURRENT_TIMESTAMP, message_count integer default 1, is_deleted boolean default false, knowledge_base_id integer, novel_graph_id integer, knowledge_graph_id integer constraint fk_chat_threads_knowledge_graph references public.graphs on delete set null, ip varchar(128) ); comment on table public.chat_threads is '聊天会话记录表,记录每个用户的会话基本信息'; comment on column public.chat_threads.id is '主键 ID'; comment on column public.chat_threads.thread_id is '会话线程 ID(UUID 格式)'; comment on column public.chat_threads.user_id is '用户 ID,关联 user_list 表'; comment on column public.chat_threads.title is '会话标题(首次请求内容的前10个字)'; comment on column public.chat_threads.first_query is '首次请求的完整内容'; comment on column public.chat_threads.created_at is '会话创建时间'; comment on column public.chat_threads.updated_at is '最后更新时间'; comment on column public.chat_threads.message_count is '该会话的消息总数'; comment on column public.chat_threads.is_deleted is '是否已删除(软删除标记)'; comment on column public.chat_threads.knowledge_graph_id is '绑定的知识图谱 graphs.id,与 knowledge_base_id 二选一'; comment on column public.chat_threads.ip is '最近一次发起聊天时的客户端 IP(可选)'; alter table public.chat_threads owner to zuoleiroot; create index idx_chat_threads_user_id on public.chat_threads (user_id); create index idx_chat_threads_created_at on public.chat_threads (created_at desc); create index idx_chat_threads_user_created on public.chat_threads (user_id asc, created_at desc); create index idx_chat_threads_novel_graph_id on public.chat_threads (novel_graph_id); create index idx_chat_threads_knowledge_graph_id on public.chat_threads (knowledge_graph_id); create table public.knowledge_processing_task ( id serial primary key, user_id integer not null constraint fk_kb_processing_user references public.user_list on delete cascade, knowledge_base_id integer not null constraint fk_kb_processing_kb references public.knowledge_base on delete cascade, task_name varchar(255) not null, instruction text not null, file_ids integer[] not null, task_type varchar(50) not null, status varchar(20) default 'pending'::character varying, result text, result_file_url text, error_message text, created_at timestamp with time zone default CURRENT_TIMESTAMP, updated_at timestamp with time zone default CURRENT_TIMESTAMP, started_at timestamp with time zone, completed_at timestamp with time zone ); comment on table public.knowledge_processing_task is '知识加工任务表:合并、对比、总结等异步任务'; comment on column public.knowledge_processing_task.result_file_url is '加工结果文件的 OSS 下载链接'; alter table public.knowledge_processing_task owner to zuoleiroot; create index idx_kb_processing_user_id on public.knowledge_processing_task (user_id); create index idx_kb_processing_kb_id on public.knowledge_processing_task (knowledge_base_id); create index idx_kb_processing_status on public.knowledge_processing_task (status); create index idx_kb_processing_created_at on public.knowledge_processing_task (created_at desc); create index idx_kb_processing_user_status on public.knowledge_processing_task (user_id, status); create index idx_department_enterprise_id on public.department (enterprise_id);