diff --git a/.cursor/rules/db-migrations.mdc b/.cursor/rules/db-migrations.mdc index 2db3e42112..1639c95d81 100644 --- a/.cursor/rules/db-migrations.mdc +++ b/.cursor/rules/db-migrations.mdc @@ -43,4 +43,4 @@ DROP TABLE "old_table"; CREATE INDEX "users_email_idx" ON "users" ("email"); ``` -**Important**: After modifying migration SQL (e.g., adding `IF NOT EXISTS` clauses), run `bun run db:generate-client` to update the hash in `packages/database/src/core/migrations.json`. +**Important**: After modifying migration SQL (e.g., adding `IF NOT EXISTS` clauses), run `bun run db:generate:client` to update the hash in `packages/database/src/core/migrations.json`. diff --git a/.cursor/rules/debug-usage.mdc b/.cursor/rules/debug-usage.mdc index 76a1197c36..644ebd4f7a 100644 --- a/.cursor/rules/debug-usage.mdc +++ b/.cursor/rules/debug-usage.mdc @@ -3,9 +3,10 @@ description: 包含添加 console.log 日志请求时 globs: alwaysApply: false --- + # Debug 包使用指南 -本项目使用 [debug](mdc:https:/github.com/debug-js/debug) 包进行调试日志记录。使用此规则来确保团队成员统一调试日志格式。 +本项目使用 `debug` 包进行调试日志记录。使用此规则来确保团队成员统一调试日志格式。 ## 基本用法 @@ -15,14 +16,14 @@ alwaysApply: false import debug from 'debug'; ``` -2. 创建一个命名空间的日志记录器: +1. 创建一个命名空间的日志记录器: ```typescript // 格式: lobe:[模块]:[子模块] const log = debug('lobe-[模块名]:[子模块名]'); ``` -3. 使用日志记录器: +1. 使用日志记录器: ```typescript log('简单消息'); @@ -46,7 +47,7 @@ log('格式化数字: %d', number); ## 示例 -查看 [market/index.ts](mdc:src/server/routers/edge/market/index.ts) 中的使用示例: +查看 `src/server/routers/edge/market/index.ts` 中的使用示例: ```typescript import debug from 'debug'; @@ -63,8 +64,9 @@ log('getAgent input: %O', input); ### 在浏览器中 在控制台执行: + ```javascript -localStorage.debug = 'lobe-*' +localStorage.debug = 'lobe-*'; ``` ### 在 Node.js 环境中 diff --git a/.cursor/rules/desktop-controller-tests.mdc b/.cursor/rules/desktop-controller-tests.mdc index cafa486ba2..f4c4f08ed8 100644 --- a/.cursor/rules/desktop-controller-tests.mdc +++ b/.cursor/rules/desktop-controller-tests.mdc @@ -3,13 +3,14 @@ description: 桌面端测试 globs: alwaysApply: false --- + # 桌面端控制器单元测试指南 ## 测试框架与目录结构 LobeChat 桌面端使用 Vitest 作为测试框架。控制器的单元测试应放置在对应控制器文件同级的 `__tests__` 目录下,并以原控制器文件名加 `.test.ts` 作为文件名。 -``` +```plaintext apps/desktop/src/main/controllers/ ├── __tests__/ │ ├── index.test.ts diff --git a/.cursor/rules/desktop-feature-implementation.mdc b/.cursor/rules/desktop-feature-implementation.mdc index b80137b789..04c796e924 100644 --- a/.cursor/rules/desktop-feature-implementation.mdc +++ b/.cursor/rules/desktop-feature-implementation.mdc @@ -3,7 +3,8 @@ description: 当要做 electron 相关工作时 globs: alwaysApply: false --- -**桌面端新功能实现指南** + +# 桌面端新功能实现指南 ## 桌面端应用架构概述 @@ -26,6 +27,7 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架 ### 1. 确定功能需求与设计 首先确定新功能的需求和设计,包括: + - 功能描述和用例 - 是否需要系统级API(如文件系统、网络等) - UI/UX设计(如必要) @@ -64,9 +66,10 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架 ```typescript // src/services/electron/newFeatureService.ts - import { ensureElectronIpc } from '@/utils/electron/ipc'; import type { NewFeatureParams } from '@lobechat/electron-client-ipc'; + import { ensureElectronIpc } from '@/utils/electron/ipc'; + const ipc = ensureElectronIpc(); export const newFeatureService = async (params: NewFeatureParams) => { @@ -84,7 +87,7 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架 ### 5. 如果是新增内置工具,遵循工具实现流程 -参考 [desktop-local-tools-implement.mdc](mdc:desktop-local-tools-implement.mdc) 了解更多关于添加内置工具的详细步骤。 +参考 `desktop-local-tools-implement.mdc` 了解更多关于添加内置工具的详细步骤。 ### 6. 添加测试 @@ -120,12 +123,13 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架 ```typescript // apps/desktop/src/main/controllers/NotificationCtr.ts -import { Notification } from 'electron'; -import { ControllerModule, IpcMethod } from '@/controllers'; import type { DesktopNotificationResult, ShowDesktopNotificationParams, } from '@lobechat/electron-client-ipc'; +import { Notification } from 'electron'; + +import { ControllerModule, IpcMethod } from '@/controllers'; export default class NotificationCtr extends ControllerModule { static override readonly groupName = 'notification'; diff --git a/.cursor/rules/desktop-local-tools-implement.mdc b/.cursor/rules/desktop-local-tools-implement.mdc index e513496ab5..8eba043fec 100644 --- a/.cursor/rules/desktop-local-tools-implement.mdc +++ b/.cursor/rules/desktop-local-tools-implement.mdc @@ -3,78 +3,79 @@ description: globs: alwaysApply: false --- + **新增桌面端工具流程:** -1. **定义工具接口 (Manifest):** - * **文件:** `src/tools/[tool_category]/index.ts` (例如: `src/tools/local-files/index.ts`) - * **操作:** - * 在 `ApiName` 对象(例如 `LocalFilesApiName`)中添加一个新的、唯一的 API 名称。 - * 在 `Manifest` 对象(例如 `LocalFilesManifest`)的 `api` 数组中,新增一个对象来定义新工具的接口。 - * **关键字段:** - * `name`: 使用上一步定义的 API 名称。 - * `description`: 清晰描述工具的功能,供 Agent 理解和向用户展示。 - * `parameters`: 使用 JSON Schema 定义工具所需的输入参数。 - * `type`: 通常是 'object'。 - * `properties`: 定义每个参数的名称、`description`、`type` (string, number, boolean, array, etc.),使用英文。 - * `required`: 一个字符串数组,列出必须提供的参数名称。 +1. **定义工具接口 (Manifest):** + - **文件:** `src/tools/[tool_category]/index.ts` (例如: `src/tools/local-files/index.ts`) + - **操作:** + - 在 `ApiName` 对象(例如 `LocalFilesApiName`)中添加一个新的、唯一的 API 名称。 + - 在 `Manifest` 对象(例如 `LocalFilesManifest`)的 `api` 数组中,新增一个对象来定义新工具的接口。 + - **关键字段:** + - `name`: 使用上一步定义的 API 名称。 + - `description`: 清晰描述工具的功能,供 Agent 理解和向用户展示。 + - `parameters`: 使用 JSON Schema 定义工具所需的输入参数。 + - `type`: 通常是 'object'。 + - `properties`: 定义每个参数的名称、`description`、`type` (string, number, boolean, array, etc.),使用英文。 + - `required`: 一个字符串数组,列出必须提供的参数名称。 -2. **定义相关类型:** - * **文件 1:** `packages/electron-client-ipc/src/types.ts` (或类似的共享 IPC 类型文件) - * **操作:** 定义传递给 IPC 事件的参数类型接口 (例如: `RenameLocalFileParams`, `MoveLocalFileParams`)。确保与 Manifest 中定义的 `parameters` 一致。 - * **文件 2:** `src/tools/[tool_category]/type.ts` (例如: `src/tools/local-files/type.ts`) - * **操作:** 定义此工具执行后,存储在前端 Zustand Store 中的状态类型接口 (例如: `LocalRenameFileState`, `LocalMoveFileState`)。这通常包含操作结果(成功/失败)、错误信息以及相关数据(如旧路径、新路径等)。 +2. **定义相关类型:** + - **文件 1:** `packages/electron-client-ipc/src/types.ts` (或类似的共享 IPC 类型文件) + - **操作:** 定义传递给 IPC 事件的参数类型接口 (例如: `RenameLocalFileParams`, `MoveLocalFileParams`)。确保与 Manifest 中定义的 `parameters` 一致。 + - **文件 2:** `src/tools/[tool_category]/type.ts` (例如: `src/tools/local-files/type.ts`) + - **操作:** 定义此工具执行后,存储在前端 Zustand Store 中的状态类型接口 (例如: `LocalRenameFileState`, `LocalMoveFileState`)。这通常包含操作结果(成功/失败)、错误信息以及相关数据(如旧路径、新路径等)。 -3. **实现前端状态管理 (Store Action):** - * **文件:** `src/store/chat/slices/builtinTool/actions/[tool_category].ts` (例如: `src/store/chat/slices/builtinTool/actions/localFile.ts`) - * **操作:** - * 导入在步骤 2 中定义的 IPC 参数类型和状态类型。 - * 在 Action 接口 (例如: `LocalFileAction`) 中添加新 Action 的方法签名,使用对应的 IPC 参数类型。 - * 在 `createSlice` (例如: `localFileSlice`) 中实现该 Action 方法: - * 接收 `id` (消息 ID) 和 `params` (符合 IPC 参数类型)。 - * 设置加载状态 (`toggleLocalFileLoading(id, true)`)。 - * 调用对应的 `Service` 层方法 (见步骤 4),传递 `params`。 - * 使用 `try...catch` 处理 `Service` 调用可能发生的错误。 - * **成功时:** - * 调用 `updatePluginState(id, {...})` 更新插件状态,使用步骤 2 中定义的状态类型。 - * 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,通常包含成功确认信息。 - * **失败时:** - * 记录错误 (`console.error`)。 - * 调用 `updatePluginState(id, {...})` 更新插件状态,包含错误信息。 - * 调用 `internal_updateMessagePluginError(id, {...})` 设置消息的错误状态。 - * 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,包含错误信息。 - * 在 `finally` 块中取消加载状态 (`toggleLocalFileLoading(id, false)`)。 - * 返回操作是否成功 (`boolean`)。 +3. **实现前端状态管理 (Store Action):** + - **文件:** `src/store/chat/slices/builtinTool/actions/[tool_category].ts` (例如: `src/store/chat/slices/builtinTool/actions/localFile.ts`) + - **操作:** + - 导入在步骤 2 中定义的 IPC 参数类型和状态类型。 + - 在 Action 接口 (例如: `LocalFileAction`) 中添加新 Action 的方法签名,使用对应的 IPC 参数类型。 + - 在 `createSlice` (例如: `localFileSlice`) 中实现该 Action 方法: + - 接收 `id` (消息 ID) 和 `params` (符合 IPC 参数类型)。 + - 设置加载状态 (`toggleLocalFileLoading(id, true)`)。 + - 调用对应的 `Service` 层方法 (见步骤 4),传递 `params`。 + - 使用 `try...catch` 处理 `Service` 调用可能发生的错误。 + - **成功时:** + - 调用 `updatePluginState(id, {...})` 更新插件状态,使用步骤 2 中定义的状态类型。 + - 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,通常包含成功确认信息。 + - **失败时:** + - 记录错误 (`console.error`)。 + - 调用 `updatePluginState(id, {...})` 更新插件状态,包含错误信息。 + - 调用 `internal_updateMessagePluginError(id, {...})` 设置消息的错误状态。 + - 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,包含错误信息。 + - 在 `finally` 块中取消加载状态 (`toggleLocalFileLoading(id, false)`)。 + - 返回操作是否成功 (`boolean`)。 -4. **实现 Service 层 (调用 IPC):** - * **文件:** `src/services/electron/[tool_category]Service.ts` (例如: `src/services/electron/localFileService.ts`) - * **操作:** - * 导入在步骤 2 中定义的 IPC 参数类型。 - * 添加一个新的 `async` 方法,方法名通常与 Action 名称对应 (例如: `renameLocalFile`)。 - * 方法接收 `params` (符合 IPC 参数类型)。 - * 通过 `ensureElectronIpc()` 获取 IPC 代理 (`const ipc = ensureElectronIpc();`),调用与 Manifest 中 `name` 字段匹配的链式方法,并将 `params` 传递过去。 - * 定义方法的返回类型,通常是 `Promise<{ success: boolean; error?: string }>`,与后端 Controller 返回的结构一致。 +4. **实现 Service 层 (调用 IPC):** + - **文件:** `src/services/electron/[tool_category]Service.ts` (例如: `src/services/electron/localFileService.ts`) + - **操作:** + - 导入在步骤 2 中定义的 IPC 参数类型。 + - 添加一个新的 `async` 方法,方法名通常与 Action 名称对应 (例如: `renameLocalFile`)。 + - 方法接收 `params` (符合 IPC 参数类型)。 + - 通过 `ensureElectronIpc()` 获取 IPC 代理 (`const ipc = ensureElectronIpc();`),调用与 Manifest 中 `name` 字段匹配的链式方法,并将 `params` 传递过去。 + - 定义方法的返回类型,通常是 `Promise<{ success: boolean; error?: string }>`,与后端 Controller 返回的结构一致。 -5. **实现后端逻辑 (Controller / IPC Handler):** - * **文件:** `apps/desktop/src/main/controllers/[ToolName]Ctr.ts` (例如: `apps/desktop/src/main/controllers/LocalFileCtr.ts`) - * **操作:** - * 导入 Node.js 相关模块 (`fs`, `path` 等) 和 IPC 相关依赖 (`ControllerModule`, `IpcMethod`、参数类型等)。 - * 添加一个新的 `async` 方法,方法名通常以 `handle` 开头 (例如: `handleRenameFile`)。 - * 使用 `@IpcMethod()` 装饰器将此方法注册为对应 IPC 事件的处理器,确保方法名与 Manifest 中的 `name` 以及 Service 层的链式调用一致。 - * 方法的参数应解构自 Service 层传递过来的对象,类型与步骤 2 中定义的 IPC 参数类型匹配。 - * 实现核心业务逻辑: - * 进行必要的输入验证。 - * 执行文件系统操作或其他后端任务 (例如: `fs.promises.rename`)。 - * 使用 `try...catch` 捕获执行过程中的错误。 - * 处理特定错误码 (`error.code`) 以提供更友好的错误消息。 - * 返回一个包含 `success` (boolean) 和可选 `error` (string) 字段的对象。 +5. **实现后端逻辑 (Controller / IPC Handler):** + - **文件:** `apps/desktop/src/main/controllers/[ToolName]Ctr.ts` (例如: `apps/desktop/src/main/controllers/LocalFileCtr.ts`) + - **操作:** + - 导入 Node.js 相关模块 (`fs`, `path` 等) 和 IPC 相关依赖 (`ControllerModule`, `IpcMethod`、参数类型等)。 + - 添加一个新的 `async` 方法,方法名通常以 `handle` 开头 (例如: `handleRenameFile`)。 + - 使用 `@IpcMethod()` 装饰器将此方法注册为对应 IPC 事件的处理器,确保方法名与 Manifest 中的 `name` 以及 Service 层的链式调用一致。 + - 方法的参数应解构自 Service 层传递过来的对象,类型与步骤 2 中定义的 IPC 参数类型匹配。 + - 实现核心业务逻辑: + - 进行必要的输入验证。 + - 执行文件系统操作或其他后端任务 (例如: `fs.promises.rename`)。 + - 使用 `try...catch` 捕获执行过程中的错误。 + - 处理特定错误码 (`error.code`) 以提供更友好的错误消息。 + - 返回一个包含 `success` (boolean) 和可选 `error` (string) 字段的对象。 -6. **更新 Agent 文档 (System Role):** - * **文件:** `src/tools/[tool_category]/systemRole.ts` (例如: `src/tools/local-files/systemRole.ts`) - * **操作:** - * 在 `` 部分添加新工具的简要描述。 - * 如果需要,更新 ``。 - * 在 `` 部分为新工具添加详细的使用说明,解释其参数、用途和预期行为。 - * 如有必要,更新 ``。 - * 如有必要(例如工具返回了新的数据结构或路径),更新 `` 中的示例。 +6. **更新 Agent 文档 (System Role):** + - **文件:** `src/tools/[tool_category]/systemRole.ts` (例如: `src/tools/local-files/systemRole.ts`) + - **操作:** + - 在 `` 部分添加新工具的简要描述。 + - 如果需要,更新 ``。 + - 在 `` 部分为新工具添加详细的使用说明,解释其参数、用途和预期行为。 + - 如有必要,更新 ``。 + - 如有必要(例如工具返回了新的数据结构或路径),更新 `` 中的示例。 通过遵循这些步骤,可以系统地将新的桌面端工具集成到 LobeChat 的插件系统中。 diff --git a/.cursor/rules/desktop-menu-configuration.mdc b/.cursor/rules/desktop-menu-configuration.mdc index 2da65e4c8b..9998d32aa0 100644 --- a/.cursor/rules/desktop-menu-configuration.mdc +++ b/.cursor/rules/desktop-menu-configuration.mdc @@ -3,7 +3,8 @@ description: globs: alwaysApply: false --- -**桌面端菜单配置指南** + +# 桌面端菜单配置指南 ## 菜单系统概述 @@ -15,7 +16,7 @@ LobeChat 桌面应用有三种主要的菜单类型: ## 菜单相关文件结构 -``` +```plaintext apps/desktop/src/main/ ├── menus/ # 菜单定义 │ ├── appMenu.ts # 应用菜单配置 @@ -33,8 +34,9 @@ apps/desktop/src/main/ 应用菜单在 `apps/desktop/src/main/menus/appMenu.ts` 中定义: 1. **导入依赖** + ```typescript - import { app, BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions } from 'electron'; + import { BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions, app } from 'electron'; import { is } from 'electron-util'; ``` @@ -43,6 +45,7 @@ apps/desktop/src/main/ - 每个菜单项可以包含:label, accelerator (快捷键), role, submenu, click 等属性 3. **创建菜单工厂函数** + ```typescript export const createAppMenu = (win: BrowserWindow) => { const template = [ @@ -61,6 +64,7 @@ apps/desktop/src/main/ 上下文菜单通常在特定元素上右键点击时显示: 1. **在主进程中定义菜单模板** + ```typescript // apps/desktop/src/main/menus/contextMenu.ts export const createContextMenu = () => { @@ -73,6 +77,7 @@ apps/desktop/src/main/ ``` 2. **在适当的事件处理器中显示菜单** + ```typescript const menu = createContextMenu(); menu.popup(); @@ -83,11 +88,13 @@ apps/desktop/src/main/ 托盘菜单在 `TrayMenuCtr.ts` 中配置: 1. **创建托盘图标** + ```typescript this.tray = new Tray(trayIconPath); ``` 2. **定义托盘菜单** + ```typescript const contextMenu = Menu.buildFromTemplate([ { label: '显示主窗口', click: this.showMainWindow }, @@ -97,6 +104,7 @@ apps/desktop/src/main/ ``` 3. **设置托盘菜单** + ```typescript this.tray.setContextMenu(contextMenu); ``` @@ -106,11 +114,13 @@ apps/desktop/src/main/ 为菜单添加多语言支持: 1. **导入本地化工具** + ```typescript import { i18n } from '../locales'; ``` 2. **使用翻译函数** + ```typescript const template = [ { @@ -118,14 +128,13 @@ apps/desktop/src/main/ submenu: [ { label: i18n.t('menu.new'), click: createNew }, // ... - ] + ], }, // ... ]; ``` -3. **在语言切换时更新菜单** - 在 `MenuCtr.ts` 中监听语言变化事件并重新创建菜单 +3. **在语言切换时更新菜单** 在 `MenuCtr.ts` 中监听语言变化事件并重新创建菜单 ## 添加新菜单项流程 @@ -134,6 +143,7 @@ apps/desktop/src/main/ - 确定在菜单中的位置(主菜单项或子菜单项) 2. **定义菜单项** + ```typescript const newMenuItem: MenuItemConstructorOptions = { label: '新功能', @@ -141,12 +151,11 @@ apps/desktop/src/main/ click: (_, window) => { // 处理点击事件 if (window) window.webContents.send('trigger-new-feature'); - } + }, }; ``` -3. **添加到菜单模板** - 将新菜单项添加到相应的菜单模板中 +3. **添加到菜单模板** 将新菜单项添加到相应的菜单模板中 4. **对于与渲染进程交互的功能** - 使用 `window.webContents.send()` 发送 IPC 消息到渲染进程 @@ -157,6 +166,7 @@ apps/desktop/src/main/ 动态控制菜单项状态: 1. **保存对菜单项的引用** + ```typescript this.menuItems = {}; const menu = Menu.buildFromTemplate(template); @@ -164,6 +174,7 @@ apps/desktop/src/main/ ``` 2. **根据条件更新状态** + ```typescript updateMenuState(state) { if (this.menuItems.newFeature) { @@ -179,6 +190,7 @@ apps/desktop/src/main/ 2. **平台特定菜单** - 使用 `process.platform` 检查为不同平台提供不同菜单 + ```typescript if (process.platform === 'darwin') { template.unshift({ role: 'appMenu' }); diff --git a/.cursor/rules/desktop-window-management.mdc b/.cursor/rules/desktop-window-management.mdc index 1de0b8d12d..47b4417834 100644 --- a/.cursor/rules/desktop-window-management.mdc +++ b/.cursor/rules/desktop-window-management.mdc @@ -3,7 +3,8 @@ description: globs: alwaysApply: false --- -**桌面端窗口管理指南** + +# 桌面端窗口管理指南 ## 窗口管理概述 @@ -16,7 +17,7 @@ LobeChat 桌面应用使用 Electron 的 `BrowserWindow` 管理应用窗口。 ## 相关文件结构 -``` +```plaintext apps/desktop/src/main/ ├── appBrowsers.ts # 窗口管理的核心文件 ├── controllers/ @@ -63,6 +64,7 @@ export const createMainWindow = () => { 实现窗口状态持久化保存和恢复: 1. **保存窗口状态** + ```typescript const saveWindowState = (window: BrowserWindow) => { if (!window.isMinimized() && !window.isMaximized()) { @@ -80,6 +82,7 @@ export const createMainWindow = () => { ``` 2. **恢复窗口状态** + ```typescript const restoreWindowState = (window: BrowserWindow) => { const savedState = settings.get('windowState'); @@ -96,6 +99,7 @@ export const createMainWindow = () => { ``` 3. **监听窗口事件** + ```typescript window.on('close', () => saveWindowState(window)); window.on('moved', () => saveWindowState(window)); @@ -107,6 +111,7 @@ export const createMainWindow = () => { 对于需要多窗口支持的功能: 1. **跟踪窗口** + ```typescript export class WindowManager { private windows: Map = new Map(); @@ -133,6 +138,7 @@ export const createMainWindow = () => { ``` 2. **窗口间通信** + ```typescript // 从一个窗口向另一个窗口发送消息 sendMessageToWindow(targetWindowId, channel, data) { @@ -148,9 +154,11 @@ export const createMainWindow = () => { 通过 IPC 实现窗口操作: 1. **在主进程中注册 IPC 处理器** + ```typescript // apps/desktop/src/main/controllers/BrowserWindowsCtr.ts import { BrowserWindow } from 'electron'; + import { ControllerModule, IpcMethod } from '@/controllers'; export default class BrowserWindowsCtr extends ControllerModule { @@ -178,10 +186,12 @@ export const createMainWindow = () => { } } ``` + - `@IpcMethod()` 根据控制器的 `groupName` 自动将方法映射为 `windows.minimizeWindow` 形式的通道名称。 - 控制器需继承 `ControllerModule`,并在 `controllers/registry.ts` 中通过 `controllerIpcConstructors` 注册,便于类型生成。 2. **在渲染进程中调用** + ```typescript // src/services/electron/windowService.ts import { ensureElectronIpc } from '@/utils/electron/ipc'; @@ -194,6 +204,7 @@ export const createMainWindow = () => { close: () => ipc.windows.closeWindow(), }; ``` + - `ensureElectronIpc()` 会基于 `DesktopIpcServices` 运行时生成 Proxy,并通过 `window.electronAPI.invoke` 与主进程通信;不再直接使用 `dispatch`。 ### 5. 自定义窗口控制 (无边框窗口) @@ -201,6 +212,7 @@ export const createMainWindow = () => { 对于自定义窗口标题栏: 1. **创建无边框窗口** + ```typescript const window = new BrowserWindow({ frame: false, @@ -210,6 +222,7 @@ export const createMainWindow = () => { ``` 2. **在渲染进程中实现拖拽区域** + ```css /* CSS */ .titlebar { @@ -229,6 +242,7 @@ export const createMainWindow = () => { 2. **安全性** - 始终设置适当的 `webPreferences` 确保安全 + ```typescript webPreferences: { preload: path.join(__dirname, '../preload/index.js'), @@ -255,6 +269,7 @@ export const createMainWindow = () => { ```typescript // apps/desktop/src/main/controllers/BrowserWindowsCtr.ts import type { OpenSettingsWindowOptions } from '@lobechat/electron-client-ipc'; + import { ControllerModule, IpcMethod } from '@/controllers'; export default class BrowserWindowsCtr extends ControllerModule { diff --git a/.cursor/rules/drizzle-schema-style-guide.mdc b/.cursor/rules/drizzle-schema-style-guide.mdc index 545c935b37..29ff1db2ca 100644 --- a/.cursor/rules/drizzle-schema-style-guide.mdc +++ b/.cursor/rules/drizzle-schema-style-guide.mdc @@ -10,14 +10,14 @@ This document outlines the conventions and best practices for defining PostgreSQ ## Configuration -- Drizzle configuration is managed in [drizzle.config.ts](mdc:drizzle.config.ts) +- Drizzle configuration is managed in `drizzle.config.ts` - Schema files are located in the src/database/schemas/ directory - Migration files are output to `src/database/migrations/` - The project uses `postgresql` dialect with `strict: true` ## Helper Functions -Commonly used column definitions, especially for timestamps, are centralized in [src/database/schemas/\_helpers.ts](mdc:src/database/schemas/_helpers.ts): +Commonly used column definitions, especially for timestamps, are centralized in `src/database/schemas/_helpers.ts`: - `timestamptz(name: string)`: Creates a timestamp column with timezone - `createdAt()`, `updatedAt()`, `accessedAt()`: Helper functions for standard timestamp columns @@ -46,7 +46,7 @@ Commonly used column definitions, especially for timestamps, are centralized in ### Timestamps -- Consistently use the `...timestamps` spread from [\_helpers.ts](mdc:src/database/schemas/_helpers.ts) for `created_at`, `updated_at`, and `accessed_at` columns +- Consistently use the `...timestamps` spread from `_helpers.ts` for `created_at`, `updated_at`, and `accessed_at` columns ### Default Values @@ -74,12 +74,12 @@ Commonly used column definitions, especially for timestamps, are centralized in ## Relations -- Table relationships are defined centrally in [src/database/schemas/relations.ts](mdc:src/database/schemas/relations.ts) using the `relations()` utility from `drizzle-orm` +- Table relationships are defined centrally in `src/database/schemas/relations.ts` using the `relations()` utility from `drizzle-orm` ## Code Style & Structure -- **File Organization**: Each main database entity typically has its own schema file (e.g., [user.ts](mdc:src/database/schemas/user.ts), [agent.ts](mdc:src/database/schemas/agent.ts)) -- All schemas are re-exported from [src/database/schemas/index.ts](mdc:src/database/schemas/index.ts) +- **File Organization**: Each main database entity typically has its own schema file (e.g., `user.ts`, `agent.ts`) +- All schemas are re-exported from `src/database/schemas/index.ts` - **ESLint**: Files often start with `/* eslint-disable sort-keys-fix/sort-keys-fix */` - **Comments**: Use JSDoc-style comments to explain the purpose of tables and complex columns, fields that are self-explanatory do not require jsdoc explanations, such as id, user_id, etc. diff --git a/.cursor/rules/hotkey.mdc b/.cursor/rules/hotkey.mdc index 867d27499b..c803130067 100644 --- a/.cursor/rules/hotkey.mdc +++ b/.cursor/rules/hotkey.mdc @@ -1,6 +1,7 @@ --- alwaysApply: false --- + # 如何添加新的快捷键:开发者指南 本指南将带您一步步地向 LobeChat 添加一个新的快捷键功能。我们将通过一个完整示例,演示从定义到实现的整个过程。 diff --git a/.cursor/rules/i18n.mdc b/.cursor/rules/i18n.mdc index 6e70f1f788..295a777dab 100644 --- a/.cursor/rules/i18n.mdc +++ b/.cursor/rules/i18n.mdc @@ -37,6 +37,7 @@ export default { - `sync.status.ready` - Feature + group + status **Parameters:** Use `{{variableName}}` syntax + ```typescript 'alert.cloud.desc': '我们提供 {{credit}} 额度积分', ``` diff --git a/.cursor/rules/project-structure.mdc b/.cursor/rules/project-structure.mdc index 8e61b5383b..dd051d6890 100644 --- a/.cursor/rules/project-structure.mdc +++ b/.cursor/rules/project-structure.mdc @@ -1,6 +1,5 @@ --- -description: Project directory structure overview -alwaysApply: false +alwaysApply: true --- # LobeChat Project Structure @@ -27,6 +26,11 @@ lobe-chat/ │ ├── agent-runtime/ │ ├── builtin-agents/ │ ├── builtin-tool-*/ # builtin tool packages +│ ├── business/ # cloud-only business logic packages +│ │ ├── config/ +│ │ ├── const/ +│ │ └── model-runtime/ +│ ├── config/ │ ├── const/ │ ├── context-engine/ │ ├── conversation-flow/ @@ -36,6 +40,8 @@ lobe-chat/ │ │ ├── schemas/ │ │ └── repositories/ │ ├── desktop-bridge/ +│ ├── edge-config/ +│ ├── editor-runtime/ │ ├── electron-client-ipc/ │ ├── electron-server-ipc/ │ ├── fetch-sse/ @@ -46,7 +52,7 @@ lobe-chat/ │ │ └── src/ │ │ ├── core/ │ │ └── providers/ -│ ├── obervability-otel/ +│ ├── observability-otel/ │ ├── prompts/ │ ├── python-interpreter/ │ ├── ssrf-safe-fetch/ @@ -72,6 +78,10 @@ lobe-chat/ │ │ │ ├── onboarding/ │ │ │ └── router/ │ │ └── desktop/ +│ ├── business/ # cloud-only business logic (client/server) +│ │ ├── client/ +│ │ ├── locales/ +│ │ └── server/ │ ├── components/ │ ├── config/ │ ├── const/ @@ -130,6 +140,9 @@ lobe-chat/ - Repository (bff-queries): `packages/database/src/repositories` - Third-party Integrations: `src/libs` — analytics, oidc etc. - Builtin Tools: `src/tools`, `packages/builtin-tool-*` +- Business (cloud-only): Code specific to LobeHub cloud service, only expose empty interfaces for opens-source version. + - `src/business/*` + - `packages/business/*` ## Data Flow Architecture diff --git a/.cursor/rules/react.mdc b/.cursor/rules/react.mdc index 9811ceb03d..306852aeea 100644 --- a/.cursor/rules/react.mdc +++ b/.cursor/rules/react.mdc @@ -107,7 +107,7 @@ This project uses a **hybrid routing architecture**: Next.js App Router for stat | Router | oauth, reset-password, etc.) | src/app/[variants]/(auth)/ | +------------------+--------------------------------+--------------------------------+ | React Router | Main SPA features | BrowserRouter + Routes | -| DOM | (chat, discover, settings) | desktopRouter.config.tsx | +| DOM | (chat, community, settings) | desktopRouter.config.tsx | | | | mobileRouter.config.tsx | +------------------+--------------------------------+--------------------------------+ ``` @@ -122,16 +122,16 @@ This project uses a **hybrid routing architecture**: Next.js App Router for stat ### Router Utilities ```tsx -import { dynamicElement, redirectElement, ErrorBoundary, RouteConfig } from '@/utils/router'; +import { ErrorBoundary, RouteConfig, dynamicElement, redirectElement } from '@/utils/router'; // Lazy load a page component -element: dynamicElement(() => import('./chat'), 'Desktop > Chat') +element: dynamicElement(() => import('./chat'), 'Desktop > Chat'); // Create a redirect -element: redirectElement('/settings/profile') +element: redirectElement('/settings/profile'); // Error boundary for route -errorElement: +errorElement: ; ``` ### Adding New Routes @@ -142,6 +142,18 @@ errorElement: ### Navigation +**Important**: For SPA pages (React Router DOM routes), use `Link` from `react-router-dom`, NOT from `next/link`. + +```tsx +// ❌ Wrong - next/link in SPA pages +import Link from 'next/link'; +Home + +// ✅ Correct - react-router-dom Link in SPA pages +import { Link } from 'react-router-dom'; +Home +``` + ```tsx // In components - use react-router-dom hooks import { useNavigate, useParams } from 'react-router-dom'; diff --git a/.cursor/rules/recent-data-usage.mdc b/.cursor/rules/recent-data-usage.mdc index 2e75f00b41..981e94f97e 100644 --- a/.cursor/rules/recent-data-usage.mdc +++ b/.cursor/rules/recent-data-usage.mdc @@ -42,7 +42,7 @@ const Component = () => { return (
- {recentTopics.map(topic => ( + {recentTopics.map((topic) => (
{topic.title}
))}
@@ -81,6 +81,7 @@ const isInit = useSessionStore(recentSelectors.isRecentTopicsInit); ``` **RecentTopic 类型:** + ```typescript interface RecentTopic { agent: { diff --git a/.cursor/rules/testing-guide/testing-guide.mdc b/.cursor/rules/testing-guide/testing-guide.mdc index 9ae5bf6ac2..0180eb211f 100644 --- a/.cursor/rules/testing-guide/testing-guide.mdc +++ b/.cursor/rules/testing-guide/testing-guide.mdc @@ -3,173 +3,199 @@ globs: *.test.ts,*.test.tsx alwaysApply: false --- -# 测试指南 - LobeChat Testing Guide +# LobeChat Testing Guide -## 测试环境概览 +## Test Overview -LobeChat 项目使用 Vitest 测试库,配置了两种不同的测试环境: +LobeChat testing consists of **E2E tests** and **Unit tests**. This guide focuses on **Unit tests**. -### 客户端数据库测试环境 (DOM Environment) +Unit tests are organized into three main categories: -- **配置文件**: [vitest.config.ts](mdc:vitest.config.ts) -- **环境**: Happy DOM (浏览器环境模拟) -- **数据库**: PGLite (浏览器环境的 PostgreSQL) -- **用途**: 测试前端组件、客户端逻辑、React 组件等 -- **设置文件**: [tests/setup.ts](mdc:tests/setup.ts) +```plaintext ++---------------------+---------------------------+-----------------------------+ +| Category | Location | Config File | ++---------------------+---------------------------+-----------------------------+ +| Next.js Webapp | src/**/*.test.ts(x) | vitest.config.ts | +| Packages | packages/*/**/*.test.ts | packages/*/vitest.config.ts | +| Desktop App | apps/desktop/**/*.test.ts | apps/desktop/vitest.config.ts | ++---------------------+---------------------------+-----------------------------+ +``` -### 服务端数据库测试环境 (Node Environment) +### Next.js Webapp Tests -目前只有 `packages/database` 下的测试可以通过配置 `TEST_SERVER_DB=1` 环境变量来使用服务端数据库测试 +- **Config File**: `vitest.config.ts` +- **Environment**: Happy DOM (browser environment simulation) +- **Database**: PGLite (PostgreSQL for browser environments) +- **Setup File**: `tests/setup.ts` +- **Purpose**: Testing React components, hooks, stores, utilities, and client-side logic -- **配置文件**: [packages/database/vitest.config.mts](mdc:packages/database/vitest.config.mts) 并且设置环境变量 `TEST_SERVER_DB=1` -- **环境**: Node.js -- **数据库**: 真实的 PostgreSQL 数据库 -- **并发限制**: 单线程运行 (`singleFork: true`) -- **用途**: 测试数据库模型、服务端逻辑、API 端点等 -- **设置文件**: [packages/database/tests/setup-db.ts](mdc:packages/database/tests/setup-db.ts) +### Packages Tests -## 测试运行命令 +Most packages use standard Vitest configuration. However, the `database` package is special: -** 性能警告**: 项目包含 3000+ 测试用例,完整运行需要约 10 分钟。务必使用文件过滤或测试名称过滤。 +#### Database Package (Special Case) -### 正确的命令格式 +The database package supports **dual-environment testing**: + +| Environment | Database | Config | Use Case | +|------------------|-----------------|---------------------------------------|-----------------------------------| +| Client (Default) | PGLite | `packages/database/vitest.config.mts` | Fast local development | +| Server | Real PostgreSQL | Set `TEST_SERVER_DB=1` | CI/CD, compatibility verification | + +Server environment details: + +- **Concurrency**: Single-threaded (`singleFork: true`) +- **Setup File**: `packages/database/tests/setup-db.ts` +- **Requirement**: `DATABASE_TEST_URL` environment variable must be set + +### Desktop App Tests + +- **Config File**: `apps/desktop/vitest.config.ts` +- **Environment**: Node.js +- **Purpose**: Testing Electron main process controllers, IPC handlers, and desktop-specific logic + +## Test Commands + +**Performance Warning**: The project contains 3000+ test cases. A full run takes approximately 10 minutes. Always use file filtering or test name filtering. + +### Recommended Command Format ```bash -# 运行所有客户端/服务端测试 -bunx vitest run --silent='passed-only' # 客户端测试 -cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' # 服务端测试 +# Run all client/server tests +bunx vitest run --silent='passed-only' # Client tests +cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' # Server tests -# 运行特定测试文件 (支持模糊匹配) +# Run specific test file (supports fuzzy matching) bunx vitest run --silent='passed-only' user.test.ts -# 运行特定测试用例名称 (使用 -t 参数) +# Run specific test case by name (using -t flag) bunx vitest run --silent='passed-only' -t "test case name" -# 组合使用文件和测试名称过滤 +# Combine file and test name filtering bunx vitest run --silent='passed-only' filename.test.ts -t "specific test" -# 生成覆盖率报告 (使用 --coverage 参数) +# Generate coverage report (using --coverage flag) bunx vitest run --silent='passed-only' --coverage ``` -### 避免的命令格式 +### Commands to Avoid ```bash -# 这些命令会运行所有 3000+ 测试用例,耗时约 10 分钟! +# ❌ These commands run all 3000+ test cases, taking ~10 minutes! npm test npm test some-file.test.ts -# 不要使用裸 vitest (会进入 watch 模式) +# ❌ Don't use bare vitest (enters watch mode) vitest test-file.test.ts ``` -## 测试修复原则 +## Test Fixing Principles -### 核心原则 +### Core Principles -1. **收集足够的上下文** - 在修复测试之前,务必做到: - - 完整理解测试的意图和实现 - - 强烈建议阅读当前的 git diff 和 PR diff +1. **Gather Sufficient Context** + Before fixing tests, ensure you: + - Fully understand the test's intent and implementation + - Strongly recommended: review the current git diff and PR diff -2. **测试优先修复** - 如果是测试本身写错了,应优先修改测试,而不是实现代码。 +2. **Prioritize Test Fixes** + If the test itself is incorrect, fix the test first rather than the implementation code. -3. **专注单一问题** - 只修复指定的测试,不要顺带添加额外测试。 +3. **Focus on a Single Issue** + Only fix the specified test; don't add extra tests along the way. -4. **不自作主张** - 发现其他问题时,不要直接修改,需先提出并讨论。 +4. **Don't Act Unilaterally** + When discovering other issues, don't modify them directly—raise and discuss first. -### 测试协作最佳实践 +### Testing Collaboration Best Practices -基于实际开发经验总结的重要协作原则: +Important collaboration principles based on real development experience: -#### 1. 失败处理策略 +#### 1. Failure Handling Strategy -**核心原则**: 避免盲目重试,快速识别问题并寻求帮助。 +**Core Principle**: Avoid blind retries; quickly identify problems and seek help. -- **失败阈值**: 当连续尝试修复测试 1-2 次都失败后,应立即停止继续尝试 -- **问题总结**: 分析失败原因,整理已尝试的解决方案及其失败原因 -- **寻求帮助**: 带着清晰的问题摘要和尝试记录向团队寻求帮助 -- **避免陷阱**: 不要陷入"不断尝试相同或类似方法"的循环 +- **Failure Threshold**: After 1-2 consecutive failed fix attempts, stop immediately +- **Problem Summary**: Analyze failure reasons and document attempted solutions with their failure causes +- **Seek Help**: Approach the team with a clear problem summary and attempt history +- **Avoid the Trap**: Don't fall into the loop of repeatedly trying the same or similar approaches ```typescript -// 错误做法:连续失败后继续盲目尝试 -// 第3次、第4次仍在用相似的方法修复同一个问题 +// ❌ Wrong approach: Keep blindly trying after consecutive failures +// 3rd, 4th attempts still using similar methods to fix the same problem -// 正确做法:失败1-2次后总结问题 +// ✅ Correct approach: Summarize after 1-2 failures /* -问题总结: -1. 尝试过的方法:修改 mock 数据结构 -2. 失败原因:仍然提示类型不匹配 -3. 具体错误:Expected 'UserData' but received 'UserProfile' -4. 需要帮助:不确定最新的 UserData 接口定义 +Problem Summary: +1. Attempted method: Modified mock data structure +2. Failure reason: Still getting type mismatch error +3. Specific error: Expected 'UserData' but received 'UserProfile' +4. Help needed: Unsure about the latest UserData interface definition */ ``` -#### 2. 测试用例命名规范 +#### 2. Test Case Naming Conventions -**核心原则**: 测试应该关注"行为",而不是"实现细节"。 +**Core Principle**: Tests should focus on "behavior," not "implementation details." -- **描述业务场景**: `describe` 和 `it` 的标题应该描述具体的业务场景和预期行为 -- **避免实现绑定**: 不要在测试名称中提及具体的代码行号、覆盖率目标或实现细节 -- **保持稳定性**: 测试名称应该在代码重构后仍然有意义 +- **Describe Business Scenarios**: `describe` and `it` titles should describe specific business scenarios and expected behaviors +- **Avoid Implementation Binding**: Don't mention specific line numbers, coverage goals, or implementation details in test names +- **Maintain Stability**: Test names should remain meaningful after code refactoring ```typescript -// 错误的测试命名 +// ❌ Poor test naming describe('User component coverage', () => { it('covers line 45-50 in getUserData', () => { - // 为了覆盖第45-50行而写的测试 + // Test written just to cover lines 45-50 }); it('tests the else branch', () => { - // 仅为了测试某个分支而存在 + // Exists only to test a specific branch }); }); -// 正确的测试命名 +// ✅ Good test naming describe('', () => { it('should render fallback icon when image url is not provided', () => { - // 测试具体的业务场景,自然会覆盖相关代码分支 + // Tests a specific business scenario, naturally covering relevant code branches }); it('should display user initials when avatar image fails to load', () => { - // 描述用户行为和预期结果 + // Describes user behavior and expected outcome }); }); ``` -**覆盖率提升的正确思路**: +**The Right Approach to Improving Coverage**: -- 通过设计各种业务场景(正常流程、边缘情况、错误处理)来自然提升覆盖率 -- 不要为了达到覆盖率数字而写测试,更不要在测试中注释"为了覆盖 xxx 行" +- Naturally improve coverage by designing various business scenarios (happy paths, edge cases, error handling) +- Don't write tests just to hit coverage numbers, and never comment "to cover line xxx" in tests -#### 3. 测试组织结构 +#### 3. Test Organization Structure -**核心原则**: 维护清晰的测试层次结构,避免冗余的顶级测试块。 +**Core Principle**: Maintain a clear test hierarchy; avoid redundant top-level test blocks. -- **复用现有结构**: 添加新测试时,优先在现有的 `describe` 块中寻找合适的位置 -- **逻辑分组**: 相关的测试用例应该组织在同一个 `describe` 块内 -- **避免碎片化**: 不要为了单个测试用例就创建新的顶级 `describe` 块 +- **Reuse Existing Structure**: When adding new tests, first look for an appropriate place in existing `describe` blocks +- **Logical Grouping**: Related test cases should be organized within the same `describe` block +- **Avoid Fragmentation**: Don't create a new top-level `describe` block for a single test case ```typescript -// 错误的组织方式:创建过多顶级块 +// ❌ Poor organization: Too many top-level blocks describe('', () => { it('should render user name', () => {}); }); describe('UserProfile new prop test', () => { - // 不必要的新块 + // Unnecessary new block it('should handle email display', () => {}); }); describe('UserProfile edge cases', () => { - // 不必要的新块 + // Unnecessary new block it('should handle missing avatar', () => {}); }); -// 正确的组织方式:合并相关测试 +// ✅ Good organization: Merge related tests describe('', () => { it('should render user name', () => {}); @@ -178,78 +204,78 @@ describe('', () => { it('should handle missing avatar', () => {}); describe('when user data is incomplete', () => { - // 只有在有多个相关子场景时才创建子组 + // Only create sub-groups when there are multiple related sub-scenarios it('should show placeholder for missing name', () => {}); it('should hide email section when email is undefined', () => {}); }); }); ``` -**组织决策流程**: +**Organization Decision Flow**: -1. 是否存在逻辑相关的现有 `describe` 块? → 如果有,添加到其中 -2. 是否有多个(3个以上)相关的测试用例? → 如果有,可以考虑创建新的子 `describe` -3. 是否是独立的、无关联的功能模块? → 如果是,才考虑创建新的顶级 `describe` +1. Is there a logically related existing `describe` block? → If yes, add to it +2. Are there multiple (3+) related test cases? → If yes, consider creating a new sub-`describe` +3. Is it an independent, unrelated feature module? → Only then consider creating a new top-level `describe` -### 测试修复流程 +### Test Fixing Workflow -1. **复现问题**: 定位并运行失败的测试,确认能在本地复现 -2. **分析原因**: 阅读测试代码、错误日志和相关文件的 Git 修改历史 -3. **建立假设**: 判断问题出在测试逻辑、实现代码还是环境配置 -4. **修复验证**: 根据假设进行修复,重新运行测试确认通过 -5. **扩大验证**: 运行当前文件内所有测试,确保没有引入新问题 -6. **撰写总结**: 说明错误原因和修复方法 +1. **Reproduce the Issue**: Locate and run the failing test; confirm it can be reproduced locally +2. **Analyze the Cause**: Read test code, error logs, and Git history of related files +3. **Form a Hypothesis**: Determine if the problem is in test logic, implementation code, or environment configuration +4. **Fix and Verify**: Apply the fix based on your hypothesis; rerun the test to confirm it passes +5. **Expand Verification**: Run all tests in the current file to ensure no new issues were introduced +6. **Write a Summary**: Document the error cause and fix method -### 修复完成后的总结 +### Post-Fix Summary -测试修复完成后,应该提供简要说明,包括: +After completing a test fix, provide a brief explanation including: -1. **错误原因分析**: 说明测试失败的根本原因 - - 测试逻辑错误 - - 实现代码bug - - 环境配置问题 - - 依赖变更导致的问题 +1. **Root Cause Analysis**: Explain the fundamental reason for the test failure + - Test logic error + - Implementation bug + - Environment configuration issue + - Dependency change -2. **修复方法说明**: 简述采用的修复方式 - - 修改了哪些文件 - - 采用了什么解决方案 - - 为什么选择这种修复方式 +2. **Fix Description**: Briefly describe the fix approach + - Which files were modified + - What solution was applied + - Why this fix approach was chosen -**示例格式**: +**Example Format**: ```markdown -## 测试修复总结 +## Test Fix Summary -**错误原因**: 测试中的 mock 数据格式与实际 API 返回格式不匹配,导致断言失败。 +**Root Cause**: The mock data format in the test didn't match the actual API response format, causing assertion failures. -**修复方法**: 更新了测试文件中的 mock 数据结构,使其与最新的 API 响应格式保持一致。具体修改了 `user.test.ts` 中的 `mockUserData` 对象结构。 +**Fix**: Updated the mock data structure in the test file to match the latest API response format. Specifically modified the `mockUserData` object structure in `user.test.ts`. ``` -## 测试编写最佳实践 +## Test Writing Best Practices -### Mock 数据策略:追求"低成本的真实性" +### Mock Data Strategy: Aim for "Low-Cost Authenticity" -**核心原则**: 测试数据应默认追求真实性,只有在引入"高昂的测试成本"时才进行简化。 +**Core Principle**: Test data should default to authenticity; only simplify when it introduces "high testing costs." -#### 什么是"高昂的测试成本"? +#### What Are "High Testing Costs"? -"高成本"指的是测试中引入了外部依赖,使测试变慢、不稳定或复杂: +"High cost" refers to introducing external dependencies in tests that make them slow, unstable, or complex: -- **文件 I/O 操作**:读写硬盘文件 -- **网络请求**:HTTP 调用、数据库连接 -- **系统调用**:获取系统时间、环境变量等 +- **File I/O Operations**: Reading/writing disk files +- **Network Requests**: HTTP calls, database connections +- **System Calls**: Getting system time, environment variables, etc. -#### 推荐做法:Mock 依赖,保留真实数据 +#### Recommended Approach: Mock Dependencies, Keep Real Data ```typescript -// 好的做法:Mock I/O 操作,但使用真实的文件内容格式 +// ✅ Good approach: Mock I/O operations but use real file content formats describe('parseContentType', () => { beforeEach(() => { - // Mock 文件读取操作(避免真实 I/O) + // Mock file read operation (avoid real I/O) vi.spyOn(fs, 'readFileSync').mockImplementation((path) => { - // 但返回真实的文件内容格式 - if (path.includes('.pdf')) return '%PDF-1.4\n%âãÏÓ'; // 真实 PDF 文件头 - if (path.includes('.png')) return '\x89PNG\r\n\x1a\n'; // 真实 PNG 文件头 + // But return real file content formats + if (path.includes('.pdf')) return '%PDF-1.4\n%âãÏÓ'; // Real PDF header + if (path.includes('.png')) return '\x89PNG\r\n\x1a\n'; // Real PNG header return ''; }); }); @@ -260,40 +286,38 @@ describe('parseContentType', () => { }); }); -// 过度简化:使用不真实的数据 +// ❌ Over-simplified: Using unrealistic data describe('parseContentType', () => { it('should detect PDF content type correctly', () => { - // 这种简化数据没有测试价值 + // This simplified data has no test value const result = parseContentType('fake-pdf-content'); expect(result).toBe('application/pdf'); }); }); ``` -#### 真实标识符的价值 +#### The Value of Real Identifiers ```typescript -// ✅ 使用真实标识符 +// ✅ Use real identifiers const result = parseModelString('openai', '+gpt-4,+gpt-3.5-turbo'); -// ❌ 使用占位符(价值较低) +// ❌ Use placeholders (lower value) const result = parseModelString('test-provider', '+model1,+model2'); ``` -### 现代化Mock技巧:环境设置与Mock方法 +### Modern Mocking Techniques: Environment Setup and Mock Methods -**环境设置 + Mock方法结合使用** - -客户端代码测试时,推荐使用环境注释配合现代化Mock方法: +When testing client-side code, use environment annotations with modern mock methods: ```typescript /** - * @vitest-environment happy-dom // 提供浏览器API + * @vitest-environment happy-dom // Provides browser APIs */ import { beforeEach, vi } from 'vitest'; beforeEach(() => { - // 现代方法1:使用vi.stubGlobal替代global.xxx = ... + // Modern method 1: Use vi.stubGlobal instead of global.xxx = ... const mockImage = vi.fn().mockImplementation(() => ({ addEventListener: vi.fn(), naturalHeight: 600, @@ -301,72 +325,72 @@ beforeEach(() => { })); vi.stubGlobal('Image', mockImage); - // 现代方法2:使用vi.spyOn保留原功能,只mock特定方法 + // Modern method 2: Use vi.spyOn to preserve original functionality, only mock specific methods vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url'); vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}); }); ``` -**环境选择优先级** +#### Environment Selection Priority -1. **@vitest-environment happy-dom** (推荐) - 轻量、快速,项目已安装 -2. **@vitest-environment jsdom** - 功能完整,但需要额外安装jsdom包 -3. **不设置环境** - Node.js环境,需要手动mock所有浏览器API +1. **@vitest-environment happy-dom** (Recommended) - Lightweight, fast, already installed in the project +2. **@vitest-environment jsdom** - Full-featured, but requires additional jsdom package installation +3. **No environment set** - Node.js environment, requires manually mocking all browser APIs -**Mock方法对比** +#### Mock Method Comparison ```typescript -// ❌ 旧方法:直接操作global对象(类型问题) +// ❌ Old method: Directly manipulating global object (type issues) global.Image = mockImage; global.URL = { ...global.URL, createObjectURL: mockFn }; -// ✅ 现代方法:类型安全的vi API -vi.stubGlobal('Image', mockImage); // 完全替换全局对象 -vi.spyOn(URL, 'createObjectURL'); // 部分mock,保留其他功能 +// ✅ Modern method: Type-safe vi API +vi.stubGlobal('Image', mockImage); // Completely replace global object +vi.spyOn(URL, 'createObjectURL'); // Partial mock, preserve other functionality ``` -### 测试覆盖率原则:代码分支优于用例数量 +### Test Coverage Principles: Code Branches Over Test Quantity -**核心原则**: 优先覆盖所有代码分支,而非编写大量重复用例 +**Core Principle**: Prioritize covering all code branches rather than writing many repetitive test cases. ```typescript -// ❌ 过度测试:29个测试用例都验证相同分支 +// ❌ Over-testing: 29 test cases all validating the same branch describe('getImageDimensions', () => { it('should reject .txt files'); it('should reject .pdf files'); - // ... 25个类似测试,都走相同的验证分支 + // ... 25 similar tests, all hitting the same validation branch }); -// ✅ 精简测试:4个核心用例覆盖所有分支 +// ✅ Lean testing: 4 core cases covering all branches describe('getImageDimensions', () => { - it('should return dimensions for valid File object'); // 成功路径 - File - it('should return dimensions for valid data URI'); // 成功路径 - String - it('should return undefined for invalid inputs'); // 输入验证分支 - it('should return undefined when image fails to load'); // 错误处理分支 + it('should return dimensions for valid File object'); // Success path - File + it('should return dimensions for valid data URI'); // Success path - String + it('should return undefined for invalid inputs'); // Input validation branch + it('should return undefined when image fails to load'); // Error handling branch }); ``` -**分支覆盖策略** +#### Branch Coverage Strategy -1. **成功路径** - 每种输入类型1个测试即可 -2. **边界条件** - 合并类似场景到单个测试 -3. **错误处理** - 测试代表性错误即可 -4. **业务逻辑** - 覆盖所有if/else分支 +1. **Success Paths** - One test per input type is sufficient +2. **Boundary Conditions** - Consolidate similar scenarios into a single test +3. **Error Handling** - Test representative errors only +4. **Business Logic** - Cover all if/else branches -**合理测试数量** +#### Reasonable Test Counts -- 简单工具函数:2-5个测试 -- 复杂业务逻辑:5-10个测试 -- 核心安全功能:适当增加,但避免重复路径 +- Simple utility functions: 2-5 tests +- Complex business logic: 5-10 tests +- Core security features: Add more as needed, but avoid duplicate paths -### 错误处理测试:测试"行为"而非"文本" +### Error Handling Tests: Test "Behavior" Not "Text" -**核心原则**: 测试应该验证程序在错误发生时的行为是可预测的,而不是验证易变的错误信息文本。 +**Core Principle**: Tests should verify that program behavior is predictable when errors occur, not verify error message text that may change. -#### 推荐的错误测试方式 +#### Recommended Error Testing Approach ```typescript -// ✅ 测试错误类型和属性 +// ✅ Test error types and properties expect(() => validateUser({})).toThrow(ValidationError); expect(() => processPayment({})).toThrow( expect.objectContaining({ @@ -375,136 +399,136 @@ expect(() => processPayment({})).toThrow( }), ); -// ❌ 避免测试具体错误文本 -expect(() => processUser({})).toThrow('用户数据不能为空,请检查输入参数'); +// ❌ Avoid testing specific error text +expect(() => processUser({})).toThrow('User data cannot be empty, please check input parameters'); ``` -### 疑难解答:警惕模块污染 +### Troubleshooting: Beware of Module Pollution -**识别信号**: 当你的测试出现以下"灵异"现象时,优先怀疑模块污染: +**Warning Signs**: When your tests exhibit these "mysterious" behaviors, suspect module pollution first: -- 单独运行某个测试通过,但和其他测试一起运行就失败 -- 测试的执行顺序影响结果 -- Mock 设置看起来正确,但实际使用的是旧的 Mock 版本 +- A test passes when run alone but fails when run with other tests +- Test execution order affects results +- Mock setup appears correct but actually uses an old mock version -#### 典型场景:动态 Mock 同一模块 +#### Typical Scenario: Dynamic Mocking of the Same Module ```typescript -// ❌ 问题:动态Mock同一模块 +// ❌ Problem: Dynamic mocking of the same module it('dev mode', async () => { vi.doMock('./config', () => ({ isDev: true })); - const { getSettings } = await import('./service'); // 可能使用缓存 + const { getSettings } = await import('./service'); // May use cache }); -// ✅ 解决:清除模块缓存 +// ✅ Solution: Clear module cache beforeEach(() => { - vi.resetModules(); // 确保每个测试都是干净环境 + vi.resetModules(); // Ensure each test has a clean environment }); ``` -**记住**: `vi.resetModules()` 是解决测试"灵异"失败的终极武器。 +**Remember**: `vi.resetModules()` is the ultimate weapon for resolving "mysterious" test failures. -## 测试文件组织 +## Test File Organization -### 文件命名约定 +### File Naming Convention -`*.test.ts`, `*.test.tsx` (任意位置) +`*.test.ts`, `*.test.tsx` (any location) -### 测试文件组织风格 +### Test File Organization Style -项目采用 **测试文件与源文件同目录** 的组织风格: +The project uses a **co-located test files** organization style: -- 测试文件放在对应源文件的同一目录下 -- 命名格式:`原文件名.test.ts` 或 `原文件名.test.tsx` +- Test files are placed in the same directory as the corresponding source files +- Naming format: `originalFileName.test.ts` or `originalFileName.test.tsx` -例如: +Example: ```plaintext src/components/Button/ -├── index.tsx # 源文件 -└── index.test.tsx # 测试文件 +├── index.tsx # Source file +└── index.test.tsx # Test file ``` -- 也有少数情况会统一放到 `__tests__` 文件夹, 例如 `packages/database/src/models/__tests__` -- 测试使用的辅助文件放到 fixtures 文件夹 +- In some cases, tests are consolidated in a `__tests__` folder, e.g., `packages/database/src/models/__tests__` +- Test helper files are placed in a fixtures folder -## 测试调试技巧 +## Test Debugging Tips -### 测试调试步骤 +### Test Debugging Steps -1. **确定测试环境**: 根据文件路径选择正确的配置文件 -2. **隔离问题**: 使用 `-t` 参数只运行失败的测试用例 -3. **分析错误**: 仔细阅读错误信息、堆栈跟踪和最近的文件修改记录 -4. **添加调试**: 在测试中添加 `console.log` 了解执行流程 +1. **Determine Test Environment**: Select the correct config file based on file path +2. **Isolate the Problem**: Use the `-t` flag to run only the failing test case +3. **Analyze the Error**: Carefully read error messages, stack traces, and recent file modification history +4. **Add Debugging**: Add `console.log` statements in tests to understand execution flow -### TypeScript 类型处理 +### TypeScript Type Handling -在测试中,为了提高编写效率和可读性,可以适当放宽 TypeScript 类型检测: +In tests, you can relax TypeScript type checking to improve writing efficiency and readability: -#### 推荐的类型放宽策略 +#### Recommended Type Relaxation Strategies ```typescript -// 使用非空断言访问测试中确定存在的属性 +// Use non-null assertion to access properties you're certain exist in tests const result = await someFunction(); expect(result!.data).toBeDefined(); expect(result!.status).toBe('success'); -// 使用 any 类型简化复杂的 Mock 设置 +// Use any type to simplify complex mock setups const mockStream = new ReadableStream() as any; mockStream.toReadableStream = () => mockStream; -// 访问私有成员 -await instance['getFromCache']('key'); // 推荐中括号 -await (instance as any).getFromCache('key'); // 避免as any +// Access private members +await instance['getFromCache']('key'); // Bracket notation recommended +await (instance as any).getFromCache('key'); // Avoid as any ``` -#### 适用场景 +#### Applicable Scenarios -- **Mock 对象**: 对于测试用的 Mock 数据,使用 `as any` 避免复杂的类型定义 -- **第三方库**: 处理复杂的第三方库类型时,适当使用 `any` 提高效率 -- **测试断言**: 在确定对象存在的测试场景中,使用 `!` 非空断言 -- **私有成员访问**: 优先使用中括号 `instance['privateMethod']()` 而不是 `(instance as any).privateMethod()` -- **临时调试**: 快速编写测试时,先用 `any` 保证功能,后续可选择性地优化类型 +- **Mock Objects**: Use `as any` for test mock data to avoid complex type definitions +- **Third-Party Libraries**: Use `any` appropriately when handling complex third-party library types +- **Test Assertions**: Use `!` non-null assertion in test scenarios where you're certain the object exists +- **Private Member Access**: Prefer bracket notation `instance['privateMethod']()` over `(instance as any).privateMethod()` +- **Temporary Debugging**: When quickly writing tests, use `any` first to ensure functionality, then optionally optimize types later -#### 注意事项 +#### Important Notes -- **适度使用**: 不要过度依赖 `any`,核心业务逻辑的类型仍应保持严格 -- **私有成员访问优先级**: 中括号访问 > `as any` 转换,保持更好的类型安全性 -- **文档说明**: 对于使用 `any` 的复杂场景,添加注释说明原因 -- **测试覆盖**: 确保即使使用了 `any`,测试仍能有效验证功能正确性 +- **Use Moderately**: Don't over-rely on `any`; core business logic types should remain strict +- **Private Member Access Priority**: Bracket notation > `as any` casting for better type safety +- **Documentation**: Add comments explaining the reason for complex `any` usage scenarios +- **Test Coverage**: Ensure tests still effectively verify correctness even when using `any` -### 检查最近修改记录 +### Checking Recent Modifications -**核心原则**:测试突然失败时,优先检查最近的代码修改。 +**Core Principle**: When tests suddenly fail, first check recent code changes. -#### 快速检查方法 +#### Quick Check Methods ```bash -git status # 查看当前修改状态 -git diff HEAD -- '*.test.*' # 检查测试文件改动 -git diff main...HEAD # 对比主分支差异 -gh pr diff # 查看PR中的所有改动 +git status # View current modification status +git diff HEAD -- '*.test.*' # Check test file changes +git diff main...HEAD # Compare with main branch +gh pr diff # View all changes in the PR ``` -#### 常见原因与解决 +#### Common Causes and Solutions -- **最新提交引入bug** → 检查并修复实现代码 -- **分支代码滞后** → `git rebase main` 同步主分支 +- **Latest commit introduced a bug** → Check and fix the implementation code +- **Branch code is outdated** → `git rebase main` to sync with main branch -## 特殊场景的测试 +## Special Testing Scenarios -针对一些特殊场景的测试,需要阅读相关 rules: +For special testing scenarios, refer to the related rules: -- [Electron IPC 接口测试策略](mdc:./electron-ipc-test.mdc) -- [数据库 Model 测试指南](mdc:./db-model-test.mdc) +- `electron-ipc-test.mdc` - Electron IPC Interface Testing Strategy +- `db-model-test.mdc` - Database Model Testing Guide -## 核心要点 +## Key Takeaways -- **命令格式**: 使用 `bunx vitest run --silent='passed-only'` 并指定文件过滤 -- **修复原则**: 失败1-2次后寻求帮助,测试命名关注行为而非实现细节 -- **调试流程**: 复现 → 分析 → 假设 → 修复 → 验证 → 总结 -- **文件组织**: 优先在现有 `describe` 块中添加测试,避免创建冗余顶级块 -- **数据策略**: 默认追求真实性,只有高成本(I/O、网络等)时才简化 -- **错误测试**: 测试错误类型和行为,避免依赖具体的错误信息文本 -- **模块污染**: 测试"灵异"失败时,优先怀疑模块污染,使用 `vi.resetModules()` 解决 -- **安全要求**: Model 测试必须包含权限检查,并在双环境下验证通过 +- **Command Format**: Use `bunx vitest run --silent='passed-only'` with file filtering +- **Fix Principles**: Seek help after 1-2 failures; focus test naming on behavior, not implementation details +- **Debug Workflow**: Reproduce → Analyze → Hypothesize → Fix → Verify → Summarize +- **File Organization**: Prefer adding tests to existing `describe` blocks; avoid creating redundant top-level blocks +- **Data Strategy**: Default to authenticity; only simplify for high-cost scenarios (I/O, network, etc.) +- **Error Testing**: Test error types and behavior; avoid depending on specific error message text +- **Module Pollution**: When tests fail "mysteriously," suspect module pollution first; use `vi.resetModules()` to resolve +- **Security Requirements**: Model tests must include permission checks and pass in both environments diff --git a/.cursor/rules/testing-guide/zustand-store-action-test.mdc b/.cursor/rules/testing-guide/zustand-store-action-test.mdc index 7b90e7719b..2318ec7dba 100644 --- a/.cursor/rules/testing-guide/zustand-store-action-test.mdc +++ b/.cursor/rules/testing-guide/zustand-store-action-test.mdc @@ -1,6 +1,6 @@ --- description: Best practices for testing Zustand store actions -globs: 'src/store/**/*.test.ts' +globs: src/store/**/*.test.ts alwaysApply: false --- diff --git a/.cursor/rules/zustand-action-patterns.mdc b/.cursor/rules/zustand-action-patterns.mdc index a3491e7288..365487c31d 100644 --- a/.cursor/rules/zustand-action-patterns.mdc +++ b/.cursor/rules/zustand-action-patterns.mdc @@ -16,7 +16,7 @@ Main interfaces exposed for UI component consumption: - Naming: Verb form (`createTopic`, `sendMessage`, `updateTopicTitle`) - Responsibilities: Parameter validation, flow orchestration, calling internal actions -- Example: [src/store/chat/slices/topic/action.ts](mdc:src/store/chat/slices/topic/action.ts) +- Example: `src/store/chat/slices/topic/action.ts` ```typescript // Public Action example diff --git a/.cursor/rules/zustand-slice-organization.mdc b/.cursor/rules/zustand-slice-organization.mdc index c25752cf1a..3226bc3099 100644 --- a/.cursor/rules/zustand-slice-organization.mdc +++ b/.cursor/rules/zustand-slice-organization.mdc @@ -105,7 +105,7 @@ export const initialTopicState: ChatTopicState = { }; ``` -2. `reducer.ts` (复杂状态使用): +1. `reducer.ts` (复杂状态使用): - 定义纯函数 reducer,处理同步状态转换 - 使用 `immer` 确保不可变更新 @@ -151,7 +151,7 @@ export const topicReducer = (state: ChatTopic[] = [], payload: ChatTopicDispatch }; ``` -3. `selectors.ts`: +1. `selectors.ts`: - 提供状态查询和计算函数 - 供 UI 组件使用的状态订阅接口 - 重要: 使用 `export const xxxSelectors` 模式聚合所有 selectors @@ -186,7 +186,7 @@ export const topicSelectors = { 当 slice 的 actions 过于复杂时,可以拆分到子目录: -``` +```plaintext src/store/chat/slices/aiChat/ ├── actions/ │ ├── generateAIChat.ts # AI 对话生成 @@ -204,7 +204,7 @@ src/store/chat/slices/aiChat/ 管理多种内置工具的状态: -``` +```plaintext src/store/chat/slices/builtinTool/ ├── actions/ │ ├── dalle.ts # DALL-E 图像生成 diff --git a/CLAUDE.md b/CLAUDE.md index 2b6c2bd62e..78b661311e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,7 @@ see @.cursor/rules/typescript.mdc ### Testing -- **Required Rule**: read `@.cursor/rules/testing-guide/testing-guide.mdc` before writing tests +- **Required Rule**: read `.cursor/rules/testing-guide/testing-guide.mdc` before writing tests - **Command**: - web: `bunx vitest run --silent='passed-only' '[file-path-pattern]'` - packages(eg: database): `cd packages/database && bunx vitest run --silent='passed-only' '[file-path-pattern]'` diff --git a/GEMINI.md b/GEMINI.md index 7e4c9836b8..1b935e59a1 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -34,7 +34,7 @@ see @.cursor/rules/typescript.mdc ### Testing -- **Required Rule**: read `@.cursor/rules/testing-guide/testing-guide.mdc` before writing tests +- **Required Rule**: read `.cursor/rules/testing-guide/testing-guide.mdc` before writing tests - **Command**: - web: `bunx vitest run --silent='passed-only' '[file-path-pattern]'` - packages(eg: database): `cd packages/database && bunx vitest run --silent='passed-only' '[file-path-pattern]'` diff --git a/docs/development/database-schema.dbml b/docs/development/database-schema.dbml index 57a2c06754..09c3a2496d 100644 --- a/docs/development/database-schema.dbml +++ b/docs/development/database-schema.dbml @@ -1093,6 +1093,22 @@ table topic_documents { } } +table topic_shares { + id text [pk, not null] + topic_id text [not null] + user_id text [not null] + visibility text [not null, default: 'private'] + page_view_count integer [not null, default: 0] + accessed_at "timestamp with time zone" [not null, default: `now()`] + created_at "timestamp with time zone" [not null, default: `now()`] + updated_at "timestamp with time zone" [not null, default: `now()`] + + indexes { + topic_id [name: 'topic_shares_topic_id_unique', unique] + user_id [name: 'topic_shares_user_id_idx'] + } +} + table topics { id text [pk, not null] title text diff --git a/locales/en-US/chat.json b/locales/en-US/chat.json index 7bc829994e..eff353923c 100644 --- a/locales/en-US/chat.json +++ b/locales/en-US/chat.json @@ -253,6 +253,8 @@ "sessionGroup.sorting": "Group sorting updating...", "sessionGroup.tooLong": "Group name length should be between 1-20", "shareModal.copy": "Copy", + "shareModal.copyLink": "Copy Link", + "shareModal.copyLinkSuccess": "Link copied", "shareModal.download": "Download Screenshot", "shareModal.downloadError": "Download failed", "shareModal.downloadFile": "Download File", @@ -268,12 +270,26 @@ "shareModal.imageType": "Image Format", "shareModal.includeTool": "Include Skill messages", "shareModal.includeUser": "Include User Messages", + "shareModal.link": "Link", + "shareModal.link.linkHint": "Anyone with the link can view this topic", + "shareModal.link.noTopic": "Start a conversation first to share", + "shareModal.link.permissionLink": "Anyone with the link", + "shareModal.link.permissionPrivate": "Private", + "shareModal.link.privateHint": "Only you can access this link", + "shareModal.link.updateError": "Failed to update sharing settings", + "shareModal.link.visibilityUpdated": "Visibility updated", "shareModal.loadingPdf": "Loading PDF...", "shareModal.noPdfData": "No PDF data available", "shareModal.pdf": "PDF", "shareModal.pdfErrorDescription": "An error occurred while generating the PDF, please try again", "shareModal.pdfGenerationError": "PDF generation failed", "shareModal.pdfReady": "PDF is ready", + "shareModal.popover.moreOptions": "More share options", + "shareModal.popover.privacyWarning.confirm": "I understand, continue", + "shareModal.popover.privacyWarning.content": "Please ensure the conversation does not contain any private or sensitive information before sharing. LobeHub is not responsible for any security issues that may arise from sharing.", + "shareModal.popover.privacyWarning.title": "Privacy Notice", + "shareModal.popover.title": "Share Topic", + "shareModal.popover.visibility": "Visibility", "shareModal.regeneratePdf": "Regenerate PDF", "shareModal.screenshot": "Screenshot", "shareModal.settings": "Export Settings", @@ -286,6 +302,14 @@ "shareModal.withPluginInfo": "Include Skill Information", "shareModal.withRole": "Include Message Role", "shareModal.withSystemRole": "Include Agent Profile", + "sharePage.error.forbidden.subtitle": "This share is private and not accessible.", + "sharePage.error.forbidden.title": "Access Denied", + "sharePage.error.notFound.subtitle": "This topic does not exist or has been removed.", + "sharePage.error.notFound.title": "Topic Not Found", + "sharePage.error.unauthorized.action": "Sign In", + "sharePage.error.unauthorized.subtitle": "Please sign in to view this shared topic.", + "sharePage.error.unauthorized.title": "Sign In Required", + "sharePageDisclaimer": "This content is shared by a user and does not represent the views of LobeHub. LobeHub is not responsible for any consequences arising from this shared content.", "stt.action": "Voice Input", "stt.loading": "Recognizing...", "stt.prettifying": "Polishing...", diff --git a/locales/zh-CN/chat.json b/locales/zh-CN/chat.json index e0ac0dd50d..84ec23d4df 100644 --- a/locales/zh-CN/chat.json +++ b/locales/zh-CN/chat.json @@ -253,6 +253,8 @@ "sessionGroup.sorting": "正在更新排序…", "sessionGroup.tooLong": "分组名称长度需为 1–20 个字符", "shareModal.copy": "复制", + "shareModal.copyLink": "复制链接", + "shareModal.copyLinkSuccess": "链接已复制", "shareModal.download": "下载截图", "shareModal.downloadError": "下载失败,请检查网络后重试", "shareModal.downloadFile": "下载文件", @@ -268,12 +270,26 @@ "shareModal.imageType": "图片格式", "shareModal.includeTool": "包含技能消息", "shareModal.includeUser": "包含用户消息", + "shareModal.link": "链接", + "shareModal.link.linkHint": "任何拿到链接的人都可以查看此话题", + "shareModal.link.noTopic": "先开始对话,才能分享", + "shareModal.link.permissionLink": "有链接即可访问", + "shareModal.link.permissionPrivate": "私密", + "shareModal.link.privateHint": "仅你自己可以访问此链接", + "shareModal.link.updateError": "更新分享设置失败", + "shareModal.link.visibilityUpdated": "可见性已更新", "shareModal.loadingPdf": "正在加载 PDF…", "shareModal.noPdfData": "暂无 PDF 数据", "shareModal.pdf": "PDF", "shareModal.pdfErrorDescription": "生成 PDF 时出错,请重试或联系支持", "shareModal.pdfGenerationError": "PDF 生成失败", "shareModal.pdfReady": "PDF 已准备就绪", + "shareModal.popover.moreOptions": "更多分享方式", + "shareModal.popover.privacyWarning.confirm": "我已了解,继续", + "shareModal.popover.privacyWarning.content": "分享前请确保对话中不包含任何隐私或敏感信息。因分享而可能产生的任何安全问题,LobeHub 概不负责。", + "shareModal.popover.privacyWarning.title": "隐私提醒", + "shareModal.popover.title": "分享话题", + "shareModal.popover.visibility": "可见性", "shareModal.regeneratePdf": "重新生成 PDF", "shareModal.screenshot": "截图", "shareModal.settings": "导出设置", @@ -286,6 +302,14 @@ "shareModal.withPluginInfo": "包含技能信息", "shareModal.withRole": "包含消息角色", "shareModal.withSystemRole": "包含助理档案", + "sharePage.error.forbidden.subtitle": "此分享为私密状态,无法访问。", + "sharePage.error.forbidden.title": "无权访问", + "sharePage.error.notFound.subtitle": "该话题不存在或已被删除。", + "sharePage.error.notFound.title": "话题不存在", + "sharePage.error.unauthorized.action": "登录", + "sharePage.error.unauthorized.subtitle": "请登录后查看此分享话题。", + "sharePage.error.unauthorized.title": "需要登录", + "sharePageDisclaimer": "此内容由用户分享,不代表 LobeHub 观点。LobeHub 不对该分享内容产生的任何后果承担责任。", "stt.action": "语音输入", "stt.loading": "识别中…", "stt.prettifying": "润色中…", diff --git a/packages/business/const/src/index.ts b/packages/business/const/src/index.ts index bafd6f0879..4090fcf7c7 100644 --- a/packages/business/const/src/index.ts +++ b/packages/business/const/src/index.ts @@ -4,3 +4,6 @@ export * from './llm'; export * from './url'; export const ENABLE_BUSINESS_FEATURES = false; +export const ENABLE_TOPIC_LINK_SHARE = + ENABLE_BUSINESS_FEATURES || + (process.env.NODE_ENV === 'development' && !!process.env.NEXT_PUBLIC_ENABLE_TOPIC_LINK_SHARE); diff --git a/packages/database/migrations/0069_add_topic_shares_table.sql b/packages/database/migrations/0069_add_topic_shares_table.sql new file mode 100644 index 0000000000..d4de5ff240 --- /dev/null +++ b/packages/database/migrations/0069_add_topic_shares_table.sql @@ -0,0 +1,22 @@ +CREATE TABLE IF NOT EXISTS "topic_shares" ( + "id" text PRIMARY KEY NOT NULL, + "topic_id" text NOT NULL, + "user_id" text NOT NULL, + "visibility" text DEFAULT 'private' NOT NULL, + "page_view_count" integer DEFAULT 0 NOT NULL, + "accessed_at" timestamp with time zone DEFAULT now() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "topic_shares" ADD CONSTRAINT "topic_shares_topic_id_topics_id_fk" FOREIGN KEY ("topic_id") REFERENCES "public"."topics"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION WHEN duplicate_object THEN NULL; END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "topic_shares" ADD CONSTRAINT "topic_shares_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION WHEN duplicate_object THEN NULL; END $$; +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "topic_shares_topic_id_unique" ON "topic_shares" USING btree ("topic_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "topic_shares_user_id_idx" ON "topic_shares" USING btree ("user_id"); diff --git a/packages/database/migrations/meta/0069_snapshot.json b/packages/database/migrations/meta/0069_snapshot.json new file mode 100644 index 0000000000..9b6ae9ae6f --- /dev/null +++ b/packages/database/migrations/meta/0069_snapshot.json @@ -0,0 +1,9704 @@ +{ + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + }, + "dialect": "postgresql", + "enums": {}, + "id": "0517bc1a-e3f8-4d2a-8b95-a81661851a25", + "policies": {}, + "prevId": "13fbe553-95a0-42b2-974c-f38b5e1da138", + "roles": {}, + "schemas": {}, + "sequences": {}, + "tables": { + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "editor_data": { + "name": "editor_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_color": { + "name": "background_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "market_identifier": { + "name": "market_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plugins": { + "name": "plugins", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_config": { + "name": "chat_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "few_shots": { + "name": "few_shots", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_role": { + "name": "system_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tts": { + "name": "tts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "virtual": { + "name": "virtual", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "opening_message": { + "name": "opening_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "opening_questions": { + "name": "opening_questions", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "session_group_id": { + "name": "session_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "client_id_user_id_unique": { + "name": "client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_slug_user_id_unique": { + "name": "agents_slug_user_id_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_user_id_idx": { + "name": "agents_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_title_idx": { + "name": "agents_title_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_description_idx": { + "name": "agents_description_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_session_group_id_idx": { + "name": "agents_session_group_id_idx", + "columns": [ + { + "expression": "session_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_user_id_users_id_fk": { + "name": "agents_user_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_session_group_id_session_groups_id_fk": { + "name": "agents_session_group_id_session_groups_id_fk", + "tableFrom": "agents", + "tableTo": "session_groups", + "columnsFrom": ["session_group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents_files": { + "name": "agents_files", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_files_agent_id_idx": { + "name": "agents_files_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_files_file_id_files_id_fk": { + "name": "agents_files_file_id_files_id_fk", + "tableFrom": "agents_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_files_agent_id_agents_id_fk": { + "name": "agents_files_agent_id_agents_id_fk", + "tableFrom": "agents_files", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_files_user_id_users_id_fk": { + "name": "agents_files_user_id_users_id_fk", + "tableFrom": "agents_files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_files_file_id_agent_id_user_id_pk": { + "name": "agents_files_file_id_agent_id_user_id_pk", + "columns": ["file_id", "agent_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents_knowledge_bases": { + "name": "agents_knowledge_bases", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_knowledge_bases_agent_id_idx": { + "name": "agents_knowledge_bases_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_knowledge_bases_agent_id_agents_id_fk": { + "name": "agents_knowledge_bases_agent_id_agents_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_knowledge_bases_knowledge_base_id_knowledge_bases_id_fk": { + "name": "agents_knowledge_bases_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_knowledge_bases_user_id_users_id_fk": { + "name": "agents_knowledge_bases_user_id_users_id_fk", + "tableFrom": "agents_knowledge_bases", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_knowledge_bases_agent_id_knowledge_base_id_pk": { + "name": "agents_knowledge_bases_agent_id_knowledge_base_id_pk", + "columns": ["agent_id", "knowledge_base_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_cron_jobs": { + "name": "agent_cron_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "cron_pattern": { + "name": "cron_pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'UTC'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "edit_data": { + "name": "edit_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_executions": { + "name": "max_executions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining_executions": { + "name": "remaining_executions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_conditions": { + "name": "execution_conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_executed_at": { + "name": "last_executed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_executions": { + "name": "total_executions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_cron_jobs_agent_id_idx": { + "name": "agent_cron_jobs_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_cron_jobs_group_id_idx": { + "name": "agent_cron_jobs_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_cron_jobs_user_id_idx": { + "name": "agent_cron_jobs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_cron_jobs_enabled_idx": { + "name": "agent_cron_jobs_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_cron_jobs_remaining_executions_idx": { + "name": "agent_cron_jobs_remaining_executions_idx", + "columns": [ + { + "expression": "remaining_executions", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_cron_jobs_last_executed_at_idx": { + "name": "agent_cron_jobs_last_executed_at_idx", + "columns": [ + { + "expression": "last_executed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_cron_jobs_agent_id_agents_id_fk": { + "name": "agent_cron_jobs_agent_id_agents_id_fk", + "tableFrom": "agent_cron_jobs", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_cron_jobs_group_id_chat_groups_id_fk": { + "name": "agent_cron_jobs_group_id_chat_groups_id_fk", + "tableFrom": "agent_cron_jobs", + "tableTo": "chat_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_cron_jobs_user_id_users_id_fk": { + "name": "agent_cron_jobs_user_id_users_id_fk", + "tableFrom": "agent_cron_jobs", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_models": { + "name": "ai_models", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization": { + "name": "organization", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'chat'" + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pricing": { + "name": "pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parameters": { + "name": "parameters", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "abilities": { + "name": "abilities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "context_window_tokens": { + "name": "context_window_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_models_user_id_idx": { + "name": "ai_models_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_models_user_id_users_id_fk": { + "name": "ai_models_user_id_users_id_fk", + "tableFrom": "ai_models", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_models_id_provider_id_user_id_pk": { + "name": "ai_models_id_provider_id_user_id_pk", + "columns": ["id", "provider_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_providers": { + "name": "ai_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "fetch_on_client": { + "name": "fetch_on_client", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "check_model": { + "name": "check_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_vaults": { + "name": "key_vaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_providers_user_id_idx": { + "name": "ai_providers_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_providers_user_id_users_id_fk": { + "name": "ai_providers_user_id_users_id_fk", + "tableFrom": "ai_providers", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ai_providers_id_user_id_pk": { + "name": "ai_providers_id_user_id_pk", + "columns": ["id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "api_keys_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "api_keys_user_id_idx": { + "name": "api_keys_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_key_unique": { + "name": "api_keys_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.async_tasks": { + "name": "async_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_tasks_user_id_idx": { + "name": "async_tasks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "async_tasks_user_id_users_id_fk": { + "name": "async_tasks_user_id_users_id_fk", + "tableFrom": "async_tasks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey": { + "name": "passkey", + "schema": "", + "columns": { + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backedUp": { + "name": "backedUp", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deviceType": { + "name": "deviceType", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "passkey_credential_id_unique": { + "name": "passkey_credential_id_unique", + "columns": [ + { + "expression": "credentialID", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "passkey_user_id_idx": { + "name": "passkey_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "passkey_userId_users_id_fk": { + "name": "passkey_userId_users_id_fk", + "tableFrom": "passkey", + "tableTo": "users", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_sessions": { + "name": "auth_sessions", + "schema": "", + "columns": { + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "auth_session_userId_idx": { + "name": "auth_session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_sessions_user_id_users_id_fk": { + "name": "auth_sessions_user_id_users_id_fk", + "tableFrom": "auth_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_sessions_token_unique": { + "name": "auth_sessions_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "two_factor_secret_idx": { + "name": "two_factor_secret_idx", + "columns": [ + { + "expression": "secret", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "two_factor_user_id_idx": { + "name": "two_factor_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "two_factor_user_id_users_id_fk": { + "name": "two_factor_user_id_users_id_fk", + "tableFrom": "two_factor", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "schema": "", + "columns": { + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_groups": { + "name": "chat_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_color": { + "name": "background_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editor_data": { + "name": "editor_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_groups_client_id_user_id_unique": { + "name": "chat_groups_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_groups_group_id_idx": { + "name": "chat_groups_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_groups_user_id_users_id_fk": { + "name": "chat_groups_user_id_users_id_fk", + "tableFrom": "chat_groups", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_groups_group_id_session_groups_id_fk": { + "name": "chat_groups_group_id_session_groups_id_fk", + "tableFrom": "chat_groups", + "tableTo": "session_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_groups_agents": { + "name": "chat_groups_agents", + "schema": "", + "columns": { + "chat_group_id": { + "name": "chat_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'participant'" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_groups_agents_chat_group_id_chat_groups_id_fk": { + "name": "chat_groups_agents_chat_group_id_chat_groups_id_fk", + "tableFrom": "chat_groups_agents", + "tableTo": "chat_groups", + "columnsFrom": ["chat_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_groups_agents_agent_id_agents_id_fk": { + "name": "chat_groups_agents_agent_id_agents_id_fk", + "tableFrom": "chat_groups_agents", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_groups_agents_user_id_users_id_fk": { + "name": "chat_groups_agents_user_id_users_id_fk", + "tableFrom": "chat_groups_agents", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_groups_agents_chat_group_id_agent_id_pk": { + "name": "chat_groups_agents_chat_group_id_agent_id_pk", + "columns": ["chat_group_id", "agent_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_type": { + "name": "file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_char_count": { + "name": "total_char_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_line_count": { + "name": "total_line_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pages": { + "name": "pages", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editor_data": { + "name": "editor_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_source_idx": { + "name": "documents_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_file_type_idx": { + "name": "documents_file_type_idx", + "columns": [ + { + "expression": "file_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_source_type_idx": { + "name": "documents_source_type_idx", + "columns": [ + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_user_id_idx": { + "name": "documents_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_file_id_idx": { + "name": "documents_file_id_idx", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_parent_id_idx": { + "name": "documents_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_knowledge_base_id_idx": { + "name": "documents_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_client_id_user_id_unique": { + "name": "documents_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_slug_user_id_unique": { + "name": "documents_slug_user_id_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"documents\".\"slug\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_file_id_files_id_fk": { + "name": "documents_file_id_files_id_fk", + "tableFrom": "documents", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_knowledge_base_id_knowledge_bases_id_fk": { + "name": "documents_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "documents", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_parent_id_documents_id_fk": { + "name": "documents_parent_id_documents_id_fk", + "tableFrom": "documents", + "tableTo": "documents", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_user_id_users_id_fk": { + "name": "documents_user_id_users_id_fk", + "tableFrom": "documents", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_type": { + "name": "file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_task_id": { + "name": "chunk_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "embedding_task_id": { + "name": "embedding_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "file_hash_idx": { + "name": "file_hash_idx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "files_user_id_idx": { + "name": "files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "files_parent_id_idx": { + "name": "files_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "files_client_id_user_id_unique": { + "name": "files_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "files_user_id_users_id_fk": { + "name": "files_user_id_users_id_fk", + "tableFrom": "files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "files_file_hash_global_files_hash_id_fk": { + "name": "files_file_hash_global_files_hash_id_fk", + "tableFrom": "files", + "tableTo": "global_files", + "columnsFrom": ["file_hash"], + "columnsTo": ["hash_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "files_parent_id_documents_id_fk": { + "name": "files_parent_id_documents_id_fk", + "tableFrom": "files", + "tableTo": "documents", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "files_chunk_task_id_async_tasks_id_fk": { + "name": "files_chunk_task_id_async_tasks_id_fk", + "tableFrom": "files", + "tableTo": "async_tasks", + "columnsFrom": ["chunk_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "files_embedding_task_id_async_tasks_id_fk": { + "name": "files_embedding_task_id_async_tasks_id_fk", + "tableFrom": "files", + "tableTo": "async_tasks", + "columnsFrom": ["embedding_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.global_files": { + "name": "global_files", + "schema": "", + "columns": { + "hash_id": { + "name": "hash_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "file_type": { + "name": "file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator": { + "name": "creator", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "global_files_creator_idx": { + "name": "global_files_creator_idx", + "columns": [ + { + "expression": "creator", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "global_files_creator_users_id_fk": { + "name": "global_files_creator_users_id_fk", + "tableFrom": "global_files", + "tableTo": "users", + "columnsFrom": ["creator"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_files": { + "name": "knowledge_base_files", + "schema": "", + "columns": { + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "knowledge_base_files_kb_id_idx": { + "name": "knowledge_base_files_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_files_knowledge_base_id_knowledge_bases_id_fk": { + "name": "knowledge_base_files_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "knowledge_base_files", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_files_file_id_files_id_fk": { + "name": "knowledge_base_files_file_id_files_id_fk", + "tableFrom": "knowledge_base_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_files_user_id_users_id_fk": { + "name": "knowledge_base_files_user_id_users_id_fk", + "tableFrom": "knowledge_base_files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_base_files_knowledge_base_id_file_id_pk": { + "name": "knowledge_base_files_knowledge_base_id_file_id_pk", + "columns": ["knowledge_base_id", "file_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_bases": { + "name": "knowledge_bases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "knowledge_bases_client_id_user_id_unique": { + "name": "knowledge_bases_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "knowledge_bases_user_id_idx": { + "name": "knowledge_bases_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_bases_user_id_users_id_fk": { + "name": "knowledge_bases_user_id_users_id_fk", + "tableFrom": "knowledge_bases", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.generation_batches": { + "name": "generation_batches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_topic_id": { + "name": "generation_topic_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ratio": { + "name": "ratio", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "generation_batches_user_id_idx": { + "name": "generation_batches_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "generation_batches_topic_id_idx": { + "name": "generation_batches_topic_id_idx", + "columns": [ + { + "expression": "generation_topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "generation_batches_user_id_users_id_fk": { + "name": "generation_batches_user_id_users_id_fk", + "tableFrom": "generation_batches", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "generation_batches_generation_topic_id_generation_topics_id_fk": { + "name": "generation_batches_generation_topic_id_generation_topics_id_fk", + "tableFrom": "generation_batches", + "tableTo": "generation_topics", + "columnsFrom": ["generation_topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.generation_topics": { + "name": "generation_topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "generation_topics_user_id_idx": { + "name": "generation_topics_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "generation_topics_user_id_users_id_fk": { + "name": "generation_topics_user_id_users_id_fk", + "tableFrom": "generation_topics", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.generations": { + "name": "generations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_batch_id": { + "name": "generation_batch_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "async_task_id": { + "name": "async_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seed": { + "name": "seed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "asset": { + "name": "asset", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "generations_user_id_idx": { + "name": "generations_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "generations_batch_id_idx": { + "name": "generations_batch_id_idx", + "columns": [ + { + "expression": "generation_batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "generations_user_id_users_id_fk": { + "name": "generations_user_id_users_id_fk", + "tableFrom": "generations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "generations_generation_batch_id_generation_batches_id_fk": { + "name": "generations_generation_batch_id_generation_batches_id_fk", + "tableFrom": "generations", + "tableTo": "generation_batches", + "columnsFrom": ["generation_batch_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "generations_async_task_id_async_tasks_id_fk": { + "name": "generations_async_task_id_async_tasks_id_fk", + "tableFrom": "generations", + "tableTo": "async_tasks", + "columnsFrom": ["async_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "generations_file_id_files_id_fk": { + "name": "generations_file_id_files_id_fk", + "tableFrom": "generations", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_chunks": { + "name": "message_chunks", + "schema": "", + "columns": { + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_chunks_message_id_messages_id_fk": { + "name": "message_chunks_message_id_messages_id_fk", + "tableFrom": "message_chunks", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_chunks_chunk_id_chunks_id_fk": { + "name": "message_chunks_chunk_id_chunks_id_fk", + "tableFrom": "message_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_chunks_user_id_users_id_fk": { + "name": "message_chunks_user_id_users_id_fk", + "tableFrom": "message_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_chunks_chunk_id_message_id_pk": { + "name": "message_chunks_chunk_id_message_id_pk", + "columns": ["chunk_id", "message_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_groups": { + "name": "message_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_group_id": { + "name": "parent_group_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editor_data": { + "name": "editor_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "message_groups_client_id_user_id_unique": { + "name": "message_groups_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_groups_topic_id_idx": { + "name": "message_groups_topic_id_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_groups_type_idx": { + "name": "message_groups_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_groups_topic_id_topics_id_fk": { + "name": "message_groups_topic_id_topics_id_fk", + "tableFrom": "message_groups", + "tableTo": "topics", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_groups_user_id_users_id_fk": { + "name": "message_groups_user_id_users_id_fk", + "tableFrom": "message_groups", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_groups_parent_group_id_message_groups_id_fk": { + "name": "message_groups_parent_group_id_message_groups_id_fk", + "tableFrom": "message_groups", + "tableTo": "message_groups", + "columnsFrom": ["parent_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_groups_parent_message_id_messages_id_fk": { + "name": "message_groups_parent_message_id_messages_id_fk", + "tableFrom": "message_groups", + "tableTo": "messages", + "columnsFrom": ["parent_message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_plugins": { + "name": "message_plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "intervention": { + "name": "intervention", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "api_name": { + "name": "api_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments": { + "name": "arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "message_plugins_client_id_user_id_unique": { + "name": "message_plugins_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_plugins_tool_call_id_idx": { + "name": "message_plugins_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_plugins_id_messages_id_fk": { + "name": "message_plugins_id_messages_id_fk", + "tableFrom": "message_plugins", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_plugins_user_id_users_id_fk": { + "name": "message_plugins_user_id_users_id_fk", + "tableFrom": "message_plugins", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_queries": { + "name": "message_queries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rewrite_query": { + "name": "rewrite_query", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embeddings_id": { + "name": "embeddings_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "message_queries_client_id_user_id_unique": { + "name": "message_queries_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_queries_message_id_messages_id_fk": { + "name": "message_queries_message_id_messages_id_fk", + "tableFrom": "message_queries", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_queries_user_id_users_id_fk": { + "name": "message_queries_user_id_users_id_fk", + "tableFrom": "message_queries", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_queries_embeddings_id_embeddings_id_fk": { + "name": "message_queries_embeddings_id_embeddings_id_fk", + "tableFrom": "message_queries", + "tableTo": "embeddings", + "columnsFrom": ["embeddings_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_query_chunks": { + "name": "message_query_chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_id": { + "name": "query_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "similarity": { + "name": "similarity", + "type": "numeric(6, 5)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_query_chunks_id_messages_id_fk": { + "name": "message_query_chunks_id_messages_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_query_chunks_query_id_message_queries_id_fk": { + "name": "message_query_chunks_query_id_message_queries_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "message_queries", + "columnsFrom": ["query_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_query_chunks_chunk_id_chunks_id_fk": { + "name": "message_query_chunks_chunk_id_chunks_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_query_chunks_user_id_users_id_fk": { + "name": "message_query_chunks_user_id_users_id_fk", + "tableFrom": "message_query_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_query_chunks_chunk_id_id_query_id_pk": { + "name": "message_query_chunks_chunk_id_id_query_id_pk", + "columns": ["chunk_id", "id", "query_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_tts": { + "name": "message_tts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_md5": { + "name": "content_md5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "voice": { + "name": "voice", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "message_tts_client_id_user_id_unique": { + "name": "message_tts_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_tts_id_messages_id_fk": { + "name": "message_tts_id_messages_id_fk", + "tableFrom": "message_tts", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_tts_file_id_files_id_fk": { + "name": "message_tts_file_id_files_id_fk", + "tableFrom": "message_tts", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_tts_user_id_users_id_fk": { + "name": "message_tts_user_id_users_id_fk", + "tableFrom": "message_tts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_translates": { + "name": "message_translates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from": { + "name": "from", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to": { + "name": "to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "message_translates_client_id_user_id_unique": { + "name": "message_translates_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_translates_id_messages_id_fk": { + "name": "message_translates_id_messages_id_fk", + "tableFrom": "message_translates", + "tableTo": "messages", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_translates_user_id_users_id_fk": { + "name": "message_translates_user_id_users_id_fk", + "tableFrom": "message_translates", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editor_data": { + "name": "editor_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reasoning": { + "name": "reasoning", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "search": { + "name": "search", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favorite": { + "name": "favorite", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observation_id": { + "name": "observation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quota_id": { + "name": "quota_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message_group_id": { + "name": "message_group_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "messages_created_at_idx": { + "name": "messages_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_client_id_user_unique": { + "name": "message_client_id_user_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_topic_id_idx": { + "name": "messages_topic_id_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_parent_id_idx": { + "name": "messages_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_quota_id_idx": { + "name": "messages_quota_id_idx", + "columns": [ + { + "expression": "quota_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_user_id_idx": { + "name": "messages_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_session_id_idx": { + "name": "messages_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_thread_id_idx": { + "name": "messages_thread_id_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_agent_id_idx": { + "name": "messages_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_group_id_idx": { + "name": "messages_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_session_id_sessions_id_fk": { + "name": "messages_session_id_sessions_id_fk", + "tableFrom": "messages", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_topic_id_topics_id_fk": { + "name": "messages_topic_id_topics_id_fk", + "tableFrom": "messages", + "tableTo": "topics", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_thread_id_threads_id_fk": { + "name": "messages_thread_id_threads_id_fk", + "tableFrom": "messages", + "tableTo": "threads", + "columnsFrom": ["thread_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_parent_id_messages_id_fk": { + "name": "messages_parent_id_messages_id_fk", + "tableFrom": "messages", + "tableTo": "messages", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_quota_id_messages_id_fk": { + "name": "messages_quota_id_messages_id_fk", + "tableFrom": "messages", + "tableTo": "messages", + "columnsFrom": ["quota_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_agent_id_agents_id_fk": { + "name": "messages_agent_id_agents_id_fk", + "tableFrom": "messages", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_group_id_chat_groups_id_fk": { + "name": "messages_group_id_chat_groups_id_fk", + "tableFrom": "messages", + "tableTo": "chat_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_message_group_id_message_groups_id_fk": { + "name": "messages_message_group_id_message_groups_id_fk", + "tableFrom": "messages", + "tableTo": "message_groups", + "columnsFrom": ["message_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages_files": { + "name": "messages_files", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "messages_files_file_id_files_id_fk": { + "name": "messages_files_file_id_files_id_fk", + "tableFrom": "messages_files", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_files_message_id_messages_id_fk": { + "name": "messages_files_message_id_messages_id_fk", + "tableFrom": "messages_files", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_files_user_id_users_id_fk": { + "name": "messages_files_user_id_users_id_fk", + "tableFrom": "messages_files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "messages_files_file_id_message_id_pk": { + "name": "messages_files_file_id_message_id_pk", + "columns": ["file_id", "message_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nextauth_accounts": { + "name": "nextauth_accounts", + "schema": "", + "columns": { + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_accounts_user_id_users_id_fk": { + "name": "nextauth_accounts_user_id_users_id_fk", + "tableFrom": "nextauth_accounts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "nextauth_accounts_provider_providerAccountId_pk": { + "name": "nextauth_accounts_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nextauth_authenticators": { + "name": "nextauth_authenticators", + "schema": "", + "columns": { + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentialBackedUp": { + "name": "credentialBackedUp", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "credentialDeviceType": { + "name": "credentialDeviceType", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialID": { + "name": "credentialID", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentialPublicKey": { + "name": "credentialPublicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_authenticators_user_id_users_id_fk": { + "name": "nextauth_authenticators_user_id_users_id_fk", + "tableFrom": "nextauth_authenticators", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "nextauth_authenticators_user_id_credentialID_pk": { + "name": "nextauth_authenticators_user_id_credentialID_pk", + "columns": ["user_id", "credentialID"] + } + }, + "uniqueConstraints": { + "nextauth_authenticators_credentialID_unique": { + "name": "nextauth_authenticators_credentialID_unique", + "nullsNotDistinct": false, + "columns": ["credentialID"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nextauth_sessions": { + "name": "nextauth_sessions", + "schema": "", + "columns": { + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nextauth_sessions_user_id_users_id_fk": { + "name": "nextauth_sessions_user_id_users_id_fk", + "tableFrom": "nextauth_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nextauth_verificationtokens": { + "name": "nextauth_verificationtokens", + "schema": "", + "columns": { + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "nextauth_verificationtokens_identifier_token_pk": { + "name": "nextauth_verificationtokens_identifier_token_pk", + "columns": ["identifier", "token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_handoffs": { + "name": "oauth_handoffs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client": { + "name": "client", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_access_tokens": { + "name": "oidc_access_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_access_tokens_user_id_users_id_fk": { + "name": "oidc_access_tokens_user_id_users_id_fk", + "tableFrom": "oidc_access_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_authorization_codes": { + "name": "oidc_authorization_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_authorization_codes_user_id_users_id_fk": { + "name": "oidc_authorization_codes_user_id_users_id_fk", + "tableFrom": "oidc_authorization_codes", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_clients": { + "name": "oidc_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "grants": { + "name": "grants", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "application_type": { + "name": "application_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "client_uri": { + "name": "client_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo_uri": { + "name": "logo_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_uri": { + "name": "policy_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tos_uri": { + "name": "tos_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_first_party": { + "name": "is_first_party", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_consents": { + "name": "oidc_consents", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_consents_user_id_users_id_fk": { + "name": "oidc_consents_user_id_users_id_fk", + "tableFrom": "oidc_consents", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_consents_client_id_oidc_clients_id_fk": { + "name": "oidc_consents_client_id_oidc_clients_id_fk", + "tableFrom": "oidc_consents", + "tableTo": "oidc_clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "oidc_consents_user_id_client_id_pk": { + "name": "oidc_consents_user_id_client_id_pk", + "columns": ["user_id", "client_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_device_codes": { + "name": "oidc_device_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_code": { + "name": "user_code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_device_codes_user_id_users_id_fk": { + "name": "oidc_device_codes_user_id_users_id_fk", + "tableFrom": "oidc_device_codes", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_grants": { + "name": "oidc_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_grants_user_id_users_id_fk": { + "name": "oidc_grants_user_id_users_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_interactions": { + "name": "oidc_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_refresh_tokens": { + "name": "oidc_refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_refresh_tokens_user_id_users_id_fk": { + "name": "oidc_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_sessions": { + "name": "oidc_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_sessions_user_id_users_id_fk": { + "name": "oidc_sessions_user_id_users_id_fk", + "tableFrom": "oidc_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chunks": { + "name": "chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "abstract": { + "name": "abstract", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chunks_client_id_user_id_unique": { + "name": "chunks_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chunks_user_id_idx": { + "name": "chunks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chunks_user_id_users_id_fk": { + "name": "chunks_user_id_users_id_fk", + "tableFrom": "chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_chunks": { + "name": "document_chunks", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "page_index": { + "name": "page_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_chunks_document_id_idx": { + "name": "document_chunks_document_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_chunks_chunk_id_idx": { + "name": "document_chunks_chunk_id_idx", + "columns": [ + { + "expression": "chunk_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_chunks_document_id_documents_id_fk": { + "name": "document_chunks_document_id_documents_id_fk", + "tableFrom": "document_chunks", + "tableTo": "documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_chunks_chunk_id_chunks_id_fk": { + "name": "document_chunks_chunk_id_chunks_id_fk", + "tableFrom": "document_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_chunks_user_id_users_id_fk": { + "name": "document_chunks_user_id_users_id_fk", + "tableFrom": "document_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "document_chunks_document_id_chunk_id_pk": { + "name": "document_chunks_document_id_chunk_id_pk", + "columns": ["document_id", "chunk_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embeddings": { + "name": "embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "embeddings": { + "name": "embeddings", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "embeddings_client_id_user_id_unique": { + "name": "embeddings_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embeddings_chunk_id_idx": { + "name": "embeddings_chunk_id_idx", + "columns": [ + { + "expression": "chunk_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embeddings_user_id_idx": { + "name": "embeddings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "embeddings_chunk_id_chunks_id_fk": { + "name": "embeddings_chunk_id_chunks_id_fk", + "tableFrom": "embeddings", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embeddings_user_id_users_id_fk": { + "name": "embeddings_user_id_users_id_fk", + "tableFrom": "embeddings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "embeddings_chunk_id_unique": { + "name": "embeddings_chunk_id_unique", + "nullsNotDistinct": false, + "columns": ["chunk_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.unstructured_chunks": { + "name": "unstructured_chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "parent_id": { + "name": "parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "composite_id": { + "name": "composite_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "unstructured_chunks_client_id_user_id_unique": { + "name": "unstructured_chunks_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "unstructured_chunks_composite_id_chunks_id_fk": { + "name": "unstructured_chunks_composite_id_chunks_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "chunks", + "columnsFrom": ["composite_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "unstructured_chunks_user_id_users_id_fk": { + "name": "unstructured_chunks_user_id_users_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "unstructured_chunks_file_id_files_id_fk": { + "name": "unstructured_chunks_file_id_files_id_fk", + "tableFrom": "unstructured_chunks", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rag_eval_dataset_records": { + "name": "rag_eval_dataset_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_dataset_records_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "dataset_id": { + "name": "dataset_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ideal": { + "name": "ideal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_files": { + "name": "reference_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_dataset_records_dataset_id_rag_eval_datasets_id_fk": { + "name": "rag_eval_dataset_records_dataset_id_rag_eval_datasets_id_fk", + "tableFrom": "rag_eval_dataset_records", + "tableTo": "rag_eval_datasets", + "columnsFrom": ["dataset_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_dataset_records_user_id_users_id_fk": { + "name": "rag_eval_dataset_records_user_id_users_id_fk", + "tableFrom": "rag_eval_dataset_records", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rag_eval_datasets": { + "name": "rag_eval_datasets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_datasets_id_seq", + "schema": "public", + "increment": "1", + "startWith": "30000", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_datasets_knowledge_base_id_knowledge_bases_id_fk": { + "name": "rag_eval_datasets_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "rag_eval_datasets", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_datasets_user_id_users_id_fk": { + "name": "rag_eval_datasets_user_id_users_id_fk", + "tableFrom": "rag_eval_datasets", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rag_eval_evaluations": { + "name": "rag_eval_evaluations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_evaluations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eval_records_url": { + "name": "eval_records_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dataset_id": { + "name": "dataset_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_evaluations_dataset_id_rag_eval_datasets_id_fk": { + "name": "rag_eval_evaluations_dataset_id_rag_eval_datasets_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "rag_eval_datasets", + "columnsFrom": ["dataset_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluations_knowledge_base_id_knowledge_bases_id_fk": { + "name": "rag_eval_evaluations_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "knowledge_bases", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluations_user_id_users_id_fk": { + "name": "rag_eval_evaluations_user_id_users_id_fk", + "tableFrom": "rag_eval_evaluations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rag_eval_evaluation_records": { + "name": "rag_eval_evaluation_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "rag_eval_evaluation_records_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "ideal": { + "name": "ideal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "question_embedding_id": { + "name": "question_embedding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "dataset_record_id": { + "name": "dataset_record_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "evaluation_id": { + "name": "evaluation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "rag_eval_evaluation_records_question_embedding_id_embeddings_id_fk": { + "name": "rag_eval_evaluation_records_question_embedding_id_embeddings_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "embeddings", + "columnsFrom": ["question_embedding_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_dataset_record_id_rag_eval_dataset_records_id_fk": { + "name": "rag_eval_evaluation_records_dataset_record_id_rag_eval_dataset_records_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "rag_eval_dataset_records", + "columnsFrom": ["dataset_record_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_evaluation_id_rag_eval_evaluations_id_fk": { + "name": "rag_eval_evaluation_records_evaluation_id_rag_eval_evaluations_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "rag_eval_evaluations", + "columnsFrom": ["evaluation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rag_eval_evaluation_records_user_id_users_id_fk": { + "name": "rag_eval_evaluation_records_user_id_users_id_fk", + "tableFrom": "rag_eval_evaluation_records", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_permissions": { + "name": "rbac_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "rbac_permissions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "rbac_permissions_code_unique": { + "name": "rbac_permissions_code_unique", + "nullsNotDistinct": false, + "columns": ["code"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_role_permissions": { + "name": "rbac_role_permissions", + "schema": "", + "columns": { + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "permission_id": { + "name": "permission_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rbac_role_permissions_role_id_idx": { + "name": "rbac_role_permissions_role_id_idx", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rbac_role_permissions_permission_id_idx": { + "name": "rbac_role_permissions_permission_id_idx", + "columns": [ + { + "expression": "permission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rbac_role_permissions_role_id_rbac_roles_id_fk": { + "name": "rbac_role_permissions_role_id_rbac_roles_id_fk", + "tableFrom": "rbac_role_permissions", + "tableTo": "rbac_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rbac_role_permissions_permission_id_rbac_permissions_id_fk": { + "name": "rbac_role_permissions_permission_id_rbac_permissions_id_fk", + "tableFrom": "rbac_role_permissions", + "tableTo": "rbac_permissions", + "columnsFrom": ["permission_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "rbac_role_permissions_role_id_permission_id_pk": { + "name": "rbac_role_permissions_role_id_permission_id_pk", + "columns": ["role_id", "permission_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_roles": { + "name": "rbac_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "rbac_roles_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "rbac_roles_name_unique": { + "name": "rbac_roles_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rbac_user_roles": { + "name": "rbac_user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "rbac_user_roles_user_id_idx": { + "name": "rbac_user_roles_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rbac_user_roles_role_id_idx": { + "name": "rbac_user_roles_role_id_idx", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rbac_user_roles_user_id_users_id_fk": { + "name": "rbac_user_roles_user_id_users_id_fk", + "tableFrom": "rbac_user_roles", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "rbac_user_roles_role_id_rbac_roles_id_fk": { + "name": "rbac_user_roles_role_id_rbac_roles_id_fk", + "tableFrom": "rbac_user_roles", + "tableTo": "rbac_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "rbac_user_roles_user_id_role_id_pk": { + "name": "rbac_user_roles_user_id_role_id_pk", + "columns": ["user_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents_to_sessions": { + "name": "agents_to_sessions", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "agents_to_sessions_session_id_idx": { + "name": "agents_to_sessions_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_to_sessions_agent_id_idx": { + "name": "agents_to_sessions_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_to_sessions_agent_id_agents_id_fk": { + "name": "agents_to_sessions_agent_id_agents_id_fk", + "tableFrom": "agents_to_sessions", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_to_sessions_session_id_sessions_id_fk": { + "name": "agents_to_sessions_session_id_sessions_id_fk", + "tableFrom": "agents_to_sessions", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agents_to_sessions_user_id_users_id_fk": { + "name": "agents_to_sessions_user_id_users_id_fk", + "tableFrom": "agents_to_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agents_to_sessions_agent_id_session_id_pk": { + "name": "agents_to_sessions_agent_id_session_id_pk", + "columns": ["agent_id", "session_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.file_chunks": { + "name": "file_chunks", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "file_chunks_file_id_files_id_fk": { + "name": "file_chunks_file_id_files_id_fk", + "tableFrom": "file_chunks", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_chunks_chunk_id_chunks_id_fk": { + "name": "file_chunks_chunk_id_chunks_id_fk", + "tableFrom": "file_chunks", + "tableTo": "chunks", + "columnsFrom": ["chunk_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_chunks_user_id_users_id_fk": { + "name": "file_chunks_user_id_users_id_fk", + "tableFrom": "file_chunks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "file_chunks_file_id_chunk_id_pk": { + "name": "file_chunks_file_id_chunk_id_pk", + "columns": ["file_id", "chunk_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files_to_sessions": { + "name": "files_to_sessions", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "files_to_sessions_file_id_files_id_fk": { + "name": "files_to_sessions_file_id_files_id_fk", + "tableFrom": "files_to_sessions", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "files_to_sessions_session_id_sessions_id_fk": { + "name": "files_to_sessions_session_id_sessions_id_fk", + "tableFrom": "files_to_sessions", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "files_to_sessions_user_id_users_id_fk": { + "name": "files_to_sessions_user_id_users_id_fk", + "tableFrom": "files_to_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "files_to_sessions_file_id_session_id_pk": { + "name": "files_to_sessions_file_id_session_id_pk", + "columns": ["file_id", "session_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_groups": { + "name": "session_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_groups_client_id_user_id_unique": { + "name": "session_groups_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_groups_user_id_users_id_fk": { + "name": "session_groups_user_id_users_id_fk", + "tableFrom": "session_groups", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background_color": { + "name": "background_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'agent'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slug_user_id_unique": { + "name": "slug_user_id_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_client_id_user_id_unique": { + "name": "sessions_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_id_user_id_idx": { + "name": "sessions_id_user_id_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_user_id_updated_at_idx": { + "name": "sessions_user_id_updated_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_group_id_idx": { + "name": "sessions_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sessions_group_id_session_groups_id_fk": { + "name": "sessions_group_id_session_groups_id_fk", + "tableFrom": "sessions", + "tableTo": "session_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.threads": { + "name": "threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editor_data": { + "name": "editor_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_message_id": { + "name": "source_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_thread_id": { + "name": "parent_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "threads_client_id_user_id_unique": { + "name": "threads_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "threads_topic_id_idx": { + "name": "threads_topic_id_idx", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "threads_agent_id_idx": { + "name": "threads_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "threads_group_id_idx": { + "name": "threads_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "threads_topic_id_topics_id_fk": { + "name": "threads_topic_id_topics_id_fk", + "tableFrom": "threads", + "tableTo": "topics", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "threads_parent_thread_id_threads_id_fk": { + "name": "threads_parent_thread_id_threads_id_fk", + "tableFrom": "threads", + "tableTo": "threads", + "columnsFrom": ["parent_thread_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "threads_agent_id_agents_id_fk": { + "name": "threads_agent_id_agents_id_fk", + "tableFrom": "threads", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "threads_group_id_chat_groups_id_fk": { + "name": "threads_group_id_chat_groups_id_fk", + "tableFrom": "threads", + "tableTo": "chat_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "threads_user_id_users_id_fk": { + "name": "threads_user_id_users_id_fk", + "tableFrom": "threads", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.topic_documents": { + "name": "topic_documents", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "topic_documents_document_id_documents_id_fk": { + "name": "topic_documents_document_id_documents_id_fk", + "tableFrom": "topic_documents", + "tableTo": "documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "topic_documents_topic_id_topics_id_fk": { + "name": "topic_documents_topic_id_topics_id_fk", + "tableFrom": "topic_documents", + "tableTo": "topics", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "topic_documents_user_id_users_id_fk": { + "name": "topic_documents_user_id_users_id_fk", + "tableFrom": "topic_documents", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "topic_documents_document_id_topic_id_pk": { + "name": "topic_documents_document_id_topic_id_pk", + "columns": ["document_id", "topic_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.topic_shares": { + "name": "topic_shares", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "page_view_count": { + "name": "page_view_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "topic_shares_topic_id_unique": { + "name": "topic_shares_topic_id_unique", + "columns": [ + { + "expression": "topic_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topic_shares_user_id_idx": { + "name": "topic_shares_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "topic_shares_topic_id_topics_id_fk": { + "name": "topic_shares_topic_id_topics_id_fk", + "tableFrom": "topic_shares", + "tableTo": "topics", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "topic_shares_user_id_users_id_fk": { + "name": "topic_shares_user_id_users_id_fk", + "tableFrom": "topic_shares", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.topics": { + "name": "topics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favorite": { + "name": "favorite", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editor_data": { + "name": "editor_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "history_summary": { + "name": "history_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "topics_client_id_user_id_unique": { + "name": "topics_client_id_user_id_unique", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_user_id_idx": { + "name": "topics_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_id_user_id_idx": { + "name": "topics_id_user_id_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_session_id_idx": { + "name": "topics_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_group_id_idx": { + "name": "topics_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_agent_id_idx": { + "name": "topics_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_extract_status_gin_idx": { + "name": "topics_extract_status_gin_idx", + "columns": [ + { + "expression": "(metadata->'userMemoryExtractStatus') jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "topics_session_id_sessions_id_fk": { + "name": "topics_session_id_sessions_id_fk", + "tableFrom": "topics", + "tableTo": "sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "topics_agent_id_agents_id_fk": { + "name": "topics_agent_id_agents_id_fk", + "tableFrom": "topics", + "tableTo": "agents", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "topics_group_id_chat_groups_id_fk": { + "name": "topics_group_id_chat_groups_id_fk", + "tableFrom": "topics", + "tableTo": "chat_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "topics_user_id_users_id_fk": { + "name": "topics_user_id_users_id_fk", + "tableFrom": "topics", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_installed_plugins": { + "name": "user_installed_plugins", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_params": { + "name": "custom_params", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_installed_plugins_user_id_users_id_fk": { + "name": "user_installed_plugins_user_id_users_id_fk", + "tableFrom": "user_installed_plugins", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_installed_plugins_user_id_identifier_pk": { + "name": "user_installed_plugins_user_id_identifier_pk", + "columns": ["user_id", "identifier"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_settings": { + "name": "user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tts": { + "name": "tts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "hotkey": { + "name": "hotkey", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "key_vaults": { + "name": "key_vaults", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "language_model": { + "name": "language_model", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "system_agent": { + "name": "system_agent", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "default_agent": { + "name": "default_agent", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "market": { + "name": "market", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "memory": { + "name": "memory", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool": { + "name": "tool", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_settings_id_users_id_fk": { + "name": "user_settings_id_users_id_fk", + "tableFrom": "user_settings", + "tableTo": "users", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interests": { + "name": "interests", + "type": "varchar(64)[]", + "primaryKey": false, + "notNull": false + }, + "is_onboarded": { + "name": "is_onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "onboarding": { + "name": "onboarding", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "clerk_created_at": { + "name": "clerk_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "preference": { + "name": "preference", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "phone_number_verified": { + "name": "phone_number_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_username_idx": { + "name": "users_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_banned_true_created_at_idx": { + "name": "users_banned_true_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"banned\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "users_normalized_email_unique": { + "name": "users_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + }, + "users_phone_unique": { + "name": "users_phone_unique", + "nullsNotDistinct": false, + "columns": ["phone"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_memories": { + "name": "user_memories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memory_category": { + "name": "memory_category", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "memory_layer": { + "name": "memory_layer", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "memory_type": { + "name": "memory_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_vector_1024": { + "name": "summary_vector_1024", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details_vector_1024": { + "name": "details_vector_1024", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "accessed_count": { + "name": "accessed_count", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_memories_summary_vector_1024_index": { + "name": "user_memories_summary_vector_1024_index", + "columns": [ + { + "expression": "summary_vector_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "user_memories_details_vector_1024_index": { + "name": "user_memories_details_vector_1024_index", + "columns": [ + { + "expression": "details_vector_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "user_memories_user_id_index": { + "name": "user_memories_user_id_index", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_memories_user_id_users_id_fk": { + "name": "user_memories_user_id_users_id_fk", + "tableFrom": "user_memories", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_memories_contexts": { + "name": "user_memories_contexts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_memory_ids": { + "name": "user_memory_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "associated_objects": { + "name": "associated_objects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "associated_subjects": { + "name": "associated_subjects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description_vector": { + "name": "description_vector", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "current_status": { + "name": "current_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "score_impact": { + "name": "score_impact", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "score_urgency": { + "name": "score_urgency", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_memories_contexts_description_vector_index": { + "name": "user_memories_contexts_description_vector_index", + "columns": [ + { + "expression": "description_vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "user_memories_contexts_type_index": { + "name": "user_memories_contexts_type_index", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_memories_contexts_user_id_index": { + "name": "user_memories_contexts_user_id_index", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_memories_contexts_user_id_users_id_fk": { + "name": "user_memories_contexts_user_id_users_id_fk", + "tableFrom": "user_memories_contexts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_memories_experiences": { + "name": "user_memories_experiences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_memory_id": { + "name": "user_memory_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "situation": { + "name": "situation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "situation_vector": { + "name": "situation_vector", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "reasoning": { + "name": "reasoning", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "possible_outcome": { + "name": "possible_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_vector": { + "name": "action_vector", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "key_learning": { + "name": "key_learning", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_learning_vector": { + "name": "key_learning_vector", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "score_confidence": { + "name": "score_confidence", + "type": "real", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_memories_experiences_situation_vector_index": { + "name": "user_memories_experiences_situation_vector_index", + "columns": [ + { + "expression": "situation_vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "user_memories_experiences_action_vector_index": { + "name": "user_memories_experiences_action_vector_index", + "columns": [ + { + "expression": "action_vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "user_memories_experiences_key_learning_vector_index": { + "name": "user_memories_experiences_key_learning_vector_index", + "columns": [ + { + "expression": "key_learning_vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "user_memories_experiences_type_index": { + "name": "user_memories_experiences_type_index", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_memories_experiences_user_id_index": { + "name": "user_memories_experiences_user_id_index", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_memories_experiences_user_memory_id_index": { + "name": "user_memories_experiences_user_memory_id_index", + "columns": [ + { + "expression": "user_memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_memories_experiences_user_id_users_id_fk": { + "name": "user_memories_experiences_user_id_users_id_fk", + "tableFrom": "user_memories_experiences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_memories_experiences_user_memory_id_user_memories_id_fk": { + "name": "user_memories_experiences_user_memory_id_user_memories_id_fk", + "tableFrom": "user_memories_experiences", + "tableTo": "user_memories", + "columnsFrom": ["user_memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_memories_identities": { + "name": "user_memories_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_memory_id": { + "name": "user_memory_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description_vector": { + "name": "description_vector", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "episodic_date": { + "name": "episodic_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "relationship": { + "name": "relationship", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_memories_identities_description_vector_index": { + "name": "user_memories_identities_description_vector_index", + "columns": [ + { + "expression": "description_vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "user_memories_identities_type_index": { + "name": "user_memories_identities_type_index", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_memories_identities_user_id_index": { + "name": "user_memories_identities_user_id_index", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_memories_identities_user_memory_id_index": { + "name": "user_memories_identities_user_memory_id_index", + "columns": [ + { + "expression": "user_memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_memories_identities_user_id_users_id_fk": { + "name": "user_memories_identities_user_id_users_id_fk", + "tableFrom": "user_memories_identities", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_memories_identities_user_memory_id_user_memories_id_fk": { + "name": "user_memories_identities_user_memory_id_user_memories_id_fk", + "tableFrom": "user_memories_identities", + "tableTo": "user_memories", + "columnsFrom": ["user_memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_memories_preferences": { + "name": "user_memories_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_memory_id": { + "name": "user_memory_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "conclusion_directives": { + "name": "conclusion_directives", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conclusion_directives_vector": { + "name": "conclusion_directives_vector", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "suggestions": { + "name": "suggestions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "score_priority": { + "name": "score_priority", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "accessed_at": { + "name": "accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_memories_preferences_conclusion_directives_vector_index": { + "name": "user_memories_preferences_conclusion_directives_vector_index", + "columns": [ + { + "expression": "conclusion_directives_vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + }, + "user_memories_preferences_user_id_index": { + "name": "user_memories_preferences_user_id_index", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_memories_preferences_user_memory_id_index": { + "name": "user_memories_preferences_user_memory_id_index", + "columns": [ + { + "expression": "user_memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_memories_preferences_user_id_users_id_fk": { + "name": "user_memories_preferences_user_id_users_id_fk", + "tableFrom": "user_memories_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_memories_preferences_user_memory_id_user_memories_id_fk": { + "name": "user_memories_preferences_user_memory_id_user_memories_id_fk", + "tableFrom": "user_memories_preferences", + "tableTo": "user_memories", + "columnsFrom": ["user_memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "version": "7", + "views": {} +} diff --git a/packages/database/migrations/meta/_journal.json b/packages/database/migrations/meta/_journal.json index 0fac90ed3d..77dd762aa0 100644 --- a/packages/database/migrations/meta/_journal.json +++ b/packages/database/migrations/meta/_journal.json @@ -483,6 +483,13 @@ "when": 1768189437504, "tag": "0068_update_group_data", "breakpoints": true + }, + { + "idx": 69, + "version": "7", + "when": 1768303764632, + "tag": "0069_add_topic_shares_table", + "breakpoints": true } ], "version": "6" diff --git a/packages/database/src/models/__tests__/topicShare.test.ts b/packages/database/src/models/__tests__/topicShare.test.ts new file mode 100644 index 0000000000..609bc63acb --- /dev/null +++ b/packages/database/src/models/__tests__/topicShare.test.ts @@ -0,0 +1,318 @@ +// @vitest-environment node +import { TRPCError } from '@trpc/server'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { getTestDB } from '../../core/getTestDB'; +import { agents, sessions, topicShares, topics, users } from '../../schemas'; +import { LobeChatDatabase } from '../../type'; +import { TopicShareModel } from '../topicShare'; + +const serverDB: LobeChatDatabase = await getTestDB(); + +const userId = 'topic-share-test-user-id'; +const userId2 = 'topic-share-test-user-id-2'; +const sessionId = 'topic-share-test-session'; +const topicId = 'topic-share-test-topic'; +const topicId2 = 'topic-share-test-topic-2'; +const agentId = 'topic-share-test-agent'; + +const topicShareModel = new TopicShareModel(serverDB, userId); +const topicShareModel2 = new TopicShareModel(serverDB, userId2); + +describe('TopicShareModel', () => { + beforeEach(async () => { + await serverDB.delete(users); + + // Create test users, sessions, agents and topics + await serverDB.transaction(async (tx) => { + await tx.insert(users).values([{ id: userId }, { id: userId2 }]); + await tx.insert(sessions).values([ + { id: sessionId, userId }, + { id: `${sessionId}-2`, userId: userId2 }, + ]); + await tx.insert(agents).values([{ id: agentId, userId }]); + await tx.insert(topics).values([ + { id: topicId, sessionId, userId, agentId, title: 'Test Topic' }, + { id: topicId2, sessionId, userId, title: 'Test Topic 2' }, + { id: 'user2-topic', sessionId: `${sessionId}-2`, userId: userId2, title: 'User 2 Topic' }, + ]); + }); + }); + + afterEach(async () => { + await serverDB.delete(topicShares); + await serverDB.delete(topics); + await serverDB.delete(agents); + await serverDB.delete(sessions); + await serverDB.delete(users); + }); + + describe('create', () => { + it('should create a share for a topic with default visibility', async () => { + const result = await topicShareModel.create(topicId); + + expect(result).toBeDefined(); + expect(result.topicId).toBe(topicId); + expect(result.userId).toBe(userId); + expect(result.visibility).toBe('private'); + expect(result.id).toBeDefined(); + }); + + it('should create a share with link visibility', async () => { + const result = await topicShareModel.create(topicId, 'link'); + + expect(result.visibility).toBe('link'); + }); + + it('should throw error when topic does not exist', async () => { + await expect(topicShareModel.create('non-existent-topic')).rejects.toThrow( + 'Topic not found or not owned by user', + ); + }); + + it('should throw error when trying to share another users topic', async () => { + await expect(topicShareModel.create('user2-topic')).rejects.toThrow( + 'Topic not found or not owned by user', + ); + }); + }); + + describe('updateVisibility', () => { + it('should update share visibility', async () => { + await topicShareModel.create(topicId, 'private'); + + const result = await topicShareModel.updateVisibility(topicId, 'link'); + + expect(result).toBeDefined(); + expect(result!.visibility).toBe('link'); + }); + + it('should return null when share does not exist', async () => { + const result = await topicShareModel.updateVisibility('non-existent-topic', 'link'); + + expect(result).toBeNull(); + }); + + it('should not update other users share', async () => { + // Create share for user2 + await topicShareModel2.create('user2-topic', 'private'); + + // User1 tries to update user2's share + const result = await topicShareModel.updateVisibility('user2-topic', 'link'); + + expect(result).toBeNull(); + + // Verify user2's share is unchanged + const share = await topicShareModel2.getByTopicId('user2-topic'); + expect(share!.visibility).toBe('private'); + }); + }); + + describe('deleteByTopicId', () => { + it('should delete share by topic id', async () => { + await topicShareModel.create(topicId); + + await topicShareModel.deleteByTopicId(topicId); + + const share = await topicShareModel.getByTopicId(topicId); + expect(share).toBeNull(); + }); + + it('should not delete other users share', async () => { + await topicShareModel2.create('user2-topic'); + + // User1 tries to delete user2's share + await topicShareModel.deleteByTopicId('user2-topic'); + + // User2's share should still exist + const share = await topicShareModel2.getByTopicId('user2-topic'); + expect(share).not.toBeNull(); + }); + }); + + describe('getByTopicId', () => { + it('should get share info by topic id', async () => { + const created = await topicShareModel.create(topicId, 'link'); + + const result = await topicShareModel.getByTopicId(topicId); + + expect(result).toBeDefined(); + expect(result!.id).toBe(created.id); + expect(result!.topicId).toBe(topicId); + expect(result!.visibility).toBe('link'); + }); + + it('should return null when share does not exist', async () => { + const result = await topicShareModel.getByTopicId(topicId); + + expect(result).toBeNull(); + }); + + it('should not return other users share', async () => { + await topicShareModel2.create('user2-topic'); + + const result = await topicShareModel.getByTopicId('user2-topic'); + + expect(result).toBeNull(); + }); + }); + + describe('findByShareId (static)', () => { + it('should find share by share id with topic and agent info', async () => { + const created = await topicShareModel.create(topicId, 'link'); + + const result = await TopicShareModel.findByShareId(serverDB, created.id); + + expect(result).toBeDefined(); + expect(result!.shareId).toBe(created.id); + expect(result!.topicId).toBe(topicId); + expect(result!.title).toBe('Test Topic'); + expect(result!.ownerId).toBe(userId); + expect(result!.visibility).toBe('link'); + expect(result!.agentId).toBe(agentId); + }); + + it('should return null when share does not exist', async () => { + const result = await TopicShareModel.findByShareId(serverDB, 'non-existent-share'); + + expect(result).toBeNull(); + }); + + it('should return share without agent info when topic has no agent', async () => { + const created = await topicShareModel.create(topicId2); + + const result = await TopicShareModel.findByShareId(serverDB, created.id); + + expect(result).toBeDefined(); + expect(result!.agentId).toBeNull(); + }); + }); + + describe('incrementPageViewCount (static)', () => { + it('should increment page view count', async () => { + const created = await topicShareModel.create(topicId); + + // Initial page view count is 0 + const initial = await serverDB.query.topicShares.findFirst({ + where: (t, { eq }) => eq(t.id, created.id), + }); + expect(initial!.pageViewCount).toBe(0); + + // Increment page view count + await TopicShareModel.incrementPageViewCount(serverDB, created.id); + + const after = await serverDB.query.topicShares.findFirst({ + where: (t, { eq }) => eq(t.id, created.id), + }); + expect(after!.pageViewCount).toBe(1); + }); + + it('should increment page view count multiple times', async () => { + const created = await topicShareModel.create(topicId); + + await TopicShareModel.incrementPageViewCount(serverDB, created.id); + await TopicShareModel.incrementPageViewCount(serverDB, created.id); + await TopicShareModel.incrementPageViewCount(serverDB, created.id); + + const result = await serverDB.query.topicShares.findFirst({ + where: (t, { eq }) => eq(t.id, created.id), + }); + expect(result!.pageViewCount).toBe(3); + }); + }); + + describe('findByShareIdWithAccessCheck (static)', () => { + it('should return share for owner regardless of visibility', async () => { + const created = await topicShareModel.create(topicId, 'private'); + + const result = await TopicShareModel.findByShareIdWithAccessCheck( + serverDB, + created.id, + userId, + ); + + expect(result).toBeDefined(); + expect(result.shareId).toBe(created.id); + }); + + it('should return share for anonymous user when visibility is link', async () => { + const created = await topicShareModel.create(topicId, 'link'); + + const result = await TopicShareModel.findByShareIdWithAccessCheck( + serverDB, + created.id, + undefined, + ); + + expect(result).toBeDefined(); + expect(result.shareId).toBe(created.id); + }); + + it('should throw NOT_FOUND when share does not exist', async () => { + await expect( + TopicShareModel.findByShareIdWithAccessCheck(serverDB, 'non-existent', userId), + ).rejects.toThrow(TRPCError); + + try { + await TopicShareModel.findByShareIdWithAccessCheck(serverDB, 'non-existent', userId); + } catch (error) { + expect((error as TRPCError).code).toBe('NOT_FOUND'); + } + }); + + it('should throw FORBIDDEN when visibility is private and user is not owner', async () => { + const created = await topicShareModel.create(topicId, 'private'); + + await expect( + TopicShareModel.findByShareIdWithAccessCheck(serverDB, created.id, userId2), + ).rejects.toThrow(TRPCError); + + try { + await TopicShareModel.findByShareIdWithAccessCheck(serverDB, created.id, userId2); + } catch (error) { + expect((error as TRPCError).code).toBe('FORBIDDEN'); + } + }); + + it('should throw FORBIDDEN when visibility is private and user is anonymous', async () => { + const created = await topicShareModel.create(topicId, 'private'); + + await expect( + TopicShareModel.findByShareIdWithAccessCheck(serverDB, created.id, undefined), + ).rejects.toThrow(TRPCError); + + try { + await TopicShareModel.findByShareIdWithAccessCheck(serverDB, created.id, undefined); + } catch (error) { + expect((error as TRPCError).code).toBe('FORBIDDEN'); + } + }); + }); + + describe('user isolation', () => { + it('should enforce user data isolation for all operations', async () => { + // User1 creates a share + await topicShareModel.create(topicId, 'private'); + + // User2 creates a share + await topicShareModel2.create('user2-topic', 'link'); + + // User1 cannot access user2's share via getByTopicId + const user1Access = await topicShareModel.getByTopicId('user2-topic'); + expect(user1Access).toBeNull(); + + // User2 cannot access user1's share via getByTopicId + const user2Access = await topicShareModel2.getByTopicId(topicId); + expect(user2Access).toBeNull(); + + // User1 cannot update user2's share + const updateResult = await topicShareModel.updateVisibility('user2-topic', 'private'); + expect(updateResult).toBeNull(); + + // User1 cannot delete user2's share + await topicShareModel.deleteByTopicId('user2-topic'); + const stillExists = await topicShareModel2.getByTopicId('user2-topic'); + expect(stillExists).not.toBeNull(); + }); + }); +}); diff --git a/packages/database/src/models/topicShare.ts b/packages/database/src/models/topicShare.ts new file mode 100644 index 0000000000..a05704c8fc --- /dev/null +++ b/packages/database/src/models/topicShare.ts @@ -0,0 +1,177 @@ +import type { ShareVisibility } from '@lobechat/types'; +import { TRPCError } from '@trpc/server'; +import { and, asc, eq, sql } from 'drizzle-orm'; + +import { agents, chatGroups, chatGroupsAgents, topicShares, topics } from '../schemas'; +import { LobeChatDatabase } from '../type'; + +export type TopicShareData = NonNullable< + Awaited> +>; + +export class TopicShareModel { + private userId: string; + private db: LobeChatDatabase; + + constructor(db: LobeChatDatabase, userId: string) { + this.userId = userId; + this.db = db; + } + + /** + * Create a new share for a topic. + * Each topic can only have one share record (enforced by unique constraint). + */ + create = async (topicId: string, visibility: ShareVisibility = 'private') => { + // First verify the topic belongs to the user + const topic = await this.db.query.topics.findFirst({ + where: and(eq(topics.id, topicId), eq(topics.userId, this.userId)), + }); + + if (!topic) { + throw new Error('Topic not found or not owned by user'); + } + + const [result] = await this.db + .insert(topicShares) + .values({ + topicId, + userId: this.userId, + visibility, + }) + .returning(); + + return result; + }; + + /** + * Update share visibility + */ + updateVisibility = async (topicId: string, visibility: ShareVisibility) => { + const [result] = await this.db + .update(topicShares) + .set({ updatedAt: new Date(), visibility }) + .where(and(eq(topicShares.topicId, topicId), eq(topicShares.userId, this.userId))) + .returning(); + + return result || null; + }; + + /** + * Delete a share by topic ID + */ + deleteByTopicId = async (topicId: string) => { + return this.db + .delete(topicShares) + .where(and(eq(topicShares.topicId, topicId), eq(topicShares.userId, this.userId))); + }; + + /** + * Get share info by topic ID (for the owner) + */ + getByTopicId = async (topicId: string) => { + const result = await this.db + .select({ + id: topicShares.id, + topicId: topicShares.topicId, + visibility: topicShares.visibility, + }) + .from(topicShares) + .where(and(eq(topicShares.topicId, topicId), eq(topicShares.userId, this.userId))) + .limit(1); + + return result[0] || null; + }; + + /** + * Find shared topic by share ID. + * Returns share info including ownerId for permission checking by caller. + */ + static findByShareId = async (db: LobeChatDatabase, shareId: string) => { + const result = await db + .select({ + agentAvatar: agents.avatar, + agentBackgroundColor: agents.backgroundColor, + agentId: topics.agentId, + agentMarketIdentifier: agents.marketIdentifier, + agentSlug: agents.slug, + agentTitle: agents.title, + groupAvatar: chatGroups.avatar, + groupBackgroundColor: chatGroups.backgroundColor, + groupId: topics.groupId, + groupTitle: chatGroups.title, + ownerId: topicShares.userId, + shareId: topicShares.id, + title: topics.title, + topicId: topics.id, + visibility: topicShares.visibility, + }) + .from(topicShares) + .innerJoin(topics, eq(topicShares.topicId, topics.id)) + .leftJoin(agents, eq(topics.agentId, agents.id)) + .leftJoin(chatGroups, eq(topics.groupId, chatGroups.id)) + .where(eq(topicShares.id, shareId)) + .limit(1); + + if (!result[0]) return null; + + const share = result[0]; + + // Fetch group members if this is a group topic + let groupMembers: { avatar: string | null; backgroundColor: string | null }[] | undefined; + if (share.groupId) { + const members = await db + .select({ + avatar: agents.avatar, + backgroundColor: agents.backgroundColor, + }) + .from(chatGroupsAgents) + .innerJoin(agents, eq(chatGroupsAgents.agentId, agents.id)) + .where(eq(chatGroupsAgents.chatGroupId, share.groupId)) + .orderBy(asc(chatGroupsAgents.order)) + .limit(4); + + groupMembers = members; + } + + return { ...share, groupMembers }; + }; + + /** + * Increment page view count for a share. + * Should be called after permission check passes. + */ + static incrementPageViewCount = async (db: LobeChatDatabase, shareId: string) => { + await db + .update(topicShares) + .set({ pageViewCount: sql`${topicShares.pageViewCount} + 1` }) + .where(eq(topicShares.id, shareId)); + }; + + /** + * Find shared topic by share ID with visibility check. + * Throws TRPCError if access is denied. + */ + static findByShareIdWithAccessCheck = async ( + db: LobeChatDatabase, + shareId: string, + accessUserId?: string, + ): Promise => { + const share = await TopicShareModel.findByShareId(db, shareId); + + if (!share) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Share not found' }); + } + + const isOwner = accessUserId && share.ownerId === accessUserId; + + // Only check visibility for non-owners + // 'private' - only owner can view + // 'link' - anyone with the link can view + if (!isOwner && share.visibility === 'private') { + throw new TRPCError({ code: 'FORBIDDEN', message: 'This share is private' }); + } + + return share; + }; +} diff --git a/packages/database/src/schemas/topic.ts b/packages/database/src/schemas/topic.ts index 3a62a97d03..240c514c82 100644 --- a/packages/database/src/schemas/topic.ts +++ b/packages/database/src/schemas/topic.ts @@ -1,10 +1,19 @@ /* eslint-disable sort-keys-fix/sort-keys-fix */ import type { ChatTopicMetadata, ThreadMetadata } from '@lobechat/types'; import { sql } from 'drizzle-orm'; -import { boolean, index, jsonb, pgTable, primaryKey, text, uniqueIndex } from 'drizzle-orm/pg-core'; +import { + boolean, + index, + integer, + jsonb, + pgTable, + primaryKey, + text, + uniqueIndex, +} from 'drizzle-orm/pg-core'; import { createInsertSchema } from 'drizzle-zod'; -import { idGenerator } from '../utils/idGenerator'; +import { createNanoId, idGenerator } from '../utils/idGenerator'; import { createdAt, timestamps, timestamptz } from './_helpers'; import { agents } from './agent'; import { chatGroups } from './chatGroup'; @@ -133,3 +142,36 @@ export const topicDocuments = pgTable( export type NewTopicDocument = typeof topicDocuments.$inferInsert; export type TopicDocumentItem = typeof topicDocuments.$inferSelect; + +/** + * Topic sharing table - Manages public sharing links for topics + */ +export const topicShares = pgTable( + 'topic_shares', + { + id: text('id') + .$defaultFn(() => createNanoId(8)()) + .primaryKey(), + + topicId: text('topic_id') + .notNull() + .references(() => topics.id, { onDelete: 'cascade' }), + + userId: text('user_id') + .references(() => users.id, { onDelete: 'cascade' }) + .notNull(), + + visibility: text('visibility').default('private').notNull(), // 'private' | 'link' + + pageViewCount: integer('page_view_count').default(0).notNull(), + + ...timestamps, + }, + (t) => [ + uniqueIndex('topic_shares_topic_id_unique').on(t.topicId), + index('topic_shares_user_id_idx').on(t.userId), + ], +); + +export type NewTopicShare = typeof topicShares.$inferInsert; +export type TopicShareItem = typeof topicShares.$inferSelect; diff --git a/packages/types/src/conversation.ts b/packages/types/src/conversation.ts index 674448922d..62f1715e31 100644 --- a/packages/types/src/conversation.ts +++ b/packages/types/src/conversation.ts @@ -182,4 +182,9 @@ export interface ConversationContext { * Topic ID */ topicId?: string | null; + /** + * Topic share ID for public access (used by shared topic pages) + * When present, allows unauthenticated access to topic messages + */ + topicShareId?: string; } diff --git a/packages/types/src/topic/topic.ts b/packages/types/src/topic/topic.ts index aaae18d96d..1efda5304a 100644 --- a/packages/types/src/topic/topic.ts +++ b/packages/types/src/topic/topic.ts @@ -1,6 +1,8 @@ import type { BaseDataModel } from '../meta'; // Type definitions +export type ShareVisibility = 'private' | 'link'; + export type TimeGroupId = | 'today' | 'yesterday' @@ -126,3 +128,47 @@ export interface QueryTopicParams { isInbox?: boolean; pageSize?: number; } + +/** + * Shared message data for public sharing + */ +export interface SharedMessage { + content: string; + createdAt: Date; + id: string; + role: string; +} + +/** + * Shared topic data returned by public API + */ +export interface SharedTopicData { + agentId: string | null; + agentMeta?: { + avatar?: string | null; + backgroundColor?: string | null; + marketIdentifier?: string | null; + slug?: string | null; + title?: string | null; + }; + groupId: string | null; + groupMeta?: { + avatar?: string | null; + backgroundColor?: string | null; + members?: { avatar: string | null; backgroundColor: string | null }[]; + title?: string | null; + }; + shareId: string; + title: string | null; + topicId: string; + visibility: ShareVisibility; +} + +/** + * Topic share info returned to the owner + */ +export interface TopicShareInfo { + id: string; + topicId: string; + visibility: ShareVisibility; +} diff --git a/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx b/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx index 244b3c56f1..7baaf3956e 100644 --- a/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx +++ b/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx @@ -1,5 +1,6 @@ 'use client'; +import { ENABLE_TOPIC_LINK_SHARE } from '@lobechat/business-const'; import { ActionIcon } from '@lobehub/ui'; import { Share2 } from 'lucide-react'; import dynamic from 'next/dynamic'; @@ -8,8 +9,10 @@ import { useTranslation } from 'react-i18next'; import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens'; import { useWorkspaceModal } from '@/hooks/useWorkspaceModal'; +import { useChatStore } from '@/store/chat'; const ShareModal = dynamic(() => import('@/features/ShareModal')); +const SharePopover = dynamic(() => import('@/features/SharePopover')); interface ShareButtonProps { mobile?: boolean; @@ -20,18 +23,30 @@ interface ShareButtonProps { const ShareButton = memo(({ mobile, setOpen, open }) => { const [isModalOpen, setIsModalOpen] = useWorkspaceModal(open, setOpen); const { t } = useTranslation('common'); + const activeTopicId = useChatStore((s) => s.activeTopicId); + + // Hide share button when no topic exists (no messages sent yet) + if (!activeTopicId) return null; + + const iconButton = ( + setIsModalOpen(true)} + size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE} + title={t('share')} + tooltipProps={{ + placement: 'bottom', + }} + /> + ); return ( <> - setIsModalOpen(true)} - size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE} - title={t('share')} - tooltipProps={{ - placement: 'bottom', - }} - /> + {ENABLE_TOPIC_LINK_SHARE ? ( + setIsModalOpen(true)}>{iconButton} + ) : ( + iconButton + )} setIsModalOpen(false)} open={isModalOpen} /> ); diff --git a/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx b/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx index 244b3c56f1..6c14506e66 100644 --- a/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +++ b/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx @@ -1,5 +1,6 @@ 'use client'; +import { ENABLE_TOPIC_LINK_SHARE } from '@lobechat/business-const'; import { ActionIcon } from '@lobehub/ui'; import { Share2 } from 'lucide-react'; import dynamic from 'next/dynamic'; @@ -8,8 +9,12 @@ import { useTranslation } from 'react-i18next'; import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens'; import { useWorkspaceModal } from '@/hooks/useWorkspaceModal'; +import { useChatStore } from '@/store/chat'; + +console.log('ENABLE_TOPIC_LINK_SHARE', ENABLE_TOPIC_LINK_SHARE); const ShareModal = dynamic(() => import('@/features/ShareModal')); +const SharePopover = dynamic(() => import('@/features/SharePopover')); interface ShareButtonProps { mobile?: boolean; @@ -20,18 +25,30 @@ interface ShareButtonProps { const ShareButton = memo(({ mobile, setOpen, open }) => { const [isModalOpen, setIsModalOpen] = useWorkspaceModal(open, setOpen); const { t } = useTranslation('common'); + const activeTopicId = useChatStore((s) => s.activeTopicId); + + // Hide share button when no topic exists (no messages sent yet) + if (!activeTopicId) return null; + + const iconButton = ( + setIsModalOpen(true)} + size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE} + title={t('share')} + tooltipProps={{ + placement: 'bottom', + }} + /> + ); return ( <> - setIsModalOpen(true)} - size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE} - title={t('share')} - tooltipProps={{ - placement: 'bottom', - }} - /> + {ENABLE_TOPIC_LINK_SHARE ? ( + setIsModalOpen(true)}>{iconButton} + ) : ( + iconButton + )} setIsModalOpen(false)} open={isModalOpen} /> ); diff --git a/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx b/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx index 11aa29f9a2..4da8faded5 100644 --- a/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx +++ b/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx @@ -29,8 +29,7 @@ const AspectRatioSelect = memo( {options?.map((item) => { const [width, height] = item.value.split(':').map(Number); const isWidthGreater = width > height; - const isEqual = width === height; - const isActive = isEqual ? item.value === '1:1' : active === item.value; + const isActive = active === item.value; return ( ({ - actionButton: css` - flex-shrink: 0; - `, - - button: css` - cursor: pointer; - - display: flex; - align-items: center; - justify-content: center; - - min-width: 40px; - height: 32px; - padding-block: 0; - padding-inline: 12px; - border: 1px solid ${cssVar.colorBorder}; - border-radius: ${cssVar.borderRadius}px; - - font-size: 14px; - font-weight: 500; - color: ${cssVar.colorText}; - - background: ${cssVar.colorBgContainer}; - - transition: all 0.2s ease; - - &:hover { - border-color: ${cssVar.colorPrimary}; - background: ${cssVar.colorBgTextHover}; - } - - &:disabled { - cursor: not-allowed; - opacity: 0.5; - - &:hover { - border-color: ${cssVar.colorBorder}; - background: ${cssVar.colorBgContainer}; - } - } - `, - - cancelButton: css` - border-color: ${cssVar.colorBorder}; - color: ${cssVar.colorTextTertiary}; - - &:hover { - border-color: ${cssVar.colorBorderSecondary}; - color: ${cssVar.colorText}; - background: ${cssVar.colorBgTextHover}; - } - `, - - confirmButton: css` - border-color: ${cssVar.colorSuccess}; - color: ${cssVar.colorSuccess}; - - &:hover { - border-color: ${cssVar.colorSuccessHover}; - color: ${cssVar.colorSuccessHover}; - background: ${cssVar.colorSuccessBg}; - } - `, - - container: css` - display: flex; - gap: 8px; - align-items: center; - `, - - editContainer: css` - display: flex; - gap: 8px; - align-items: center; - width: 100%; - `, - - input: css` - flex: 1; - min-width: 80px; - - .ant-input { - font-weight: 500; - text-align: center; - } - `, - - selectedButton: css` - border-color: ${cssVar.colorPrimary}; - color: ${cssVar.colorPrimary}; - background: ${cssVar.colorPrimaryBg}; - - &:hover { - border-color: ${cssVar.colorPrimary}; - color: ${cssVar.colorPrimary}; - background: ${cssVar.colorPrimaryBgHover}; - } - `, -})); +const CUSTOM_VALUE = '__custom__'; interface ImageNumSelectorProps { disabled?: boolean; @@ -130,34 +29,52 @@ const ImageNum = memo( const isCustomValue = !presetCounts.includes(imageNum); - // 处理预设按钮点击 - const handlePresetClick = useCallback( - (count: number) => { + const options = useMemo(() => { + const items = presetCounts.map((count) => ({ + label: String(count), + value: count, + })); + + // Add custom option or show current custom value + if (isCustomValue) { + items.push({ + label: String(imageNum), + value: imageNum, + }); + } else { + items.push({ + label: , + value: CUSTOM_VALUE, + } as any); + } + + return items; + }, [presetCounts, isCustomValue, imageNum]); + + const handleChange = useCallback( + (value: number | string) => { if (disabled) return; - setImageNum(count); + + if (value === CUSTOM_VALUE || (isCustomValue && value === imageNum)) { + // Enter edit mode + setCustomCount(imageNum); + customCountRef.current = imageNum; + setIsEditing(true); + } else { + setImageNum(value as number); + } }, - [disabled, setImageNum], + [disabled, isCustomValue, imageNum, setImageNum], ); - // 进入编辑模式 - const handleEditStart = useCallback(() => { - if (disabled) return; - setCustomCount(imageNum); - customCountRef.current = imageNum; - setIsEditing(true); - }, [disabled, imageNum]); - - // 确认自定义输入 const handleCustomConfirm = useCallback(() => { let count = customCountRef.current; - // 如果解析失败或输入为空,使用当前值 if (count === null) { setIsEditing(false); return; } - // 智能处理超出范围的值 (作为二次保险) if (count > max) { count = max; } else if (count < min) { @@ -174,10 +91,7 @@ const ImageNum = memo( setCustomCount(null); }, []); - // 处理输入变化 const handleInputChange = useCallback((value: number | string | null) => { - console.log('handleInputChange', value); - if (value === null) { setCustomCount(null); customCountRef.current = null; @@ -192,10 +106,8 @@ const ImageNum = memo( } }, []); - // 自动聚焦和选择输入框内容 useEffect(() => { if (isEditing) { - // 延迟聚焦以确保 input 已渲染 setTimeout(() => { if (inputRef.current) { inputRef.current.focus(); @@ -205,16 +117,12 @@ const ImageNum = memo( } }, [isEditing]); - // 验证输入是否有效 - const isValidInput = useCallback(() => { - return customCount !== null; - }, [customCount]); + const isValidInput = customCount !== null; if (isEditing) { return ( -
+ ( placeholder={`${min}-${max}`} ref={inputRef} size="small" + style={{ flex: 1 }} value={customCount} /> - -
+ + ); } return ( - - {presetCounts.map((count) => ( - - ))} - - {isCustomValue ? ( - - ) : ( - - )} - + ); }, ); diff --git a/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx b/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx index a3c7169d39..ae469cad9b 100644 --- a/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx +++ b/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx @@ -1,86 +1,41 @@ -import { Flexbox } from '@lobehub/ui'; -import { createStaticStyles, cx } from 'antd-style'; -import { memo, useCallback } from 'react'; +import { Segmented } from '@lobehub/ui'; +import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useGenerationConfigParam } from '@/store/image/slices/generationConfig/hooks'; -const styles = createStaticStyles(({ css, cssVar }) => ({ - button: css` - cursor: pointer; - - display: flex; - align-items: center; - justify-content: center; - - min-width: 60px; - height: 32px; - padding-block: 0; - padding-inline: 16px; - border: 1px solid ${cssVar.colorBorder}; - border-radius: ${cssVar.borderRadius}px; - - font-size: 14px; - font-weight: 500; - color: ${cssVar.colorText}; - - background: ${cssVar.colorBgContainer}; - - transition: all 0.2s ease; - - &:hover { - border-color: ${cssVar.colorPrimary}; - background: ${cssVar.colorBgTextHover}; - } - `, - - container: css` - display: flex; - gap: 8px; - align-items: center; - `, - - selectedButton: css` - border-color: ${cssVar.colorPrimary}; - color: ${cssVar.colorPrimary}; - background: ${cssVar.colorPrimaryBg}; - - &:hover { - border-color: ${cssVar.colorPrimary}; - color: ${cssVar.colorPrimary}; - background: ${cssVar.colorPrimaryBgHover}; - } - `, -})); - const ResolutionSelect = memo(() => { const { t } = useTranslation('image'); const { value, setValue, enumValues } = useGenerationConfigParam('resolution'); - const handleClick = useCallback( - (resolution: string) => { - setValue(resolution); + const handleChange = useCallback( + (resolution: string | number) => { + setValue(String(resolution)); }, [setValue], ); - if (!enumValues || enumValues.length === 0) { + const options = useMemo(() => { + if (!enumValues || enumValues.length === 0) return []; + return enumValues.map((resolution) => ({ + label: t(`config.resolution.options.${resolution}`, { defaultValue: resolution }), + value: resolution, + })); + }, [enumValues, t]); + + if (options.length === 0) { return null; } return ( - - {enumValues.map((resolution) => ( - - ))} - + ); }); diff --git a/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx b/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx index 74e74e94b9..681c0201d1 100644 --- a/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +++ b/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx @@ -254,4 +254,22 @@ export const mobileRoutes: RouteConfig[] = [ path: '/onboarding', }, ...BusinessMobileRoutesWithoutMainLayout, + + // Share topic route (outside main layout) + { + children: [ + { + element: dynamicElement( + () => import('../../share/t/[id]'), + 'Mobile > Share > Topic', + ), + path: ':id', + }, + ], + element: dynamicElement( + () => import('../../share/t/[id]/_layout'), + 'Mobile > Share > Topic > Layout', + ), + path: '/share/t', + }, ]; diff --git a/src/app/[variants]/router/desktopRouter.config.tsx b/src/app/[variants]/router/desktopRouter.config.tsx index 742a33fb5e..38397bdb80 100644 --- a/src/app/[variants]/router/desktopRouter.config.tsx +++ b/src/app/[variants]/router/desktopRouter.config.tsx @@ -398,6 +398,24 @@ export const desktopRoutes: RouteConfig[] = [ // Onboarding route (outside main layout) ...BusinessDesktopRoutesWithoutMainLayout, + + // Share topic route (outside main layout) + { + children: [ + { + element: dynamicElement( + () => import('../share/t/[id]'), + 'Desktop > Share > Topic', + ), + path: ':id', + }, + ], + element: dynamicElement( + () => import('../share/t/[id]/_layout'), + 'Desktop > Share > Topic > Layout', + ), + path: '/share/t', + }, ]; // Desktop onboarding route (SPA-only) diff --git a/src/app/[variants]/share/t/[id]/SharedMessageList.tsx b/src/app/[variants]/share/t/[id]/SharedMessageList.tsx new file mode 100644 index 0000000000..c5beb0aeb6 --- /dev/null +++ b/src/app/[variants]/share/t/[id]/SharedMessageList.tsx @@ -0,0 +1,54 @@ +'use client'; + +import { Flexbox } from '@lobehub/ui'; +import { memo, useCallback, useMemo } from 'react'; + +import { ChatList, ConversationProvider, MessageItem } from '@/features/Conversation'; +import { useChatStore } from '@/store/chat'; +import { messageMapKey } from '@/store/chat/utils/messageMapKey'; + +interface SharedMessageListProps { + agentId: string | null; + groupId: string | null; + shareId: string; + topicId: string; +} + +const SharedMessageList = memo(({ agentId, groupId, shareId, topicId }) => { + const context = useMemo( + () => ({ + agentId: agentId ?? '', + groupId: groupId ?? undefined, + topicId, + topicShareId: shareId, + }), + [agentId, groupId, shareId, topicId], + ); + + // Sync messages to chatStore for artifact selectors to work + const chatKey = useMemo(() => messageMapKey(context), [context]); + const replaceMessages = useChatStore((s) => s.replaceMessages); + const messages = useChatStore((s) => s.dbMessagesMap[chatKey]); + + const itemContent = useCallback( + (index: number, id: string) => , + [], + ); + + return ( + { + replaceMessages(messages, { context }); + }} + > + + + + + ); +}); + +export default SharedMessageList; diff --git a/src/app/[variants]/share/t/[id]/_layout/index.tsx b/src/app/[variants]/share/t/[id]/_layout/index.tsx new file mode 100644 index 0000000000..c0ceebed0b --- /dev/null +++ b/src/app/[variants]/share/t/[id]/_layout/index.tsx @@ -0,0 +1,170 @@ +'use client'; + +import { Avatar, Flexbox } from '@lobehub/ui'; +import { Typography } from 'antd'; +import { createStyles, cssVar } from 'antd-style'; +import NextLink from 'next/link'; +import { PropsWithChildren, memo, useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link, Outlet, useParams } from 'react-router-dom'; +import useSWR from 'swr'; + +import { ProductLogo } from '@/components/Branding'; +import { DEFAULT_AVATAR } from '@/const/meta'; +import GroupAvatar from '@/features/GroupAvatar'; +import UserAvatar from '@/features/User/UserAvatar'; +import { lambdaClient } from '@/libs/trpc/client'; +import { useAgentStore } from '@/store/agent'; +import { useUserStore } from '@/store/user'; +import { authSelectors } from '@/store/user/slices/auth/selectors'; + +import SharePortal from '../features/Portal'; + +const useStyles = createStyles(({ css, token }) => ({ + container: css` + width: 100vw; + min-height: 100vh; + background: ${token.colorBgLayout}; + `, + content: css` + flex: 1; + width: 100%; + padding-block: 24px; + padding-inline: 24px; + `, + footer: css` + padding-block: 16px; + padding-inline: 24px; + color: ${token.colorTextTertiary}; + text-align: center; + `, + header: css` + height: 52px; + padding: 8px; + `, +})); + +const ShareTopicLayout = memo(({ children }) => { + const { styles } = useStyles(); + const { t } = useTranslation('chat'); + const { id } = useParams<{ id: string }>(); + const dispatchAgentMap = useAgentStore((s) => s.internal_dispatchAgentMap); + const isLogin = useUserStore(authSelectors.isLogin); + + const { data } = useSWR( + id ? ['shared-topic', id] : null, + () => lambdaClient.share.getSharedTopic.query({ shareId: id! }), + { revalidateOnFocus: false }, + ); + + // Set agent meta to agentStore for avatar display + useEffect(() => { + if (data?.agentId && data.agentMeta) { + const meta = { + avatar: data.agentMeta.avatar ?? undefined, + backgroundColor: data.agentMeta.backgroundColor ?? undefined, + title: data.agentMeta.title ?? undefined, + }; + dispatchAgentMap(data.agentId, meta); + } + }, [data?.agentId, data?.agentMeta, dispatchAgentMap]); + + const isGroup = !!data?.groupId; + const isInboxAgent = !isGroup && data?.agentMeta?.slug === 'inbox'; + const agentOrGroupTitle = + data?.groupMeta?.title || (isInboxAgent ? 'LobeAI' : data?.agentMeta?.title); + const agentMarketIdentifier = data?.agentMeta?.marketIdentifier; + + // Build group avatars for GroupAvatar component + const groupAvatars = useMemo(() => { + if (!isGroup || !data?.groupMeta?.members) return []; + return data.groupMeta.members.map((member) => ({ + avatar: member.avatar || DEFAULT_AVATAR, + backgroundColor: member.backgroundColor || undefined, + })); + }, [isGroup, data?.groupMeta?.members]); + + const renderAgentOrGroupAvatar = () => { + // For group: use GroupAvatar with members + if (isGroup && groupAvatars.length > 0) { + return ; + } + + // For inbox agent: skip avatar as it's the same as product icon + if (isInboxAgent) { + return null; + } + + // For agent: use single Avatar + if (data?.agentMeta?.avatar) { + return ( + + ); + } + + return null; + }; + + const renderAgentOrGroupTitle = () => { + if (!agentOrGroupTitle) return null; + + // If agent has marketIdentifier, render as link to assistant page + if (agentMarketIdentifier && !data?.groupMeta?.title) { + return ( + + + {agentOrGroupTitle} + + + ); + } + + return ( + + {agentOrGroupTitle} + + ); + }; + + return ( + + + + {isLogin ? ( + + + + ) : ( + + + + )} + {renderAgentOrGroupAvatar()} + {renderAgentOrGroupTitle()} + + {data?.title && ( + + {data.title} + + )} + + {isLogin && } + + + + + {children ?? } + + + + {t('sharePageDisclaimer')} + + ); +}); + +export default ShareTopicLayout; diff --git a/src/app/[variants]/share/t/[id]/features/Portal/index.tsx b/src/app/[variants]/share/t/[id]/features/Portal/index.tsx new file mode 100644 index 0000000000..b04934b486 --- /dev/null +++ b/src/app/[variants]/share/t/[id]/features/Portal/index.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { DraggablePanel } from '@lobehub/ui'; +import { createStyles } from 'antd-style'; +import { memo } from 'react'; + +import { CHAT_PORTAL_TOOL_UI_WIDTH } from '@/const/layoutTokens'; +import { PortalContent } from '@/features/Portal/router'; +import { useChatStore } from '@/store/chat'; +import { chatPortalSelectors } from '@/store/chat/selectors'; + +const useStyles = createStyles(({ css, token }) => ({ + body: css` + overflow: hidden; + display: flex; + flex: 1; + flex-direction: column; + + height: 0; + padding-block-end: 12px; + `, + content: css` + position: relative; + + overflow: hidden; + display: flex; + flex-direction: column; + + height: 100%; + min-height: 100%; + max-height: 100%; + + background: ${token.colorBgContainer}; + `, + drawer: css` + z-index: 10; + height: 100%; + background: ${token.colorBgContainer}; + `, +})); + +const SharePortal = memo(() => { + const { styles } = useStyles(); + const showPortal = useChatStore(chatPortalSelectors.showPortal); + + return ( + +
{body}
} /> +
+ ); +}); + +SharePortal.displayName = 'SharePortal'; + +export default SharePortal; diff --git a/src/app/[variants]/share/t/[id]/index.tsx b/src/app/[variants]/share/t/[id]/index.tsx new file mode 100644 index 0000000000..7c92b9ddc5 --- /dev/null +++ b/src/app/[variants]/share/t/[id]/index.tsx @@ -0,0 +1,112 @@ +'use client'; + +import { Flexbox } from '@lobehub/ui'; +import { TRPCClientError } from '@trpc/client'; +import { Button, Result, Skeleton } from 'antd'; +import { createStyles } from 'antd-style'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import useSWR from 'swr'; + +import { lambdaClient } from '@/libs/trpc/client'; + +import SharedMessageList from './SharedMessageList'; + +const useStyles = createStyles(({ css }) => ({ + container: css` + flex: 1; + `, + errorContainer: css` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + min-height: 400px; + padding: 48px; + + text-align: center; + `, +})); + +const ShareTopicPage = memo(() => { + const { styles } = useStyles(); + const { t } = useTranslation('chat'); + const { id } = useParams<{ id: string }>(); + + const { data, error, isLoading } = useSWR( + id ? ['shared-topic', id] : null, + () => lambdaClient.share.getSharedTopic.query({ shareId: id! }), + { revalidateOnFocus: false }, + ); + + if (isLoading) { + return ( + + + + + ); + } + + if (error) { + const trpcError = error instanceof TRPCClientError ? error : null; + const errorCode = trpcError?.data?.code; + + if (errorCode === 'UNAUTHORIZED') { + return ( + + + {t('sharePage.error.unauthorized.action')} + + } + status="403" + subTitle={t('sharePage.error.unauthorized.subtitle')} + title={t('sharePage.error.unauthorized.title')} + /> + + ); + } + + if (errorCode === 'FORBIDDEN') { + return ( + + + + ); + } + + // NOT_FOUND or other errors + return ( + + + + ); + } + + if (!data) return null; + + return ( + + + + ); +}); + +export default ShareTopicPage; diff --git a/src/app/robots.tsx b/src/app/robots.tsx index 31161a831c..6f8d6a9ce7 100644 --- a/src/app/robots.tsx +++ b/src/app/robots.tsx @@ -26,7 +26,7 @@ const robots = (): MetadataRoute.Robots => { }, { allow: ['/'], - disallow: ['/api/*', '/login', '/signup', '/knowledge/*'], + disallow: ['/api/*', '/login', '/signup', '/knowledge/*', '/share/*'], userAgent: '*', }, ], diff --git a/src/business/client/BusinessMobileRoutes.tsx b/src/business/client/BusinessMobileRoutes.tsx index 986f6d0c59..a0fdd2aeb3 100644 --- a/src/business/client/BusinessMobileRoutes.tsx +++ b/src/business/client/BusinessMobileRoutes.tsx @@ -1,4 +1,4 @@ -import { RouteConfig } from '@/utils/router'; +import { type RouteConfig } from '@/utils/router'; export const BusinessMobileRoutesWithMainLayout: RouteConfig[] = []; export const BusinessMobileRoutesWithSettingsLayout: RouteConfig[] = []; diff --git a/src/features/Conversation/ChatList/index.tsx b/src/features/Conversation/ChatList/index.tsx index 82f535c5fb..f4d7038d33 100644 --- a/src/features/Conversation/ChatList/index.tsx +++ b/src/features/Conversation/ChatList/index.tsx @@ -15,6 +15,10 @@ import { dataSelectors, useConversationStore } from '../store'; import VirtualizedList from './components/VirtualizedList'; export interface ChatListProps { + /** + * Disable the actions bar for all messages (e.g., in share page) + */ + disableActionsBar?: boolean; /** * Custom item renderer. If not provided, uses default ChatItem. */ @@ -29,7 +33,7 @@ export interface ChatListProps { * * Uses ConversationStore for message data and fetching. */ -const ChatList = memo(({ welcome, itemContent }) => { +const ChatList = memo(({ disableActionsBar, welcome, itemContent }) => { // Fetch messages (SWR key is null when skipFetch is true) const context = useConversationStore((s) => s.context); const enableUserMemories = useUserStore(settingsSelectors.memoryEnabled); @@ -39,9 +43,12 @@ const ChatList = memo(({ welcome, itemContent }) => { ]); useFetchMessages(context, skipFetch); - // Fetch notebook documents when topic is selected - useFetchNotebookDocuments(context.topicId!); - useFetchTopicMemories(enableUserMemories ? context.topicId : undefined); + // Skip fetching notebook and memories for share pages (they require authentication) + const isSharePage = !!context.topicShareId; + + // Fetch notebook documents when topic is selected (skip for share pages) + useFetchNotebookDocuments(isSharePage ? undefined : context.topicId!); + useFetchTopicMemories(enableUserMemories && !isSharePage ? context.topicId : undefined); // Use selectors for data @@ -77,7 +84,7 @@ const ChatList = memo(({ welcome, itemContent }) => { } return ( - + ( toolCallId, messageId, arguments: requestArgs, + disableEditing, showPluginRender, setShowPluginRender, identifier, @@ -54,7 +56,7 @@ const Render = memo( isArgumentsStreaming, isToolCalling, }) => { - if (toolMessageId && intervention?.status === 'pending') { + if (toolMessageId && intervention?.status === 'pending' && !disableEditing) { return ( ( showPluginRender={showPluginRender} toolCallId={toolCallId} /> -
- -
+ {!disableEditing && ( +
+ +
+ )} ); diff --git a/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx b/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx index 0665bf3711..fc20e697db 100644 --- a/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +++ b/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx @@ -30,6 +30,7 @@ export interface GroupToolProps { apiName: string; arguments?: string; assistantMessageId: string; + disableEditing?: boolean; id: string; identifier: string; intervention?: ToolIntervention; @@ -43,6 +44,7 @@ const Tool = memo( arguments: requestArgs, apiName, assistantMessageId, + disableEditing, id, intervention, identifier, @@ -106,16 +108,18 @@ const Tool = memo( return ( + !disableEditing && ( + + ) } allowExpand={hasCustomRender} expand={isToolRenderExpand} @@ -150,6 +154,7 @@ const Tool = memo( (({ messageId, tools }) => { +export const Tools = memo(({ disableEditing, messageId, tools }) => { if (!tools || tools.length === 0) return null; return ( @@ -19,6 +20,7 @@ export const Tools = memo(({ messageId, tools }) => { apiName={tool.apiName} arguments={tool.arguments} assistantMessageId={messageId} + disableEditing={disableEditing} id={tool.id} identifier={tool.identifier} intervention={tool.intervention} diff --git a/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx b/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx index 1fa22cfbba..46a65b83d1 100644 --- a/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx +++ b/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx @@ -14,9 +14,10 @@ import MessageContent from './MessageContent'; interface ContentBlockProps extends AssistantContentBlock { assistantId: string; + disableEditing?: boolean; } const ContentBlock = memo( - ({ id, tools, content, imageList, reasoning, error, assistantId }) => { + ({ id, tools, content, imageList, reasoning, error, assistantId, disableEditing }) => { const errorContent = useErrorContent(error); const showImageItems = !!imageList && imageList.length > 0; const [isReasoning, deleteMessage, continueGeneration] = useConversationStore((s) => [ @@ -70,7 +71,7 @@ const ContentBlock = memo( {showImageItems && } {/* Tools */} - {hasTools && } + {hasTools && } ); }, diff --git a/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx b/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx index 1fb34d25ee..4ac2e18512 100644 --- a/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +++ b/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx @@ -25,10 +25,10 @@ const GroupItem = memo( toggleMessageEditing(item.id, true); }} > - + ) : ( - + ); }, isEqual, diff --git a/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx b/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx index 3df8770a1a..f2cdb3cf71 100644 --- a/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx +++ b/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx @@ -11,34 +11,33 @@ import { Tools } from '../../AssistantGroup/Tools'; import Reasoning from '../../components/Reasoning'; import MessageContent from './MessageContent'; -const ContentBlock = memo(({ id, tools, content, reasoning, error }) => { - const errorContent = useErrorContent(error); - const isReasoning = useConversationStore(messageStateSelectors.isMessageInReasoning(id)); - const hasTools = tools && tools.length > 0; - const showReasoning = - (!!reasoning && reasoning.content?.trim() !== '') || (!reasoning && isReasoning); +interface ContentBlockProps extends AssistantContentBlock { + disableEditing?: boolean; +} + +const ContentBlock = memo( + ({ id, tools, content, reasoning, error, disableEditing }) => { + const errorContent = useErrorContent(error); + const isReasoning = useConversationStore(messageStateSelectors.isMessageInReasoning(id)); + const hasTools = tools && tools.length > 0; + const showReasoning = + (!!reasoning && reasoning.content?.trim() !== '') || (!reasoning && isReasoning); + + if (error && (content === LOADING_FLAT || !content)) + return ; - if (error && (content === LOADING_FLAT || !content)) return ( - + + {showReasoning && } + + {/* Content - markdown text */} + + + {/* Tools */} + {hasTools && } + ); - - return ( - - {showReasoning && } - - {/* Content - markdown text */} - - - {/* Tools */} - {hasTools && } - - ); -}); + }, +); export default ContentBlock; diff --git a/src/features/Conversation/Messages/Supervisor/components/Group.tsx b/src/features/Conversation/Messages/Supervisor/components/Group.tsx index 638d3c6687..01123f0ce3 100644 --- a/src/features/Conversation/Messages/Supervisor/components/Group.tsx +++ b/src/features/Conversation/Messages/Supervisor/components/Group.tsx @@ -29,7 +29,7 @@ interface GroupChildrenProps { messageIndex: number; } -const Group = memo(({ blocks, id, content }) => { +const Group = memo(({ blocks, id, content, disableEditing }) => { const isCollapsed = useConversationStore(messageStateSelectors.isMessageCollapsed(id)); const contextValue = useMemo(() => ({ assistantGroupId: id }), [id]); @@ -46,7 +46,9 @@ const Group = memo(({ blocks, id, content }) => { {blocks.map((item) => { - return ; + return ( + + ); })} diff --git a/src/features/Conversation/Messages/Tool/Tool/index.tsx b/src/features/Conversation/Messages/Tool/Tool/index.tsx index d8a40efed1..a4207677d6 100644 --- a/src/features/Conversation/Messages/Tool/Tool/index.tsx +++ b/src/features/Conversation/Messages/Tool/Tool/index.tsx @@ -20,6 +20,7 @@ const Render = dynamic(() => import('../../AssistantGroup/Tool/Render'), { export interface InspectorProps { apiName: string; arguments?: string; + disableEditing?: boolean; identifier: string; index: number; messageId: string; @@ -32,7 +33,7 @@ export interface InspectorProps { * Tool message component - adapts Tool message data to use AssistantGroup/Tool components */ const Tool = memo( - ({ arguments: requestArgs, apiName, messageId, toolCallId, index, identifier, type }) => { + ({ arguments: requestArgs, apiName, disableEditing, messageId, toolCallId, index, identifier, type }) => { const [showDebug, setShowDebug] = useState(false); const [showPluginRender, setShowPluginRender] = useState(false); const [expand, setExpand] = useState(true); @@ -66,16 +67,18 @@ const Tool = memo( > setExpand(!!expand)} - identifier={identifier} - setShowDebug={setShowDebug} - setShowPluginRender={setShowPluginRender} - showCustomPluginRender={false} - showDebug={showDebug} - showPluginRender={showPluginRender} - /> + !disableEditing && ( + setExpand(!!expand)} + identifier={identifier} + setShowDebug={setShowDebug} + setShowPluginRender={setShowPluginRender} + showCustomPluginRender={false} + showDebug={showDebug} + showPluginRender={showPluginRender} + /> + ) } itemKey={'tool'} paddingBlock={4} @@ -83,7 +86,7 @@ const Tool = memo( title={} > - {showDebug && ( + {showDebug && !disableEditing && ( ( (({ id, index }) => { +const ToolMessage = memo(({ disableEditing, id, index }) => { const { t } = useTranslation('plugin'); const item = useConversationStore(dataSelectors.getDbMessageById(id), isEqual) as UIChatMessage; const deleteToolMessage = useConversationStore((s) => s.deleteToolMessage); @@ -29,17 +30,25 @@ const ToolMessage = memo(({ id, index }) => { return ( - - {t('inspector.delete')} - - } - title={t('inspector.orphanedToolCall')} - type={'secondary'} - /> + {!disableEditing && ( + + {t('inspector.delete')} + + } + title={t('inspector.orphanedToolCall')} + type={'secondary'} + /> + )} {item.plugin && ( - + )} ); diff --git a/src/features/Conversation/Messages/index.tsx b/src/features/Conversation/Messages/index.tsx index 6671d2c512..07c3da7b34 100644 --- a/src/features/Conversation/Messages/index.tsx +++ b/src/features/Conversation/Messages/index.tsx @@ -158,7 +158,7 @@ const MessageItem = memo( } case 'tool': { - return ; + return ; } } diff --git a/src/features/Conversation/store/slices/data/action.ts b/src/features/Conversation/store/slices/data/action.ts index 422b6353a9..a855e4f5d5 100644 --- a/src/features/Conversation/store/slices/data/action.ts +++ b/src/features/Conversation/store/slices/data/action.ts @@ -103,13 +103,14 @@ export const dataSlice: StateCreator< useFetchMessages: (context, skipFetch) => { // When skipFetch is true, SWR key is null - no fetch occurs // This is used when external messages are provided (e.g., creating new thread) + // Allow fetch if: has agentId (both agent topics and group topics have agentId) const shouldFetch = !skipFetch && !!context.agentId; return useClientDataSWRWithSync( shouldFetch ? ['CONVERSATION_FETCH_MESSAGES', context] : null, async () => { - return messageService.getMessages(context as any); + return messageService.getMessages(context); }, { onData: (data) => { diff --git a/src/features/SharePopover/index.tsx b/src/features/SharePopover/index.tsx new file mode 100644 index 0000000000..e1fa4f863d --- /dev/null +++ b/src/features/SharePopover/index.tsx @@ -0,0 +1,215 @@ +'use client'; + +import { Button, Flexbox, Popover, copyToClipboard, usePopoverContext } from '@lobehub/ui'; +import { App, Divider, Select, Skeleton, Typography } from 'antd'; +import { CopyIcon, ExternalLinkIcon, LinkIcon, LockIcon } from 'lucide-react'; +import { type ReactNode, memo, useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import useSWR from 'swr'; + +import { useIsMobile } from '@/hooks/useIsMobile'; +import { topicService } from '@/services/topic'; +import { useChatStore } from '@/store/chat'; + +import { useStyles } from './style'; + +type Visibility = 'private' | 'link'; + +interface SharePopoverContentProps { + onOpenModal?: () => void; +} + +const SharePopoverContent = memo(({ onOpenModal }) => { + const { t } = useTranslation('chat'); + const { message, modal } = App.useApp(); + const { styles } = useStyles(); + const [updating, setUpdating] = useState(false); + const { close } = usePopoverContext(); + const containerRef = useRef(null); + + const activeTopicId = useChatStore((s) => s.activeTopicId); + + const { + data: shareInfo, + isLoading, + mutate, + } = useSWR( + activeTopicId ? ['topic-share-info', activeTopicId] : null, + () => topicService.getShareInfo(activeTopicId!), + { revalidateOnFocus: false }, + ); + + // Auto-create share record if not exists + useEffect(() => { + if (!isLoading && !shareInfo && activeTopicId) { + topicService.enableSharing(activeTopicId, 'private').then(() => mutate()); + } + }, [isLoading, shareInfo, activeTopicId, mutate]); + + const shareUrl = shareInfo?.id ? `${window.location.origin}/share/t/${shareInfo.id}` : ''; + const currentVisibility = (shareInfo?.visibility as Visibility) || 'private'; + + const updateVisibility = useCallback( + async (visibility: Visibility) => { + if (!activeTopicId) return; + + setUpdating(true); + try { + await topicService.updateShareVisibility(activeTopicId, visibility); + await mutate(); + message.success(t('shareModal.link.visibilityUpdated')); + } catch { + message.error(t('shareModal.link.updateError')); + } finally { + setUpdating(false); + } + }, + [activeTopicId, mutate, message, t], + ); + + const handleVisibilityChange = useCallback( + (visibility: Visibility) => { + // Show confirmation when changing from private to link + if (currentVisibility === 'private' && visibility === 'link') { + modal.confirm({ + cancelText: t('cancel', { ns: 'common' }), + content: t('shareModal.popover.privacyWarning.content'), + okText: t('shareModal.popover.privacyWarning.confirm'), + onOk: () => updateVisibility(visibility), + title: t('shareModal.popover.privacyWarning.title'), + type: 'warning', + }); + } else { + updateVisibility(visibility); + } + }, + [currentVisibility, modal, t, updateVisibility], + ); + + const handleCopyLink = useCallback(async () => { + if (!shareUrl) return; + await copyToClipboard(shareUrl); + message.success(t('shareModal.copyLinkSuccess')); + }, [shareUrl, message, t]); + + const handleOpenModal = useCallback(() => { + close(); + onOpenModal?.(); + }, [close, onOpenModal]); + + // Loading state + if (isLoading || !shareInfo) { + return ( + + {t('share', { ns: 'common' })} + + + ); + } + + const visibilityOptions = [ + { + icon: , + label: t('shareModal.link.permissionPrivate'), + value: 'private', + }, + { + icon: , + label: t('shareModal.link.permissionLink'), + value: 'link', + }, + ]; + + const getVisibilityHint = () => { + switch (currentVisibility) { + case 'private': { + return t('shareModal.link.privateHint'); + } + case 'link': { + return t('shareModal.link.linkHint'); + } + } + }; + + return ( + + {t('shareModal.popover.title')} + + + {t('shareModal.popover.visibility')} +