diff --git a/.gitignore b/.gitignore index fe9abff..7c8cf6a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,7 @@ logs/ *.temp # Local models -models +backend/models # AI .gemini-clipboard/ diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..4f33205 --- /dev/null +++ b/backend/app/models/__init__.py @@ -0,0 +1,37 @@ +""" +导出所有数据库模型 +""" +from app.core.database import Base +from app.models.user import User +from app.models.role import Role, UserRole +from app.models.menu import SystemMenu, RoleMenu +from app.models.project import Project, ProjectMember, ProjectMemberRole +from app.models.document import DocumentMeta +from app.models.document_vector import DocumentVector +from app.models.share import ShareLink +from app.models.log import OperationLog +from app.models.mcp_bot import MCPBot +from app.models.llm_model_config import LLMModelConfig +from app.models.chat_session import ChatSession, ChatMessage +from app.models.project_vectorization_task import ProjectVectorizationTask + +__all__ = [ + "Base", + "User", + "Role", + "UserRole", + "SystemMenu", + "RoleMenu", + "Project", + "ProjectMember", + "ProjectMemberRole", + "DocumentMeta", + "DocumentVector", + "ShareLink", + "OperationLog", + "MCPBot", + "LLMModelConfig", + "ChatSession", + "ChatMessage", + "ProjectVectorizationTask", +] diff --git a/backend/app/models/chat_session.py b/backend/app/models/chat_session.py new file mode 100644 index 0000000..714cb9e --- /dev/null +++ b/backend/app/models/chat_session.py @@ -0,0 +1,44 @@ +""" +知识库对话会话模型 +""" +from sqlalchemy import Column, BigInteger, String, Integer, DateTime, Text, Boolean +from sqlalchemy.sql import func +from app.core.database import Base + + +class ChatSession(Base): + """对话会话表""" + + __tablename__ = "chat_session" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="会话ID") + project_id = Column(BigInteger, nullable=False, index=True, comment="项目ID") + user_id = Column(BigInteger, nullable=False, index=True, comment="用户ID") + llm_config_id = Column(BigInteger, nullable=False, comment="LLM配置ID") + title = Column(String(255), nullable=False, comment="会话标题") + description = Column(Text, comment="会话描述") + is_active = Column(Boolean, nullable=False, default=True, comment="是否激活") + message_count = Column(Integer, nullable=False, default=0, comment="消息数") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + def __repr__(self): + return f"" + + +class ChatMessage(Base): + """对话消息表""" + + __tablename__ = "chat_message" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="消息ID") + session_id = Column(BigInteger, nullable=False, index=True, comment="会话ID") + role = Column(String(32), nullable=False, comment="角色(user/assistant)") + content = Column(Text, nullable=False, comment="消息内容") + referenced_files = Column(Text, comment="参考文件(JSON数组)") + tokens_used = Column(Integer, comment="消耗的token数") + is_deleted = Column(Boolean, nullable=False, default=False, comment="是否已删除") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + + def __repr__(self): + return f"" diff --git a/backend/app/models/document.py b/backend/app/models/document.py new file mode 100644 index 0000000..76a1814 --- /dev/null +++ b/backend/app/models/document.py @@ -0,0 +1,28 @@ +""" +文档元数据模型 +""" +from sqlalchemy import Column, BigInteger, String, Integer, DateTime +from sqlalchemy.sql import func +from app.core.database import Base + + +class DocumentMeta(Base): + """文档元数据表模型""" + + __tablename__ = "document_meta" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="元数据ID") + project_id = Column(BigInteger, nullable=False, index=True, comment="项目ID") + file_path = Column(String(500), nullable=False, comment="文件相对路径") + title = Column(String(200), comment="文档标题") + tags = Column(String(500), comment="标签(JSON数组)") + author_id = Column(BigInteger, index=True, comment="作者ID") + word_count = Column(Integer, default=0, comment="字数统计") + view_count = Column(Integer, default=0, comment="浏览次数") + last_editor_id = Column(BigInteger, comment="最后编辑者ID") + last_edited_at = Column(DateTime, comment="最后编辑时间") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + def __repr__(self): + return f"" diff --git a/backend/app/models/document_vector.py b/backend/app/models/document_vector.py new file mode 100644 index 0000000..557bb2a --- /dev/null +++ b/backend/app/models/document_vector.py @@ -0,0 +1,36 @@ +""" +文档向量化模型 +""" +from sqlalchemy import Column, BigInteger, Integer, String, DateTime, Index, Text +from sqlalchemy.sql import func +from app.core.database import Base + + +class DocumentVector(Base): + """文档向量表模型""" + + __tablename__ = "document_vector" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="向量ID") + project_id = Column(BigInteger, nullable=False, index=True, comment="项目ID") + file_path = Column(String(500), nullable=False, comment="文件相对路径") + chunk_index = Column(Integer, nullable=False, default=0, comment="分块序号(0起),同一文件可有多个分块") + chunk_text = Column(Text, comment="分块首段文本,作为点击引用时的定位锚点") + content_hash = Column(String(64), comment="整个文件内容哈希值,用于判断文件是否变更") + zvec_id = Column(String(256), comment="ZVec返回的向量ID(每个分块独立)") + zvec_response = Column(Text, comment="ZVec完整响应JSON") + status = Column(String(32), nullable=False, default="success", comment="向量化状态:success/failed/pending") + error_message = Column(String(500), comment="错误信息") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + __table_args__ = ( + Index("idx_project_file", "project_id", "file_path"), + Index("idx_project_file_chunk", "project_id", "file_path", "chunk_index"), + ) + + def __repr__(self): + return ( + f"" + ) diff --git a/backend/app/models/git_repo.py b/backend/app/models/git_repo.py new file mode 100644 index 0000000..ebf1ca4 --- /dev/null +++ b/backend/app/models/git_repo.py @@ -0,0 +1,30 @@ +""" +项目Git仓库模型 +""" +from sqlalchemy import Column, BigInteger, String, Integer, DateTime, SmallInteger, ForeignKey +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship +from app.core.database import Base + + +class ProjectGitRepo(Base): + """项目Git仓库表模型""" + + __tablename__ = "project_git_repos" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="ID") + project_id = Column(BigInteger, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True, comment="项目ID") + name = Column(String(50), nullable=False, comment="仓库别名") + repo_url = Column(String(255), nullable=False, comment="Git仓库地址") + branch = Column(String(50), default="main", comment="Git分支") + username = Column(String(100), comment="Git用户名") + token = Column(String(255), comment="Git访问令牌/密码") + is_default = Column(SmallInteger, default=0, comment="是否默认仓库") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + # 关系 + # project = relationship("Project", back_populates="git_repos") + + def __repr__(self): + return f"" diff --git a/backend/app/models/llm_model_config.py b/backend/app/models/llm_model_config.py new file mode 100644 index 0000000..1dc2bab --- /dev/null +++ b/backend/app/models/llm_model_config.py @@ -0,0 +1,63 @@ +""" +LLM 模型配置模型 +""" +from sqlalchemy import Column, BigInteger, String, Integer, DateTime, Boolean, JSON +from sqlalchemy.sql import func + +from app.core.database import Base + + +class LLMModelConfig(Base): + """大模型配置表模型""" + + __tablename__ = "llm_model_config" + + config_id = Column(BigInteger, primary_key=True, autoincrement=True, comment="配置ID") + model_code = Column(String(128), nullable=False, unique=True, index=True, comment="模型编码") + model_name = Column(String(255), nullable=False, comment="模型名称") + model_type = Column(String(32), nullable=False, default="chat", index=True, comment="模型类型: chat/embedding") + provider = Column(String(64), comment="模型提供方") + endpoint_url = Column(String(512), comment="接口地址") + api_key = Column(String(512), comment="API Key") + llm_model_name = Column(String(128), nullable=False, comment="模型名称/部署名") + llm_timeout = Column(Integer, nullable=False, default=120, comment="超时时间(秒)") + type_config = Column(JSON, nullable=False, default=dict, comment="模型类型差异参数") + description = Column(String(500), comment="描述") + is_active = Column(Boolean, nullable=False, default=True, index=True, comment="是否启用") + is_default = Column(Boolean, nullable=False, default=False, comment="是否默认") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + def _type_config_value(self, key, default=None): + return (self.type_config or {}).get(key, default) + + @property + def llm_temperature(self): + return self._type_config_value("temperature", 0.70) + + @property + def llm_top_p(self): + return self._type_config_value("top_p", 0.90) + + @property + def llm_max_tokens(self): + return self._type_config_value("max_tokens", 8192) + + @property + def llm_system_prompt(self): + return self._type_config_value("system_prompt") + + @property + def embedding_dimension(self): + return self._type_config_value("dimension") + + @property + def chunk_size(self): + return self._type_config_value("chunk_size", 800) + + @property + def chunk_overlap(self): + return self._type_config_value("chunk_overlap", 150) + + def __repr__(self): + return f"" diff --git a/backend/app/models/log.py b/backend/app/models/log.py new file mode 100644 index 0000000..428e26e --- /dev/null +++ b/backend/app/models/log.py @@ -0,0 +1,28 @@ +""" +操作日志模型 +""" +from sqlalchemy import Column, BigInteger, String, Integer, DateTime, SmallInteger, Text +from sqlalchemy.sql import func +from app.core.database import Base + + +class OperationLog(Base): + """操作日志表模型""" + + __tablename__ = "operation_logs" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="日志ID") + user_id = Column(BigInteger, index=True, comment="操作用户ID") + username = Column(String(50), comment="用户名") + operation_type = Column(String(50), nullable=False, comment="操作类型") + resource_type = Column(String(50), nullable=False, index=True, comment="资源类型") + resource_id = Column(BigInteger, index=True, comment="资源ID") + detail = Column(Text, comment="操作详情(JSON)") + ip_address = Column(String(50), comment="IP地址") + user_agent = Column(String(500), comment="用户代理") + status = Column(SmallInteger, default=1, comment="状态:0-失败 1-成功") + error_message = Column(Text, comment="错误信息") + created_at = Column(DateTime, server_default=func.now(), index=True, comment="操作时间") + + def __repr__(self): + return f"" diff --git a/backend/app/models/mcp_bot.py b/backend/app/models/mcp_bot.py new file mode 100644 index 0000000..b7f4ef4 --- /dev/null +++ b/backend/app/models/mcp_bot.py @@ -0,0 +1,25 @@ +""" +MCP bot credential model. +""" +from sqlalchemy import Column, BigInteger, String, DateTime, SmallInteger +from sqlalchemy.sql import func + +from app.core.database import Base + + +class MCPBot(Base): + """Stores MCP access credentials mapped to a NexDocs user.""" + + __tablename__ = "mcp_bots" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="Bot credential ID") + user_id = Column(BigInteger, nullable=False, unique=True, index=True, comment="Owner user ID") + bot_id = Column(String(64), nullable=False, unique=True, index=True, comment="External MCP bot id") + bot_secret = Column(String(255), nullable=False, comment="External MCP bot secret") + status = Column(SmallInteger, default=1, index=True, comment="Status: 0-disabled 1-enabled") + last_used_at = Column(DateTime, comment="Last successful MCP access time") + created_at = Column(DateTime, server_default=func.now(), comment="Created at") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="Updated at") + + def __repr__(self): + return f"" diff --git a/backend/app/models/menu.py b/backend/app/models/menu.py new file mode 100644 index 0000000..71eaed9 --- /dev/null +++ b/backend/app/models/menu.py @@ -0,0 +1,44 @@ +""" +菜单模型 +""" +from sqlalchemy import Column, BigInteger, String, Integer, DateTime, SmallInteger +from sqlalchemy.sql import func +from app.core.database import Base + + +class SystemMenu(Base): + """系统菜单表模型""" + + __tablename__ = "system_menus" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="菜单ID") + parent_id = Column(BigInteger, default=0, comment="父菜单ID(0表示根菜单)") + menu_name = Column(String(50), nullable=False, comment="菜单名称") + menu_code = Column(String(50), nullable=False, unique=True, index=True, comment="菜单编码") + menu_type = Column(SmallInteger, nullable=False, comment="菜单类型:1-目录 2-菜单 3-按钮/权限点") + path = Column(String(255), comment="路由路径") + component = Column(String(255), comment="组件路径") + icon = Column(String(100), comment="图标") + sort_order = Column(Integer, default=0, comment="排序号") + visible = Column(SmallInteger, default=1, comment="是否可见:0-隐藏 1-显示") + status = Column(SmallInteger, default=1, index=True, comment="状态:0-禁用 1-启用") + permission = Column(String(100), comment="权限字符串") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + def __repr__(self): + return f"" + + +class RoleMenu(Base): + """角色菜单授权表模型""" + + __tablename__ = "role_menus" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="关联ID") + role_id = Column(BigInteger, nullable=False, index=True, comment="角色ID") + menu_id = Column(BigInteger, nullable=False, index=True, comment="菜单ID") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + + def __repr__(self): + return f"" diff --git a/backend/app/models/notification.py b/backend/app/models/notification.py new file mode 100644 index 0000000..3a9f20b --- /dev/null +++ b/backend/app/models/notification.py @@ -0,0 +1,26 @@ +""" +通知模型 +""" +from sqlalchemy import Column, BigInteger, String, DateTime, SmallInteger, Text, ForeignKey +from sqlalchemy.sql import func +from app.core.database import Base + + +class Notification(Base): + """用户通知表模型""" + + __tablename__ = "notifications" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="通知ID") + user_id = Column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, comment="接收用户ID") + type = Column(String(20), default="info", comment="类型:info, success, warning, error") + category = Column(String(50), default="system", comment="分类:system, project, collaboration") + title = Column(String(200), nullable=False, comment="标题") + content = Column(Text, comment="内容") + link = Column(String(255), comment="跳转链接") + is_read = Column(SmallInteger, default=0, comment="是否已读:0-未读 1-已读") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + read_at = Column(DateTime, comment="阅读时间") + + def __repr__(self): + return f"" diff --git a/backend/app/models/project.py b/backend/app/models/project.py new file mode 100644 index 0000000..e92ed94 --- /dev/null +++ b/backend/app/models/project.py @@ -0,0 +1,59 @@ +""" +项目模型 +""" +from sqlalchemy import Column, BigInteger, String, Integer, DateTime, SmallInteger, Enum +from sqlalchemy.sql import func +from app.core.database import Base +import enum + + +class ProjectMemberRole(str, enum.Enum): + """项目成员角色枚举""" + ADMIN = "admin" + EDITOR = "editor" + VIEWER = "viewer" + + +class Project(Base): + """项目表模型""" + + __tablename__ = "projects" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="项目ID") + name = Column(String(100), nullable=False, index=True, comment="项目名称") + description = Column(String(500), comment="项目描述") + storage_key = Column(String(36), nullable=False, unique=True, comment="磁盘存储UUID") + owner_id = Column(BigInteger, nullable=False, index=True, comment="项目所有者ID") + is_public = Column(SmallInteger, default=0, comment="是否公开:0-私有 1-公开") + is_template = Column(SmallInteger, default=0, comment="是否模板项目:0-否 1-是") + status = Column(SmallInteger, default=1, index=True, comment="状态:0-归档 1-活跃") + cover_image = Column(String(255), comment="封面图") + sort_order = Column(Integer, default=0, comment="排序号") + visit_count = Column(Integer, default=0, comment="访问次数") + access_pass = Column(String(100), comment="访问密码(用于分享链接)") + created_at = Column(DateTime, server_default=func.now(), index=True, comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + def __repr__(self): + return f"" + + +class ProjectMember(Base): + """项目成员表模型""" + + __tablename__ = "project_members" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="成员ID") + project_id = Column(BigInteger, nullable=False, index=True, comment="项目ID") + user_id = Column(BigInteger, nullable=False, index=True, comment="用户ID") + role = Column( + String(20), + default="viewer", + index=True, + comment="项目角色: admin/editor/viewer" + ) + invited_by = Column(BigInteger, comment="邀请人ID") + joined_at = Column(DateTime, server_default=func.now(), comment="加入时间") + + def __repr__(self): + return f"" diff --git a/backend/app/models/project_vectorization_task.py b/backend/app/models/project_vectorization_task.py new file mode 100644 index 0000000..261ddb8 --- /dev/null +++ b/backend/app/models/project_vectorization_task.py @@ -0,0 +1,37 @@ +""" +项目向量化任务模型 +""" +from sqlalchemy import BigInteger, Column, DateTime, Index, Integer, String, Text +from sqlalchemy.sql import func + +from app.core.database import Base + + +class ProjectVectorizationTask(Base): + """项目向量化后台任务表""" + + __tablename__ = "project_vectorization_task" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="任务ID") + task_id = Column(String(64), nullable=False, unique=True, index=True, comment="任务唯一标识") + project_id = Column(BigInteger, nullable=False, index=True, comment="项目ID") + user_id = Column(BigInteger, nullable=False, index=True, comment="触发用户ID") + task_type = Column(String(32), nullable=False, comment="任务类型:incremental/full") + status = Column(String(32), nullable=False, default="pending", index=True, comment="任务状态:pending/running/success/failed") + total = Column(Integer, nullable=False, default=0, comment="文件总数") + processed = Column(Integer, nullable=False, default=0, comment="处理成功数") + skipped = Column(Integer, nullable=False, default=0, comment="跳过数") + failed = Column(Integer, nullable=False, default=0, comment="失败数") + error_message = Column(Text, comment="错误信息") + started_at = Column(DateTime, comment="开始时间") + finished_at = Column(DateTime, comment="完成时间") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + __table_args__ = ( + Index("idx_vector_task_project_status", "project_id", "status"), + Index("idx_vector_task_created_at", "created_at"), + ) + + def __repr__(self): + return f"" diff --git a/backend/app/models/role.py b/backend/app/models/role.py new file mode 100644 index 0000000..7fdbb58 --- /dev/null +++ b/backend/app/models/role.py @@ -0,0 +1,38 @@ +""" +角色模型 +""" +from sqlalchemy import Column, BigInteger, String, DateTime, SmallInteger +from sqlalchemy.sql import func +from app.core.database import Base + + +class Role(Base): + """角色表模型""" + + __tablename__ = "roles" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="角色ID") + role_name = Column(String(50), nullable=False, unique=True, comment="角色名称") + role_code = Column(String(50), nullable=False, unique=True, index=True, comment="角色编码") + description = Column(String(255), comment="角色描述") + status = Column(SmallInteger, default=1, index=True, comment="状态:0-禁用 1-启用") + is_system = Column(SmallInteger, default=0, comment="是否系统角色:0-否 1-是") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + def __repr__(self): + return f"" + + +class UserRole(Base): + """用户角色关联表模型""" + + __tablename__ = "user_roles" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="关联ID") + user_id = Column(BigInteger, nullable=False, index=True, comment="用户ID") + role_id = Column(BigInteger, nullable=False, index=True, comment="角色ID") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + + def __repr__(self): + return f"" diff --git a/backend/app/models/share.py b/backend/app/models/share.py new file mode 100644 index 0000000..368ffd3 --- /dev/null +++ b/backend/app/models/share.py @@ -0,0 +1,27 @@ +""" +分享链接模型 +""" +from sqlalchemy import Column, BigInteger, String, DateTime, SmallInteger +from sqlalchemy.sql import func + +from app.core.database import Base + + +class ShareLink(Base): + """项目分享/文件分享链接""" + + __tablename__ = "share_links" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="分享ID") + project_id = Column(BigInteger, nullable=False, index=True, comment="项目ID") + share_type = Column(String(20), nullable=False, index=True, comment="分享类型: project/file") + share_code = Column(String(64), nullable=False, unique=True, index=True, comment="公开分享码") + file_path = Column(String(500), comment="文件路径,仅文件分享使用") + access_pass = Column(String(100), comment="访问密码") + created_by = Column(BigInteger, index=True, comment="创建人ID") + status = Column(SmallInteger, default=1, index=True, comment="状态:0-禁用 1-启用") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + def __repr__(self): + return f"" diff --git a/backend/app/models/user.py b/backend/app/models/user.py new file mode 100644 index 0000000..fc3e75a --- /dev/null +++ b/backend/app/models/user.py @@ -0,0 +1,29 @@ +""" +用户模型 +""" +from sqlalchemy import Column, BigInteger, String, Integer, DateTime, SmallInteger +from sqlalchemy.sql import func +from app.core.database import Base + + +class User(Base): + """用户表模型""" + + __tablename__ = "users" + + id = Column(BigInteger, primary_key=True, autoincrement=True, comment="用户ID") + username = Column(String(50), nullable=False, unique=True, index=True, comment="用户名") + password_hash = Column(String(255), nullable=False, comment="密码哈希") + nickname = Column(String(50), comment="昵称") + email = Column(String(100), index=True, comment="邮箱") + phone = Column(String(20), comment="手机号") + avatar = Column(String(255), comment="头像URL") + status = Column(SmallInteger, default=1, index=True, comment="状态:0-禁用 1-启用") + is_superuser = Column(SmallInteger, default=0, comment="是否超级管理员:0-否 1-是") + last_login_at = Column(DateTime, comment="最后登录时间") + last_login_ip = Column(String(50), comment="最后登录IP") + created_at = Column(DateTime, server_default=func.now(), comment="创建时间") + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间") + + def __repr__(self): + return f"" diff --git a/backend/requirements.txt b/backend/requirements.txt index 63a9295..a2329be 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -49,3 +49,4 @@ sentence-transformers>=3.0,<6 weasyprint==61.2 pydyf<0.11.0 mcp==1.26.0 +zvec==0.6.0