From 9fc6c92298dd3483d4d59a0af75bfd9601b21965 Mon Sep 17 00:00:00 2001 From: "alan.cl" <1165243776@qq.com> Date: Thu, 26 Feb 2026 17:33:45 +0800 Subject: [PATCH] feat: support chat share and playback --- .../upgrade/v0_8_0/upgrade_to_v0.8.0.sql | 15 + assets/schema/upgrade/v0_8_0/v0.8.0.sql | 637 +++++++++++++++ .../initialization/db_model_initialization.py | 2 + .../openapi/api_v1/agentic_data_api.py | 150 +++- .../dbgpt-app/src/dbgpt_app/share/__init__.py | 1 + .../dbgpt-app/src/dbgpt_app/share/models.py | 114 +++ .../chat/content/ManusLeftPanel.tsx | 14 +- .../chat/content/ManusRightPanel.tsx | 17 + web/pages/_app.tsx | 4 +- web/pages/index.tsx | 21 + web/pages/share/[token].tsx | 771 ++++++++++++++++++ 11 files changed, 1719 insertions(+), 27 deletions(-) create mode 100644 assets/schema/upgrade/v0_8_0/upgrade_to_v0.8.0.sql create mode 100644 assets/schema/upgrade/v0_8_0/v0.8.0.sql create mode 100644 packages/dbgpt-app/src/dbgpt_app/share/__init__.py create mode 100644 packages/dbgpt-app/src/dbgpt_app/share/models.py create mode 100644 web/pages/share/[token].tsx diff --git a/assets/schema/upgrade/v0_8_0/upgrade_to_v0.8.0.sql b/assets/schema/upgrade/v0_8_0/upgrade_to_v0.8.0.sql new file mode 100644 index 000000000..c0dd15172 --- /dev/null +++ b/assets/schema/upgrade/v0_8_0/upgrade_to_v0.8.0.sql @@ -0,0 +1,15 @@ +-- From 0.7.x to 0.8.0, we have the following changes: +USE dbgpt; + +-- share_links, Store conversation share link tokens +CREATE TABLE `share_links` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'Primary key', + `token` varchar(64) NOT NULL COMMENT 'Unique random share token', + `conv_uid` varchar(255) NOT NULL COMMENT 'The conversation uid being shared', + `created_by` varchar(255) DEFAULT NULL COMMENT 'User who created the share link', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_share_token` (`token`), + KEY `ix_share_links_token` (`token`), + KEY `ix_share_links_conv_uid` (`conv_uid`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Conversation share link table'; diff --git a/assets/schema/upgrade/v0_8_0/v0.8.0.sql b/assets/schema/upgrade/v0_8_0/v0.8.0.sql new file mode 100644 index 000000000..85a029b13 --- /dev/null +++ b/assets/schema/upgrade/v0_8_0/v0.8.0.sql @@ -0,0 +1,637 @@ +-- You can change `dbgpt` to your actual metadata database name in your `.env` file +-- eg. `LOCAL_DB_NAME=dbgpt` + +CREATE +DATABASE IF NOT EXISTS dbgpt; +use dbgpt; + +-- For alembic migration tool +CREATE TABLE IF NOT EXISTS `alembic_version` +( + version_num VARCHAR(32) NOT NULL, + CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num) +) DEFAULT CHARSET=utf8mb4 ; + +CREATE TABLE IF NOT EXISTS `knowledge_space` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'auto increment id', + `name` varchar(100) NOT NULL COMMENT 'knowledge space name', + `vector_type` varchar(50) NOT NULL COMMENT 'vector type', + `domain_type` varchar(50) NOT NULL COMMENT 'domain type', + `desc` varchar(500) NOT NULL COMMENT 'description', + `owner` varchar(100) DEFAULT NULL COMMENT 'owner', + `context` TEXT DEFAULT NULL COMMENT 'context argument', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'created time', + `gmt_modified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) COMMENT 'index:idx_name' +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='knowledge space table'; + +CREATE TABLE IF NOT EXISTS `knowledge_document` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'auto increment id', + `doc_name` varchar(100) NOT NULL COMMENT 'document path name', + `doc_type` varchar(50) NOT NULL COMMENT 'doc type', + `doc_token` varchar(100) NULL COMMENT 'doc token', + `space` varchar(50) NOT NULL COMMENT 'knowledge space', + `chunk_size` int NOT NULL COMMENT 'chunk size', + `last_sync` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'last sync time', + `status` varchar(50) NOT NULL COMMENT 'status TODO,RUNNING,FAILED,FINISHED', + `content` LONGTEXT NOT NULL COMMENT 'knowledge embedding sync result', + `result` TEXT NULL COMMENT 'knowledge content', + `questions` TEXT NULL COMMENT 'document related questions', + `vector_ids` LONGTEXT NULL COMMENT 'vector_ids', + `summary` LONGTEXT NULL COMMENT 'knowledge summary', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'created time', + `gmt_modified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + PRIMARY KEY (`id`), + KEY `idx_doc_name` (`doc_name`) COMMENT 'index:idx_doc_name' +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='knowledge document table'; + +CREATE TABLE IF NOT EXISTS `document_chunk` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'auto increment id', + `doc_name` varchar(100) NOT NULL COMMENT 'document path name', + `doc_type` varchar(50) NOT NULL COMMENT 'doc type', + `document_id` int NOT NULL COMMENT 'document parent id', + `content` longtext NOT NULL COMMENT 'chunk content', + `questions` text NULL COMMENT 'chunk related questions', + `meta_info` text NOT NULL COMMENT 'metadata info', + `gmt_created` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'created time', + `gmt_modified` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + PRIMARY KEY (`id`), + KEY `idx_document_id` (`document_id`) COMMENT 'index:document_id' +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='knowledge document chunk detail'; + + +CREATE TABLE IF NOT EXISTS `connect_config` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `db_type` varchar(255) NOT NULL COMMENT 'db type', + `db_name` varchar(255) NOT NULL COMMENT 'db name', + `db_path` varchar(255) DEFAULT NULL COMMENT 'file db path', + `db_host` varchar(255) DEFAULT NULL COMMENT 'db connect host(not file db)', + `db_port` varchar(255) DEFAULT NULL COMMENT 'db cnnect port(not file db)', + `db_user` varchar(255) DEFAULT NULL COMMENT 'db user', + `db_pwd` varchar(255) DEFAULT NULL COMMENT 'db password', + `comment` text COMMENT 'db comment', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `user_name` varchar(255) DEFAULT NULL COMMENT 'user name', + `user_id` varchar(255) DEFAULT NULL COMMENT 'user id', + `gmt_created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation time', + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record update time', + `ext_config` text COMMENT 'Extended configuration, json format', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_db` (`db_name`), + KEY `idx_q_db_type` (`db_type`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT 'Connection confi'; + +CREATE TABLE IF NOT EXISTS `chat_history` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `conv_uid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Conversation record unique id', + `chat_mode` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Conversation scene mode', + `summary` longtext COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Conversation record summary', + `user_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'interlocutor', + `messages` text COLLATE utf8mb4_unicode_ci COMMENT 'Conversation details', + `message_ids` text COLLATE utf8mb4_unicode_ci COMMENT 'Message id list, split by comma', + `app_code` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'App unique code', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'created time', + `gmt_modified` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + UNIQUE KEY `conv_uid` (`conv_uid`), + PRIMARY KEY (`id`), + KEY `idx_chat_his_app_code` (`app_code`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT 'Chat history'; + +CREATE TABLE IF NOT EXISTS `chat_history_message` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `conv_uid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Conversation record unique id', + `index` int NOT NULL COMMENT 'Message index', + `round_index` int NOT NULL COMMENT 'Round of conversation', + `message_detail` longtext COLLATE utf8mb4_unicode_ci COMMENT 'Message details, json format', + `gmt_created` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'created time', + `gmt_modified` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + UNIQUE KEY `message_uid_index` (`conv_uid`, `index`), + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT 'Chat history message'; + + +CREATE TABLE IF NOT EXISTS `chat_feed_back` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `conv_uid` varchar(128) DEFAULT NULL COMMENT 'Conversation ID', + `conv_index` int(4) DEFAULT NULL COMMENT 'Round of conversation', + `score` int(1) DEFAULT NULL COMMENT 'Score of user', + `ques_type` varchar(32) DEFAULT NULL COMMENT 'User question category', + `question` longtext DEFAULT NULL COMMENT 'User question', + `knowledge_space` varchar(128) DEFAULT NULL COMMENT 'Knowledge space name', + `messages` longtext DEFAULT NULL COMMENT 'The details of user feedback', + `message_id` varchar(255) NULL COMMENT 'Message id', + `feedback_type` varchar(50) NULL COMMENT 'Feedback type like or unlike', + `reason_types` varchar(255) NULL COMMENT 'Feedback reason categories', + `remark` text NULL COMMENT 'Feedback remark', + `user_code` varchar(128) NULL COMMENT 'User code', + `user_name` varchar(128) DEFAULT NULL COMMENT 'User name', + `gmt_created` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'created time', + `gmt_modified` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_conv` (`conv_uid`,`conv_index`), + KEY `idx_conv` (`conv_uid`,`conv_index`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='User feedback table'; + + +CREATE TABLE IF NOT EXISTS `my_plugin` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `tenant` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'user tenant', + `user_code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'user code', + `user_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'user name', + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'plugin name', + `file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'plugin package file name', + `type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin type', + `version` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin version', + `use_count` int DEFAULT NULL COMMENT 'plugin total use count', + `succ_count` int DEFAULT NULL COMMENT 'plugin total success count', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'plugin install time', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='User plugin table'; + +CREATE TABLE IF NOT EXISTS `plugin_hub` +( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'plugin name', + `description` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'plugin description', + `author` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin author', + `email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin author email', + `type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin type', + `version` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin version', + `storage_channel` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin storage channel', + `storage_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin download url', + `download_param` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'plugin download param', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'plugin upload time', + `installed` int DEFAULT NULL COMMENT 'plugin already installed count', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Plugin Hub table'; + + +CREATE TABLE IF NOT EXISTS `prompt_manage` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `chat_scene` varchar(100) DEFAULT NULL COMMENT 'Chat scene', + `sub_chat_scene` varchar(100) DEFAULT NULL COMMENT 'Sub chat scene', + `prompt_type` varchar(100) DEFAULT NULL COMMENT 'Prompt type: common or private', + `prompt_name` varchar(256) DEFAULT NULL COMMENT 'prompt name', + `prompt_code` varchar(256) DEFAULT NULL COMMENT 'prompt code', + `content` longtext COMMENT 'Prompt content', + `input_variables` varchar(1024) DEFAULT NULL COMMENT 'Prompt input variables(split by comma))', + `response_schema` text DEFAULT NULL COMMENT 'Prompt response schema', + `model` varchar(128) DEFAULT NULL COMMENT 'Prompt model name(we can use different models for different prompt)', + `prompt_language` varchar(32) DEFAULT NULL COMMENT 'Prompt language(eg:en, zh-cn)', + `prompt_format` varchar(32) DEFAULT 'f-string' COMMENT 'Prompt format(eg: f-string, jinja2)', + `prompt_desc` varchar(512) DEFAULT NULL COMMENT 'Prompt description', + `user_code` varchar(128) DEFAULT NULL COMMENT 'User code', + `user_name` varchar(128) DEFAULT NULL COMMENT 'User name', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'created time', + `gmt_modified` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + PRIMARY KEY (`id`), + UNIQUE KEY `prompt_name_uiq` (`prompt_name`, `sys_code`, `prompt_language`, `model`), + KEY `gmt_created_idx` (`gmt_created`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Prompt management table'; + + + + CREATE TABLE IF NOT EXISTS `gpts_conversations` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `conv_id` varchar(255) NOT NULL COMMENT 'The unique id of the conversation record', + `user_goal` text NOT NULL COMMENT 'User''s goals content', + `gpts_name` varchar(255) NOT NULL COMMENT 'The gpts name', + `state` varchar(255) DEFAULT NULL COMMENT 'The gpts state', + `max_auto_reply_round` int(11) NOT NULL COMMENT 'max auto reply round', + `auto_reply_count` int(11) NOT NULL COMMENT 'auto reply count', + `user_code` varchar(255) DEFAULT NULL COMMENT 'user code', + `sys_code` varchar(255) DEFAULT NULL COMMENT 'system app ', + `created_at` datetime DEFAULT NULL COMMENT 'create time', + `updated_at` datetime DEFAULT NULL COMMENT 'last update time', + `team_mode` varchar(255) NULL COMMENT 'agent team work mode', + + PRIMARY KEY (`id`), + UNIQUE KEY `uk_gpts_conversations` (`conv_id`), + KEY `idx_gpts_name` (`gpts_name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT="gpt conversations"; + +CREATE TABLE IF NOT EXISTS `gpts_instance` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `gpts_name` varchar(255) NOT NULL COMMENT 'Current AI assistant name', + `gpts_describe` varchar(2255) NOT NULL COMMENT 'Current AI assistant describe', + `resource_db` text COMMENT 'List of structured database names contained in the current gpts', + `resource_internet` text COMMENT 'Is it possible to retrieve information from the internet', + `resource_knowledge` text COMMENT 'List of unstructured database names contained in the current gpts', + `gpts_agents` varchar(1000) DEFAULT NULL COMMENT 'List of agents names contained in the current gpts', + `gpts_models` varchar(1000) DEFAULT NULL COMMENT 'List of llm model names contained in the current gpts', + `language` varchar(100) DEFAULT NULL COMMENT 'gpts language', + `user_code` varchar(255) NOT NULL COMMENT 'user code', + `sys_code` varchar(255) DEFAULT NULL COMMENT 'system app code', + `created_at` datetime DEFAULT NULL COMMENT 'create time', + `updated_at` datetime DEFAULT NULL COMMENT 'last update time', + `team_mode` varchar(255) NOT NULL COMMENT 'Team work mode', + `is_sustainable` tinyint(1) NOT NULL COMMENT 'Applications for sustainable dialogue', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_gpts` (`gpts_name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT="gpts instance"; + +CREATE TABLE `gpts_messages` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `conv_id` varchar(255) NOT NULL COMMENT 'The unique id of the conversation record', + `sender` varchar(255) NOT NULL COMMENT 'Who speaking in the current conversation turn', + `receiver` varchar(255) NOT NULL COMMENT 'Who receive message in the current conversation turn', + `model_name` varchar(255) DEFAULT NULL COMMENT 'message generate model', + `rounds` int(11) NOT NULL COMMENT 'dialogue turns', + `is_success` int(4) NULL DEFAULT 0 COMMENT 'agent message is success', + `app_code` varchar(255) NOT NULL COMMENT 'Current AI assistant code', + `app_name` varchar(255) NOT NULL COMMENT 'Current AI assistant name', + `content` text COMMENT 'Content of the speech', + `current_goal` text COMMENT 'The target corresponding to the current message', + `context` text COMMENT 'Current conversation context', + `review_info` text COMMENT 'Current conversation review info', + `action_report` longtext COMMENT 'Current conversation action report', + `resource_info` text DEFAULT NULL COMMENT 'Current conversation resource info', + `role` varchar(255) DEFAULT NULL COMMENT 'The role of the current message content', + `created_at` datetime DEFAULT NULL COMMENT 'create time', + `updated_at` datetime DEFAULT NULL COMMENT 'last update time', + PRIMARY KEY (`id`), + KEY `idx_q_messages` (`conv_id`,`rounds`,`sender`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT="gpts message"; + + +CREATE TABLE `gpts_plans` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `conv_id` varchar(255) NOT NULL COMMENT 'The unique id of the conversation record', + `sub_task_num` int(11) NOT NULL COMMENT 'Subtask number', + `sub_task_title` varchar(255) NOT NULL COMMENT 'subtask title', + `sub_task_content` text NOT NULL COMMENT 'subtask content', + `sub_task_agent` varchar(255) DEFAULT NULL COMMENT 'Available agents corresponding to subtasks', + `resource_name` varchar(255) DEFAULT NULL COMMENT 'resource name', + `rely` varchar(255) DEFAULT NULL COMMENT 'Subtask dependencies,like: 1,2,3', + `agent_model` varchar(255) DEFAULT NULL COMMENT 'LLM model used by subtask processing agents', + `retry_times` int(11) DEFAULT NULL COMMENT 'number of retries', + `max_retry_times` int(11) DEFAULT NULL COMMENT 'Maximum number of retries', + `state` varchar(255) DEFAULT NULL COMMENT 'subtask status', + `result` longtext COMMENT 'subtask result', + `created_at` datetime DEFAULT NULL COMMENT 'create time', + `updated_at` datetime DEFAULT NULL COMMENT 'last update time', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_sub_task` (`conv_id`,`sub_task_num`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT="gpt plan"; + +-- dbgpt.dbgpt_serve_flow definition +CREATE TABLE `dbgpt_serve_flow` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'Auto increment id', + `uid` varchar(128) NOT NULL COMMENT 'Unique id', + `dag_id` varchar(128) DEFAULT NULL COMMENT 'DAG id', + `name` varchar(128) DEFAULT NULL COMMENT 'Flow name', + `flow_data` longtext COMMENT 'Flow data, JSON format', + `user_name` varchar(128) DEFAULT NULL COMMENT 'User name', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` datetime DEFAULT NULL COMMENT 'Record creation time', + `gmt_modified` datetime DEFAULT NULL COMMENT 'Record update time', + `flow_category` varchar(64) DEFAULT NULL COMMENT 'Flow category', + `description` varchar(512) DEFAULT NULL COMMENT 'Flow description', + `state` varchar(32) DEFAULT NULL COMMENT 'Flow state', + `error_message` varchar(512) NULL comment 'Error message', + `source` varchar(64) DEFAULT NULL COMMENT 'Flow source', + `source_url` varchar(512) DEFAULT NULL COMMENT 'Flow source url', + `version` varchar(32) DEFAULT NULL COMMENT 'Flow version', + `define_type` varchar(32) null comment 'Flow define type(json or python)', + `label` varchar(128) DEFAULT NULL COMMENT 'Flow label', + `editable` int DEFAULT NULL COMMENT 'Editable, 0: editable, 1: not editable', + `variables` text DEFAULT NULL COMMENT 'Flow variables, JSON format', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `ix_dbgpt_serve_flow_sys_code` (`sys_code`), + KEY `ix_dbgpt_serve_flow_uid` (`uid`), + KEY `ix_dbgpt_serve_flow_dag_id` (`dag_id`), + KEY `ix_dbgpt_serve_flow_user_name` (`user_name`), + KEY `ix_dbgpt_serve_flow_name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- dbgpt.dbgpt_serve_file definition +CREATE TABLE `dbgpt_serve_file` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'Auto increment id', + `bucket` varchar(255) NOT NULL COMMENT 'Bucket name', + `file_id` varchar(255) NOT NULL COMMENT 'File id', + `file_name` varchar(256) NOT NULL COMMENT 'File name', + `file_size` int DEFAULT NULL COMMENT 'File size', + `storage_type` varchar(32) NOT NULL COMMENT 'Storage type', + `storage_path` varchar(512) NOT NULL COMMENT 'Storage path', + `uri` varchar(512) NOT NULL COMMENT 'File URI', + `custom_metadata` text DEFAULT NULL COMMENT 'Custom metadata, JSON format', + `file_hash` varchar(128) DEFAULT NULL COMMENT 'File hash', + `user_name` varchar(128) DEFAULT NULL COMMENT 'User name', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation time', + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record update time', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_bucket_file_id` (`bucket`, `file_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- dbgpt.dbgpt_serve_variables definition +CREATE TABLE `dbgpt_serve_variables` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'Auto increment id', + `key` varchar(128) NOT NULL COMMENT 'Variable key', + `name` varchar(128) DEFAULT NULL COMMENT 'Variable name', + `label` varchar(128) DEFAULT NULL COMMENT 'Variable label', + `value` text DEFAULT NULL COMMENT 'Variable value, JSON format', + `value_type` varchar(32) DEFAULT NULL COMMENT 'Variable value type(string, int, float, bool)', + `category` varchar(32) DEFAULT 'common' COMMENT 'Variable category(common or secret)', + `encryption_method` varchar(32) DEFAULT NULL COMMENT 'Variable encryption method(fernet, simple, rsa, aes)', + `salt` varchar(128) DEFAULT NULL COMMENT 'Variable salt', + `scope` varchar(32) DEFAULT 'global' COMMENT 'Variable scope(global,flow,app,agent,datasource,flow_priv,agent_priv, ""etc)', + `scope_key` varchar(256) DEFAULT NULL COMMENT 'Variable scope key, default is empty, for scope is "flow_priv", the scope_key is dag id of flow', + `enabled` int DEFAULT 1 COMMENT 'Variable enabled, 0: disabled, 1: enabled', + `description` text DEFAULT NULL COMMENT 'Variable description', + `user_name` varchar(128) DEFAULT NULL COMMENT 'User name', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation time', + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record update time', + PRIMARY KEY (`id`), + KEY `ix_your_table_name_key` (`key`), + KEY `ix_your_table_name_name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE `dbgpt_serve_model` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'Auto increment id', + `host` varchar(255) NOT NULL COMMENT 'The model worker host', + `port` int NOT NULL COMMENT 'The model worker port', + `model` varchar(255) NOT NULL COMMENT 'The model name', + `provider` varchar(255) NOT NULL COMMENT 'The model provider', + `worker_type` varchar(255) NOT NULL COMMENT 'The worker type', + `params` text NOT NULL COMMENT 'The model parameters, JSON format', + `enabled` int DEFAULT 1 COMMENT 'Whether the model is enabled, if it is enabled, it will be started when the system starts, 1 is enabled, 0 is disabled', + `worker_name` varchar(255) DEFAULT NULL COMMENT 'The worker name', + `description` text DEFAULT NULL COMMENT 'The model description', + `user_name` varchar(128) DEFAULT NULL COMMENT 'User name', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation time', + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record update time', + PRIMARY KEY (`id`), + KEY `idx_user_name` (`user_name`), + KEY `idx_sys_code` (`sys_code`), + UNIQUE KEY `uk_model_provider_type` (`model`, `provider`, `worker_type`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Model persistence table'; + +-- dbgpt.gpts_app definition +CREATE TABLE `gpts_app` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `app_code` varchar(255) NOT NULL COMMENT 'Current AI assistant code', + `app_name` varchar(255) NOT NULL COMMENT 'Current AI assistant name', + `app_describe` varchar(2255) NOT NULL COMMENT 'Current AI assistant describe', + `language` varchar(100) NOT NULL COMMENT 'gpts language', + `team_mode` varchar(255) NOT NULL COMMENT 'Team work mode', + `team_context` text COMMENT 'The execution logic and team member content that teams with different working modes rely on', + `user_code` varchar(255) DEFAULT NULL COMMENT 'user code', + `sys_code` varchar(255) DEFAULT NULL COMMENT 'system app code', + `created_at` datetime DEFAULT NULL COMMENT 'create time', + `updated_at` datetime DEFAULT NULL COMMENT 'last update time', + `icon` varchar(1024) DEFAULT NULL COMMENT 'app icon, url', + `published` varchar(64) DEFAULT 'false' COMMENT 'Has it been published?', + `param_need` text DEFAULT NULL COMMENT 'Parameter information supported by the application', + `admins` text DEFAULT NULL COMMENT 'administrator', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_gpts_app` (`app_name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE `gpts_app_collection` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `app_code` varchar(255) NOT NULL COMMENT 'Current AI assistant code', + `user_code` int(11) NOT NULL COMMENT 'user code', + `sys_code` varchar(255) NULL COMMENT 'system app code', + `created_at` datetime DEFAULT NULL COMMENT 'create time', + `updated_at` datetime DEFAULT NULL COMMENT 'last update time', + PRIMARY KEY (`id`), + KEY `idx_app_code` (`app_code`), + KEY `idx_user_code` (`user_code`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT="gpt collections"; + +-- dbgpt.gpts_app_detail definition +CREATE TABLE `gpts_app_detail` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `app_code` varchar(255) NOT NULL COMMENT 'Current AI assistant code', + `app_name` varchar(255) NOT NULL COMMENT 'Current AI assistant name', + `agent_name` varchar(255) NOT NULL COMMENT ' Agent name', + `node_id` varchar(255) NOT NULL COMMENT 'Current AI assistant Agent Node id', + `resources` text COMMENT 'Agent bind resource', + `prompt_template` text COMMENT 'Agent bind template', + `llm_strategy` varchar(25) DEFAULT NULL COMMENT 'Agent use llm strategy', + `llm_strategy_value` text COMMENT 'Agent use llm strategy value', + `created_at` datetime DEFAULT NULL COMMENT 'create time', + `updated_at` datetime DEFAULT NULL COMMENT 'last update time', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_gpts_app_agent_node` (`app_name`,`agent_name`,`node_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + +-- For deploy model cluster of DB-GPT(StorageModelRegistry) +CREATE TABLE IF NOT EXISTS `dbgpt_cluster_registry_instance` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Auto increment id', + `model_name` varchar(128) NOT NULL COMMENT 'Model name', + `host` varchar(128) NOT NULL COMMENT 'Host of the model', + `port` int(11) NOT NULL COMMENT 'Port of the model', + `weight` float DEFAULT 1.0 COMMENT 'Weight of the model', + `check_healthy` tinyint(1) DEFAULT 1 COMMENT 'Whether to check the health of the model', + `healthy` tinyint(1) DEFAULT 0 COMMENT 'Whether the model is healthy', + `enabled` tinyint(1) DEFAULT 1 COMMENT 'Whether the model is enabled', + `prompt_template` varchar(128) DEFAULT NULL COMMENT 'Prompt template for the model instance', + `last_heartbeat` datetime DEFAULT NULL COMMENT 'Last heartbeat time of the model instance', + `user_name` varchar(128) DEFAULT NULL COMMENT 'User name', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation time', + `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record update time', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_model_instance` (`model_name`, `host`, `port`, `sys_code`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Cluster model instance table, for registering and managing model instances'; + +-- dbgpt.recommend_question definition +CREATE TABLE `recommend_question` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time', + `gmt_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'last update time', + `app_code` varchar(255) NOT NULL COMMENT 'Current AI assistant code', + `question` text DEFAULT NULL COMMENT 'question', + `user_code` varchar(255) NOT NULL COMMENT 'user code', + `sys_code` varchar(255) NULL COMMENT 'system app code', + `valid` varchar(10) DEFAULT 'true' COMMENT 'is it effective,true/false', + `chat_mode` varchar(255) DEFAULT NULL COMMENT 'Conversation scene mode,chat_knowledge...', + `params` text DEFAULT NULL COMMENT 'question param', + `is_hot_question` varchar(10) DEFAULT 'false' COMMENT 'Is it a popular recommendation question?', + PRIMARY KEY (`id`), + KEY `idx_rec_q_app_code` (`app_code`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT="AI application related recommendation issues"; + +-- dbgpt.user_recent_apps definition +CREATE TABLE `user_recent_apps` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time', + `gmt_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'last update time', + `app_code` varchar(255) NOT NULL COMMENT 'AI assistant code', + `last_accessed` timestamp NULL DEFAULT NULL COMMENT 'User recent usage time', + `user_code` varchar(255) DEFAULT NULL COMMENT 'user code', + `sys_code` varchar(255) DEFAULT NULL COMMENT 'system app code', + PRIMARY KEY (`id`), + KEY `idx_user_r_app_code` (`app_code`), + KEY `idx_last_accessed` (`last_accessed`), + KEY `idx_user_code` (`user_code`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='User recently used apps'; + +-- dbgpt.dbgpt_serve_dbgpts_my definition +CREATE TABLE `dbgpt_serve_dbgpts_my` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `name` varchar(255) NOT NULL COMMENT 'plugin name', + `user_code` varchar(255) DEFAULT NULL COMMENT 'user code', + `user_name` varchar(255) DEFAULT NULL COMMENT 'user name', + `file_name` varchar(255) NOT NULL COMMENT 'plugin package file name', + `type` varchar(255) DEFAULT NULL COMMENT 'plugin type', + `version` varchar(255) DEFAULT NULL COMMENT 'plugin version', + `use_count` int DEFAULT NULL COMMENT 'plugin total use count', + `succ_count` int DEFAULT NULL COMMENT 'plugin total success count', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'System code', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'plugin install time', + `gmt_modified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`, `user_name`), + KEY `ix_my_plugin_sys_code` (`sys_code`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- dbgpt.dbgpt_serve_dbgpts_hub definition +CREATE TABLE `dbgpt_serve_dbgpts_hub` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `name` varchar(255) NOT NULL COMMENT 'plugin name', + `description` varchar(255) NULL COMMENT 'plugin description', + `author` varchar(255) DEFAULT NULL COMMENT 'plugin author', + `email` varchar(255) DEFAULT NULL COMMENT 'plugin author email', + `type` varchar(255) DEFAULT NULL COMMENT 'plugin type', + `version` varchar(255) DEFAULT NULL COMMENT 'plugin version', + `storage_channel` varchar(255) DEFAULT NULL COMMENT 'plugin storage channel', + `storage_url` varchar(255) DEFAULT NULL COMMENT 'plugin download url', + `download_param` varchar(255) DEFAULT NULL COMMENT 'plugin download param', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'plugin upload time', + `gmt_modified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time', + `installed` int DEFAULT NULL COMMENT 'plugin already installed count', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- dbgpt.evaluate_manage definition +CREATE TABLE `evaluate_manage` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `evaluate_code` varchar(256) NOT NULL COMMENT 'evaluate unique code', + `scene_key` varchar(100) DEFAULT NULL COMMENT 'scene key', + `scene_value` varchar(256) DEFAULT NULL COMMENT 'scene value', + `context` text DEFAULT NULL COMMENT 'context', + `evaluate_metrics` varchar(599) DEFAULT NULL COMMENT 'evaluate metrics', + `datasets_name` varchar(256) DEFAULT NULL COMMENT 'datasets name', + `datasets` text DEFAULT NULL COMMENT 'datasets content', + `storage_type` varchar(256) DEFAULT NULL COMMENT 'result storage type', + `parallel_num` int DEFAULT NULL COMMENT 'execute parallel thread number', + `state` VARCHAR(100) DEFAULT NULL COMMENT 'execute state', + `result` text DEFAULT NULL COMMENT 'evaluate result', + `log_info` text DEFAULT NULL COMMENT 'evaluate error log', + `average_score` text DEFAULT NULL COMMENT 'metrics average score', + `user_id` varchar(100) DEFAULT NULL COMMENT 'user id', + `user_name` varchar(128) DEFAULT NULL COMMENT 'user name', + `sys_code` varchar(128) DEFAULT NULL COMMENT 'system code', + `gmt_create` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'benchmark create time', + `gmt_modified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'benchmark finish time', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_evaluate` (`evaluate_code`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- dbgpt.benchmark_summary definition +CREATE TABLE `benchmark_summary` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'autoincrement id', + `round_id` int NOT NULL COMMENT 'task round id', + `output_path` varchar(512) NULL COMMENT 'output file path', + `right` int DEFAULT NULL COMMENT 'right number', + `wrong` int DEFAULT NULL COMMENT 'wrong number', + `failed` int DEFAULT NULL COMMENT 'failed number', + `exception` int DEFAULT NULL COMMENT 'exception number', + `llm_code` varchar(256) DEFAULT NULL COMMENT 'benchmark llm code', + `evaluate_code` varchar(256) DEFAULT NULL COMMENT 'benchmark evaluate code', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'benchmark create time', + `gmt_modified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'benchmark finish time', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- dbgpt.share_links definition +CREATE TABLE `share_links` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'Primary key', + `token` varchar(64) NOT NULL COMMENT 'Unique random share token', + `conv_uid` varchar(255) NOT NULL COMMENT 'The conversation uid being shared', + `created_by` varchar(255) DEFAULT NULL COMMENT 'User who created the share link', + `gmt_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_share_token` (`token`), + KEY `ix_share_links_token` (`token`), + KEY `ix_share_links_conv_uid` (`conv_uid`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Conversation share link table'; + + +CREATE +DATABASE IF NOT EXISTS EXAMPLE_1; +use EXAMPLE_1; +CREATE TABLE IF NOT EXISTS `users` +( + `id` int NOT NULL AUTO_INCREMENT, + `username` varchar(50) NOT NULL COMMENT '用户名', + `password` varchar(50) NOT NULL COMMENT '密码', + `email` varchar(50) NOT NULL COMMENT '邮箱', + `phone` varchar(20) DEFAULT NULL COMMENT '电话', + PRIMARY KEY (`id`), + KEY `idx_username` (`username`) COMMENT '索引:按用户名查询' +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='聊天用户表'; + +INSERT INTO users (username, password, email, phone) +VALUES ('user_1', 'password_1', 'user_1@example.com', '12345678901'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_2', 'password_2', 'user_2@example.com', '12345678902'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_3', 'password_3', 'user_3@example.com', '12345678903'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_4', 'password_4', 'user_4@example.com', '12345678904'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_5', 'password_5', 'user_5@example.com', '12345678905'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_6', 'password_6', 'user_6@example.com', '12345678906'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_7', 'password_7', 'user_7@example.com', '12345678907'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_8', 'password_8', 'user_8@example.com', '12345678908'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_9', 'password_9', 'user_9@example.com', '12345678909'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_10', 'password_10', 'user_10@example.com', '12345678900'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_11', 'password_11', 'user_11@example.com', '12345678901'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_12', 'password_12', 'user_12@example.com', '12345678902'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_13', 'password_13', 'user_13@example.com', '12345678903'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_14', 'password_14', 'user_14@example.com', '12345678904'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_15', 'password_15', 'user_15@example.com', '12345678905'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_16', 'password_16', 'user_16@example.com', '12345678906'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_17', 'password_17', 'user_17@example.com', '12345678907'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_18', 'password_18', 'user_18@example.com', '12345678908'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_19', 'password_19', 'user_19@example.com', '12345678909'); +INSERT INTO users (username, password, email, phone) +VALUES ('user_20', 'password_20', 'user_20@example.com', '12345678900'); \ No newline at end of file diff --git a/packages/dbgpt-app/src/dbgpt_app/initialization/db_model_initialization.py b/packages/dbgpt-app/src/dbgpt_app/initialization/db_model_initialization.py index 7958dd359..d7b362f84 100644 --- a/packages/dbgpt-app/src/dbgpt_app/initialization/db_model_initialization.py +++ b/packages/dbgpt-app/src/dbgpt_app/initialization/db_model_initialization.py @@ -6,6 +6,7 @@ from dbgpt.storage.chat_history.chat_history_db import ( ChatHistoryMessageEntity, ) from dbgpt_app.openapi.api_v1.feedback.feed_back_db import ChatFeedBackEntity +from dbgpt_app.share.models import ShareLinkEntity from dbgpt_serve.agent.app.recommend_question.recommend_question import ( RecommendQuestionEntity, ) @@ -38,4 +39,5 @@ _MODELS = [ RecommendQuestionEntity, FlowVariableEntity, BenchmarkSummaryEntity, + ShareLinkEntity, ] diff --git a/packages/dbgpt-app/src/dbgpt_app/openapi/api_v1/agentic_data_api.py b/packages/dbgpt-app/src/dbgpt_app/openapi/api_v1/agentic_data_api.py index 117953dfc..b3e2382a7 100644 --- a/packages/dbgpt-app/src/dbgpt_app/openapi/api_v1/agentic_data_api.py +++ b/packages/dbgpt-app/src/dbgpt_app/openapi/api_v1/agentic_data_api.py @@ -13,6 +13,7 @@ from fastapi import APIRouter, Body, Depends, File, Query, UploadFile from fastapi.responses import StreamingResponse from dbgpt._private.config import Config +from dbgpt._private.pydantic import BaseModel as _BaseModel from dbgpt.agent.resource.tool.base import tool from dbgpt.agent.skill.manage import get_skill_manager from dbgpt.component import ComponentType @@ -26,6 +27,7 @@ from dbgpt_app.openapi.api_view_model import ( from dbgpt_serve.utils.auth import UserRequest, get_user_from_headers from dbgpt_serve.datasource.manages import ConnectorManager + router = APIRouter() CFG = Config() logger = logging.getLogger(__name__) @@ -621,7 +623,7 @@ async def _react_agent_stream( database_context = f""" ## 数据库信息 - 数据库名: {database_name} -- 可用表: {', '.join(table_names)} +- 可用表: {", ".join(table_names)} - 表结构: {table_info} - 使用 'sql_query' 工具执行 SQL 查询 @@ -632,9 +634,7 @@ async def _react_agent_stream( f"(tables: {', '.join(table_names)})" ) except Exception as e: - logger.warning( - f"Failed to load database connector: {e}", exc_info=e - ) + logger.warning(f"Failed to load database connector: {e}", exc_info=e) database_context = f""" ## 数据库 - 警告: 加载数据库 '{database_name}' 失败。错误: {str(e)} @@ -1050,7 +1050,7 @@ print(json.dumps(summary, ensure_ascii=False)) @tool( description=( "对用户选择的数据库执行 SQL 查询(仅支持 SELECT)。" - "参数: {\"sql\": \"SELECT 语句\"}" + '参数: {"sql": "SELECT 语句"}' ) ) def sql_query(sql: str) -> str: @@ -1071,8 +1071,15 @@ print(json.dumps(summary, ensure_ascii=False)) sql_stripped = sql.strip().rstrip(";") sql_upper = sql_stripped.upper().lstrip() forbidden = [ - "INSERT", "UPDATE", "DELETE", "DROP", - "ALTER", "TRUNCATE", "CREATE", "GRANT", "REVOKE", + "INSERT", + "UPDATE", + "DELETE", + "DROP", + "ALTER", + "TRUNCATE", + "CREATE", + "GRANT", + "REVOKE", ] for kw in forbidden: if sql_upper.startswith(kw): @@ -1102,9 +1109,7 @@ print(json.dumps(summary, ensure_ascii=False)) # result[0] = column names, result[1:] = data rows columns = result[0] - col_names = [ - str(c[0]) if isinstance(c, tuple) else str(c) for c in columns - ] + col_names = [str(c[0]) if isinstance(c, tuple) else str(c) for c in columns] rows = result[1:] # Build markdown table @@ -1112,21 +1117,13 @@ print(json.dumps(summary, ensure_ascii=False)) separator = "| " + " | ".join(["---"] * len(col_names)) + " |" md_rows = [] for row in rows[:50]: - md_rows.append( - "| " - + " | ".join(str(v) for v in row) - + " |" - ) + md_rows.append("| " + " | ".join(str(v) for v in row) + " |") table = "\n".join([header, separator] + md_rows) if len(rows) > 50: table += f"\n\n(仅显示前 50 行,共 {len(rows)} 行)" return json.dumps( - { - "chunks": [ - {"output_type": "markdown", "content": table} - ] - }, + {"chunks": [{"output_type": "markdown", "content": table}]}, ensure_ascii=False, ) except Exception as e: @@ -2481,6 +2478,119 @@ Action Input: 工具参数的 JSON 格式 yield _sse_event({"type": "done"}) +# --------------------------------------------------------------------------- +# Share link APIs +# --------------------------------------------------------------------------- + + +class ShareCreateRequest(_BaseModel): + """Request body for creating a share link.""" + + conv_uid: str + + +class ShareCreateResponse(_BaseModel): + """Response body for share link creation.""" + + token: str + conv_uid: str + share_url: str + + +class ShareConvResponse(_BaseModel): + """Public payload returned when viewing a shared conversation.""" + + conv_uid: str + token: str + messages: list # list[{role, context, order}] + + +def _get_share_dao(): + """Lazily instantiate the ShareLinkDao (avoids import-time side-effects).""" + from dbgpt_app.share.models import ShareLinkDao + + return ShareLinkDao() + + +def _get_conversation_service(): + """Return the ConversationServe Service component.""" + from dbgpt_serve.conversation.service.service import Service + from dbgpt_serve.conversation.config import SERVE_SERVICE_COMPONENT_NAME + + return CFG.SYSTEM_APP.get_component(SERVE_SERVICE_COMPONENT_NAME, Service) + + +@router.post("/v1/chat/share", response_model=Result) +async def create_share_link( + body: ShareCreateRequest = Body(), + user_token: UserRequest = Depends(get_user_from_headers), +): + """Create (or return existing) share link for a conversation. + + The returned ``share_url`` is a relative path that the client should + prepend with the current host to form an absolute URL. + """ + dao = _get_share_dao() + created_by = user_token.user_id if user_token else None + entity = dao.create_share(conv_uid=body.conv_uid, created_by=created_by) + if entity is None: + return Result.failed(msg="Failed to create share link") + return Result.succ( + ShareCreateResponse( + token=entity.token, + conv_uid=entity.conv_uid, + share_url=f"/share/{entity.token}", + ) + ) + + +@router.get("/v1/chat/share/{token}", response_model=Result) +async def get_share_conversation(token: str): + """Public endpoint — no authentication required. + + Returns the full conversation history for the given share token so that the + replay page can reconstruct and animate the session. + """ + dao = _get_share_dao() + link = dao.get_by_token(token) + if link is None: + from fastapi import HTTPException + + raise HTTPException(status_code=404, detail="Share link not found") + + service = _get_conversation_service() + from dbgpt_serve.conversation.api.schemas import ServeRequest + + history = service.get_history_messages(ServeRequest(conv_uid=link.conv_uid)) + + messages = [ + {"role": m.role, "context": m.context, "order": m.order} + for m in (history or []) + ] + return Result.succ( + ShareConvResponse( + conv_uid=link.conv_uid, + token=token, + messages=messages, + ) + ) + + +@router.delete("/v1/chat/share/{token}", response_model=Result) +async def delete_share_link( + token: str, + user_token: UserRequest = Depends(get_user_from_headers), +): + """Revoke a share link. Only the owner (or any authenticated user) may delete.""" + dao = _get_share_dao() + deleted = dao.delete_by_token(token) + if not deleted: + from fastapi import HTTPException + + raise HTTPException(status_code=404, detail="Share link not found") + return Result.succ({"deleted": True, "token": token}) + + @router.post("/v1/chat/react-agent") async def chat_react_agent( dialogue: ConversationVo = Body(), diff --git a/packages/dbgpt-app/src/dbgpt_app/share/__init__.py b/packages/dbgpt-app/src/dbgpt_app/share/__init__.py new file mode 100644 index 000000000..43d13d020 --- /dev/null +++ b/packages/dbgpt-app/src/dbgpt_app/share/__init__.py @@ -0,0 +1 @@ +"""Share link sub-package.""" diff --git a/packages/dbgpt-app/src/dbgpt_app/share/models.py b/packages/dbgpt-app/src/dbgpt_app/share/models.py new file mode 100644 index 000000000..9fb98cae0 --- /dev/null +++ b/packages/dbgpt-app/src/dbgpt_app/share/models.py @@ -0,0 +1,114 @@ +"""Share link database models and DAO.""" + +import secrets +from datetime import datetime +from typing import Optional + +from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint + +from dbgpt.storage.metadata import BaseDao, Model + + +class ShareLinkEntity(Model): + """Share link entity — maps a random token to a conversation.""" + + __tablename__ = "share_links" + __table_args__ = (UniqueConstraint("token", name="uk_share_token"),) + + id = Column(Integer, primary_key=True, autoincrement=True, comment="Primary key") + token = Column( + String(64), + unique=False, # enforced by UniqueConstraint above + nullable=False, + index=True, + comment="Unique random share token", + ) + conv_uid = Column( + String(255), + nullable=False, + index=True, + comment="The conversation uid being shared", + ) + created_by = Column( + String(255), + nullable=True, + comment="User who created the share link", + ) + gmt_created = Column( + DateTime, + default=datetime.now, + comment="Creation time", + ) + + +class ShareLinkDao(BaseDao): + """DAO for share_links table.""" + + def create_share( + self, conv_uid: str, created_by: Optional[str] = None + ) -> Optional[ShareLinkEntity]: + """Create or return an existing share link for a conversation. + + If a share link already exists for this ``conv_uid``, it is returned + as-is so that repeat clicks always yield the same URL. + """ + # Check for an existing link first (read-only session) + existing = self.get_by_conv_uid(conv_uid) + if existing is not None: + return existing + + token = secrets.token_urlsafe(32) + entity = ShareLinkEntity( + token=token, + conv_uid=conv_uid, + created_by=created_by, + gmt_created=datetime.now(), + ) + with self.session() as session: + session.add(entity) + session.flush() + # Eagerly load all columns before the session closes + session.refresh(entity) + # Detach-safe: copy values into a plain object + result = ShareLinkEntity( + id=entity.id, + token=entity.token, + conv_uid=entity.conv_uid, + created_by=entity.created_by, + gmt_created=entity.gmt_created, + ) + return result + + def get_by_token(self, token: str) -> Optional[ShareLinkEntity]: + """Retrieve a share link by token.""" + with self.session(commit=False) as session: + row = session.query(ShareLinkEntity).filter_by(token=token).first() + if row is None: + return None + return ShareLinkEntity( + id=row.id, + token=row.token, + conv_uid=row.conv_uid, + created_by=row.created_by, + gmt_created=row.gmt_created, + ) + + def get_by_conv_uid(self, conv_uid: str) -> Optional[ShareLinkEntity]: + """Retrieve a share link by conversation uid.""" + with self.session(commit=False) as session: + row = session.query(ShareLinkEntity).filter_by(conv_uid=conv_uid).first() + if row is None: + return None + return ShareLinkEntity( + id=row.id, + token=row.token, + conv_uid=row.conv_uid, + created_by=row.created_by, + gmt_created=row.gmt_created, + ) + + def delete_by_token(self, token: str) -> bool: + """Delete a share link by token. Returns True if a row was deleted.""" + with self.session() as session: + rows = session.query(ShareLinkEntity).filter_by(token=token).delete() + return rows > 0 diff --git a/web/new-components/chat/content/ManusLeftPanel.tsx b/web/new-components/chat/content/ManusLeftPanel.tsx index 24c80d8d8..b2aa0fbe9 100644 --- a/web/new-components/chat/content/ManusLeftPanel.tsx +++ b/web/new-components/chat/content/ManusLeftPanel.tsx @@ -80,6 +80,7 @@ export interface ManusLeftPanelProps { onArtifactClick?: (artifact: ArtifactItem) => void; onArtifactDownload?: (artifact: ArtifactItem) => void; onViewAllFiles?: () => void; + onShare?: () => void; isCollapsed?: boolean; onExpand?: () => void; attachedFile?: { @@ -364,7 +365,7 @@ const StepCard: React.FC<{ onClick: () => void; }> = memo(({ step, isActive, onClick }) => { const [isVisible, setIsVisible] = useState(false); - const detailLine = step.detail ? step.detail.split('\n')[0] : ''; + const detailLine = step.description ? step.description.split('\n')[0] : ''; React.useEffect(() => { const timer = setTimeout(() => setIsVisible(true), 50); @@ -680,6 +681,7 @@ const ManusLeftPanel: React.FC = ({ onArtifactClick, onArtifactDownload, onViewAllFiles, + onShare, isCollapsed, onExpand, attachedFile, @@ -839,7 +841,7 @@ const ManusLeftPanel: React.FC = ({ )} {artifacts && artifacts.length > 0 && ( -
+
{artifacts .filter(a => a.type === 'file' || a.type === 'html') @@ -866,7 +868,7 @@ const ManusLeftPanel: React.FC = ({ )}
-
+
任务已完成
@@ -877,8 +879,10 @@ const ManusLeftPanel: React.FC = ({ {modelName && (
- Model: {modelName} - {isWorking && Processing...} + {`Model: ${modelName}`} +
+ {isWorking && Processing...} +
)} diff --git a/web/new-components/chat/content/ManusRightPanel.tsx b/web/new-components/chat/content/ManusRightPanel.tsx index df5f54f63..f40828c1f 100644 --- a/web/new-components/chat/content/ManusRightPanel.tsx +++ b/web/new-components/chat/content/ManusRightPanel.tsx @@ -23,6 +23,7 @@ import { FileTextOutlined, FolderOpenOutlined, LeftOutlined, + LinkOutlined, LoadingOutlined, PlayCircleOutlined, SearchOutlined, @@ -74,6 +75,7 @@ export interface ManusRightPanelProps { outputs: ExecutionOutput[]; isRunning?: boolean; onRerun?: () => void; + onShare?: () => void; terminalTitle?: string; onCollapse?: () => void; isCollapsed?: boolean; @@ -888,6 +890,7 @@ const ManusRightPanel: React.FC = ({ outputs, isRunning, onRerun, + onShare, terminalTitle = 'DB-GPT 的电脑', onCollapse, artifacts, @@ -1065,6 +1068,20 @@ const ManusRightPanel: React.FC = ({ )} + {onShare && ( + + + + )} +
diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index 1eeb181c1..280b0736e 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -85,12 +85,12 @@ function LayoutWrapper({ children }: { children: React.ReactNode }) { handleAuth(); }, []); - if (!isLogin) { + if (!isLogin && !router.pathname.startsWith('/share')) { return null; } const renderContent = () => { - if (router.pathname.includes('mobile')) { + if (router.pathname.includes('mobile') || router.pathname.startsWith('/share')) { return <>{children}; } return ( diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 18f0f6303..bcfa4e4d7 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -31,6 +31,7 @@ import { FileOutlined, FilePptOutlined, FileTextOutlined, + LinkOutlined, PieChartOutlined, PlusOutlined, ReadOutlined, @@ -1844,6 +1845,25 @@ const Playground: NextPage = () => { } }; + // Share current conversation — create share link and copy to clipboard + const handleShare = async () => { + if (!conversationId) { + message.warning('请先开始一段对话再分享'); + return; + } + try { + const res: any = await axios.post('/api/v1/chat/share', { conv_uid: conversationId }); + const shareUrl = res?.data?.share_url; + if (!shareUrl) throw new Error('No share URL returned'); + const fullUrl = `${window.location.origin}${shareUrl}`; + await navigator.clipboard.writeText(fullUrl); + message.success('分享链接已复制到剪贴板!'); + } catch (e) { + console.error('Failed to create share link', e); + message.error('创建分享链接失败,请稍后重试'); + } + }; + const _QuickAction = ({ icon, text, onClick }: { icon: any; text: string; onClick?: () => void }) => (
{ isRunning={isRunning} onCollapse={() => setRightPanelCollapsed(true)} onRerun={() => {}} + onShare={!loading && !!conversationId ? handleShare : undefined} terminalTitle='DB-GPT 的电脑' artifacts={artifacts.filter(a => a.messageId === activeViewMsg?.id)} onArtifactClick={artifact => { diff --git a/web/pages/share/[token].tsx b/web/pages/share/[token].tsx new file mode 100644 index 000000000..62c18bc9e --- /dev/null +++ b/web/pages/share/[token].tsx @@ -0,0 +1,771 @@ +/** + * /share/[token] — 对话回放页面 + * + * 从后端获取共享对话数据,逐步动画展示每一轮的思考→行动→输出过程。 + * 无需登录,只读,不可继续对话。 + */ + +import ManusLeftPanel, { + ArtifactItem, + ThinkingSection, + ExecutionStep as ManusExecutionStep, + StepType, +} from '@/new-components/chat/content/ManusLeftPanel'; +import ManusRightPanel, { + ExecutionOutput as ManusExecutionOutput, + ActiveStepInfo, + PanelView, +} from '@/new-components/chat/content/ManusRightPanel'; +import { + LinkOutlined, + PauseCircleOutlined, + PlayCircleOutlined, + ReloadOutlined, + StepForwardOutlined, +} from '@ant-design/icons'; +import { Button, Tooltip, message } from 'antd'; +import { GetServerSideProps, NextPage } from 'next'; +import Head from 'next/head'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +// --------------------------------------------------------------------------- +// Types mirroring index.tsx +// --------------------------------------------------------------------------- + +interface RawStep { + id: string; + title?: string; + detail?: string; + thought?: string; + action?: string; + action_input?: any; + outputs?: Array<{ output_type: string; content: any }>; + status?: string; +} + +interface RawPayload { + version: number; + type: string; + final_content: string; + steps: RawStep[]; + generated_images?: string[]; +} + +interface ParsedMessage { + role: 'human' | 'view'; + context: string; + order: number; + payload?: RawPayload; // only for view messages with react-agent payload +} + +/** A fully prepared "round" ready for the replay engine */ +interface ReplayRound { + humanText: string; + steps: ManusExecutionStep[]; + outputs: Record; + stepThoughts: Record; + finalContent: string; + artifacts: ArtifactItem[]; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +const getStepType = (title?: string): StepType => { + const lower = (title || '').toLowerCase(); + if (lower.includes('load_skill') || lower.includes('load skill')) return 'skill'; + if (lower.includes('sql_query') || lower.includes('sql query') || lower.includes('sql')) return 'sql'; + if (lower.includes('read') || lower.includes('load')) return 'read'; + if (lower.includes('edit')) return 'edit'; + if (lower.includes('write') || lower.includes('save')) return 'write'; + if (lower.includes('bash') || lower.includes('execute') || lower.includes('command')) return 'bash'; + if (lower.includes('grep') || lower.includes('search')) return 'grep'; + if (lower.includes('glob') || lower.includes('find')) return 'glob'; + if (lower.includes('html')) return 'html'; + if (lower.includes('python') || lower.includes('code')) return 'python'; + if (lower.includes('skill')) return 'skill'; + if (lower.includes('task')) return 'task'; + return 'other'; +}; + +/** Extract ArtifactItem[] from a round's steps+outputs (mirrors index.tsx buildArtifactsFromExecution) */ +function buildArtifacts(roundId: string, steps: ManusExecutionStep[], outputs: Record, finalContent: string): ArtifactItem[] { + const artifacts: ArtifactItem[] = []; + const now = Date.now(); + const seenCodeHashes = new Set(); + + steps.forEach(step => { + const stepOutputs = outputs[step.id] || []; + stepOutputs.forEach((output, oIdx) => { + if (output.output_type === 'code') { + const codeStr = String(output.content || '').trim(); + const hash = codeStr.slice(0, 200); + if (codeStr && !seenCodeHashes.has(hash)) { + seenCodeHashes.add(hash); + artifacts.push({ + id: `${roundId}-code-${step.id}-${oIdx}`, + type: 'code', + name: `code_${oIdx + 1}.py`, + content: codeStr, + createdAt: now, + downloadable: true, + }); + } + } else if (output.output_type === 'file') { + artifacts.push({ + id: `${roundId}-file-${step.id}-${oIdx}`, + type: 'file', + name: output.content?.name || output.content?.file_name || 'File', + content: output.content, + createdAt: now, + downloadable: true, + size: output.content?.size, + }); + } else if (output.output_type === 'html') { + const htmlContent = typeof output.content === 'string' + ? output.content + : output.content?.content || output.content?.html || String(output.content); + const htmlTitle = output.content?.title || 'Report'; + artifacts.push({ + id: `${roundId}-html-${step.id}-${oIdx}`, + type: 'html', + name: `${htmlTitle}.html`, + content: htmlContent, + createdAt: now, + downloadable: true, + }); + } else if (output.output_type === 'image') { + const imgUrl = typeof output.content === 'string' + ? output.content + : output.content?.url || output.content?.src || String(output.content); + const imgName = imgUrl.split('/').pop() || `image_${oIdx}.png`; + artifacts.push({ + id: `${roundId}-img-${step.id}-${oIdx}`, + type: 'image', + name: imgName.replace(/^[a-f0-9]{8}_/, ''), + content: imgUrl, + createdAt: now, + downloadable: true, + }); + } + }); + }); + + // Also scan final_content for file references (e.g. "已保存到 xxx.xlsx") + const fileRefRegex = /[\w\u4e00-\u9fa5_\-]+\.(xlsx?|csv|pdf|png|jpg|jpeg|gif|html?|txt|zip|json)/gi; + const matches = finalContent.matchAll(fileRefRegex); + for (const m of matches) { + const name = m[0]; + if (!artifacts.some(a => a.name.toLowerCase() === name.toLowerCase())) { + artifacts.push({ + id: `${roundId}-fileref-${name}`, + type: 'file', + name, + content: { name }, + createdAt: now, + downloadable: true, + }); + } + } + + return artifacts; +} + +/** Build ReplayRound[] from raw messages returned by the share API */ +function buildReplayRounds(rawMessages: Array<{ role: string; context: string; order: number }>): ReplayRound[] { + const rounds: ReplayRound[] = []; + let pendingHuman: string | null = null; + + for (const msg of rawMessages) { + if (msg.role === 'human') { + pendingHuman = msg.context; + } else if (msg.role === 'view') { + let payload: RawPayload | null = null; + try { + const parsed = JSON.parse(msg.context); + if (parsed?.version === 1 && parsed?.type === 'react-agent') { + payload = parsed as RawPayload; + } + } catch { + /* ignore */ + } + + if (!payload) continue; + + const steps: ManusExecutionStep[] = []; + const outputs: Record = {}; + const stepThoughts: Record = {}; + + (payload.steps || []).forEach((s, idx) => { + const stepId = s.id || `step-${idx}`; + // Filter terminate steps + if ((s.detail || '').toLowerCase().includes('action: terminate')) return; + + steps.push({ + id: stepId, + type: getStepType(s.title), + title: s.title || `Step ${idx + 1}`, + subtitle: (s.detail || '').split('\n')[0]?.slice(0, 80), + description: s.detail || undefined, + status: s.status === 'failed' ? 'error' : 'completed', + }); + + const stepOutputs: ManusExecutionOutput[] = []; + // Prepend code from action_input for code_interpreter steps + if (s.action === 'code_interpreter' && s.action_input) { + try { + const inp = typeof s.action_input === 'string' ? JSON.parse(s.action_input) : s.action_input; + if (inp?.code) stepOutputs.push({ output_type: 'code', content: inp.code }); + } catch { /* ignore */ } + } + if (Array.isArray(s.outputs)) { + s.outputs.forEach(o => stepOutputs.push({ output_type: o.output_type as any, content: o.content })); + } + outputs[stepId] = stepOutputs; + if (s.thought) stepThoughts[stepId] = s.thought; + }); + + const artifacts = buildArtifacts(`round-${rounds.length}`, steps, outputs, payload.final_content || ''); + rounds.push({ + humanText: pendingHuman || '', + steps, + outputs, + stepThoughts, + finalContent: payload.final_content || '', + artifacts, + }); + pendingHuman = null; + } + } + + return rounds; +} + +/** Build ThinkingSection[] from a round's steps (same grouping logic as index.tsx) */ +function buildSections(steps: ManusExecutionStep[]): ThinkingSection[] { + if (steps.length === 0) return []; + + const thinkSteps = steps.filter(s => + s.title?.toLowerCase().includes('think') || s.title?.toLowerCase().includes('plan'), + ); + const skillSteps = steps.filter(s => s.title?.toLowerCase().includes('skill')); + const otherSteps = steps.filter( + s => + !s.title?.toLowerCase().includes('think') && + !s.title?.toLowerCase().includes('plan') && + !s.title?.toLowerCase().includes('skill'), + ); + + const sections: ThinkingSection[] = []; + if (thinkSteps.length > 0) sections.push({ id: 'section-think', title: '分析与规划', isCompleted: true, steps: thinkSteps }); + if (skillSteps.length > 0) sections.push({ id: 'section-skill', title: '技能加载', isCompleted: true, steps: skillSteps }); + if (otherSteps.length > 0) sections.push({ id: 'section-execution', title: '数据处理与执行', isCompleted: true, steps: otherSteps }); + if (sections.length === 0) sections.push({ id: 'section-main', title: '执行过程', isCompleted: true, steps }); + return sections; +} + +// --------------------------------------------------------------------------- +// Replay speed config +// --------------------------------------------------------------------------- + +const SPEED_OPTIONS = [ + { label: '0.5×', value: 0.5 }, + { label: '1×', value: 1 }, + { label: '2×', value: 2 }, + { label: '4×', value: 4 }, +]; + +/** Delays (ms) per event at 1× speed */ +const BASE_DELAYS = { + beforeStep: 600, // pause before revealing a step + afterStep: 400, // pause after a step appears before showing outputs + betweenOutputs: 300, + beforeFinal: 700, + betweenRounds: 1000, +}; + +// --------------------------------------------------------------------------- +// useReplayEngine hook +// --------------------------------------------------------------------------- + +/** Represents the visible state at any instant during replay */ +interface ReplayState { + /** Which round index is currently showing (0-based) */ + roundIndex: number; + /** Steps revealed so far in the current round (all previous rounds are fully shown) */ + visibleStepCount: number; + /** The step currently "active" (highlighted in the right panel) */ + activeStepId: string | null; + /** Whether we are showing the final summary for the current round */ + showFinalForRound: boolean; + /** Index of the last fully-finished round */ + completedRoundIndex: number; + /** Whether replay has finished */ + done: boolean; +} + +function useReplayEngine(rounds: ReplayRound[], speed: number, autoPlay = false) { + const [state, setState] = useState({ + roundIndex: 0, + visibleStepCount: 0, + activeStepId: null, + showFinalForRound: false, + completedRoundIndex: -1, + done: false, + }); + const [playing, setPlaying] = useState(false); + + const timerRef = useRef | null>(null); + const stateRef = useRef(state); + stateRef.current = state; + const playingRef = useRef(playing); + playingRef.current = playing; + const speedRef = useRef(speed); + speedRef.current = speed; + + const delay = useCallback((ms: number) => { + return new Promise(resolve => { + timerRef.current = setTimeout(resolve, ms / speedRef.current); + }); + }, []); + + /** Core replay loop — runs as an async generator stepping through all events */ + const runLoop = useCallback(async () => { + for (let ri = stateRef.current.roundIndex; ri < rounds.length; ri++) { + const round = rounds[ri]; + + // Restore already-finished rounds instantly + if (ri < stateRef.current.roundIndex) continue; + + for (let si = stateRef.current.visibleStepCount; si < round.steps.length; si++) { + if (!playingRef.current) return; // paused + + const step = round.steps[si]; + await delay(BASE_DELAYS.beforeStep); + if (!playingRef.current) return; + + // Reveal the step + setState(prev => ({ + ...prev, + roundIndex: ri, + visibleStepCount: si + 1, + activeStepId: step.id, + showFinalForRound: false, + })); + + await delay(BASE_DELAYS.afterStep); + if (!playingRef.current) return; + } + + // Show final summary + await delay(BASE_DELAYS.beforeFinal); + if (!playingRef.current) return; + + setState(prev => ({ + ...prev, + roundIndex: ri, + visibleStepCount: round.steps.length, + activeStepId: round.steps.length > 0 ? round.steps[round.steps.length - 1].id : null, + showFinalForRound: true, + completedRoundIndex: ri, + })); + + if (ri < rounds.length - 1) { + await delay(BASE_DELAYS.betweenRounds); + if (!playingRef.current) return; + // Move to next round + setState(prev => ({ + ...prev, + roundIndex: ri + 1, + visibleStepCount: 0, + activeStepId: null, + showFinalForRound: false, + })); + } + } + + // Replay complete + setState(prev => ({ ...prev, done: true })); + setPlaying(false); + }, [rounds, delay]); + + const play = useCallback(() => { + if (stateRef.current.done) return; + setPlaying(true); + }, []); + + const pause = useCallback(() => { + setPlaying(false); + if (timerRef.current) clearTimeout(timerRef.current); + }, []); + + // Start/resume loop when playing becomes true + useEffect(() => { + if (playing) { + runLoop(); + } + return () => { + if (timerRef.current) clearTimeout(timerRef.current); + }; + }, [playing, runLoop]); + + // Auto-play on mount when rounds are ready + useEffect(() => { + if (autoPlay && rounds.length > 0) { + setPlaying(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + /** Jump to a specific round (skips to its end instantly) */ + const jumpToRound = useCallback((ri: number) => { + pause(); + setState({ + roundIndex: ri, + visibleStepCount: rounds[ri]?.steps.length ?? 0, + activeStepId: rounds[ri]?.steps.slice(-1)[0]?.id ?? null, + showFinalForRound: true, + completedRoundIndex: ri, + done: ri === rounds.length - 1, + }); + }, [pause, rounds]); + + /** Restart replay from the beginning */ + const restart = useCallback(() => { + pause(); + setState({ + roundIndex: 0, + visibleStepCount: 0, + activeStepId: null, + showFinalForRound: false, + completedRoundIndex: -1, + done: false, + }); + // Small delay to let state settle before starting loop + setTimeout(() => setPlaying(true), 50); + }, [pause]); + + return { state, playing, play, pause, jumpToRound, restart }; +} + +// --------------------------------------------------------------------------- +// Page component +// --------------------------------------------------------------------------- + +interface SharePageProps { + token: string; + messages?: Array<{ role: string; context: string; order: number }>; + firstQuestion?: string; + error?: string; +} + +const SharePage: NextPage = ({ token, messages: initMessages, firstQuestion, error: initError }) => { + const [fetchError] = useState(initError || null); + const [speed, setSpeed] = useState(1); + const [rightPanelView, setRightPanelView] = useState('execution'); + + const rounds = initMessages ? buildReplayRounds(initMessages) : []; + const { state, playing, play, pause, jumpToRound, restart } = useReplayEngine(rounds, speed, true); + + // ------------------------------------------------------------------------- + // Derive display data from replay state + // ------------------------------------------------------------------------- + + /** For each round index, compute what's visible */ + const getVisibleRoundData = (ri: number) => { + const round = rounds[ri]; + if (!round) return null; + + const isCurrentRound = ri === state.roundIndex; + const isCompletedRound = ri < state.roundIndex || (ri === state.roundIndex && state.showFinalForRound); + + const visibleSteps: ManusExecutionStep[] = isCurrentRound + ? round.steps.slice(0, state.visibleStepCount).map((s, idx) => ({ + ...s, + status: idx < state.visibleStepCount - 1 ? 'completed' : (playing ? 'running' : 'completed'), + })) + : isCompletedRound + ? round.steps.map(s => ({ ...s, status: 'completed' as const })) + : []; + + const sections = buildSections(visibleSteps); + const showFinal = isCompletedRound || (isCurrentRound && state.showFinalForRound); + const activeStepId = isCurrentRound ? state.activeStepId : (isCompletedRound ? round.steps.slice(-1)[0]?.id ?? null : null); + + return { round, visibleSteps, sections, showFinal, activeStepId }; + }; + + // Active round for the right panel + const activeRoundData = getVisibleRoundData(state.roundIndex); + const activeStepId = state.activeStepId; + + const rightPanelOutputs: ManusExecutionOutput[] = (() => { + if (!activeRoundData || !activeStepId) return []; + return activeRoundData.round.outputs[activeStepId] || []; + })(); + + const rightPanelActiveStep: ActiveStepInfo | null = (() => { + if (!activeRoundData || !activeStepId) return null; + const step = activeRoundData.round.steps.find(s => s.id === activeStepId); + if (!step) return null; + return { + id: step.id, + type: step.type, + title: step.title || '', + subtitle: step.subtitle, + detail: step.description, + status: playing ? 'running' : 'completed', + }; + })(); + + // Progress: total steps across all rounds + const totalSteps = rounds.reduce((acc, r) => acc + r.steps.length, 0); + const completedSteps = rounds.slice(0, state.roundIndex).reduce((acc, r) => acc + r.steps.length, 0) + state.visibleStepCount; + const progressPercent = totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0; + + const handleCopyLink = async () => { + try { + await navigator.clipboard.writeText(window.location.href); + message.success('链接已复制'); + } catch { + message.error('复制失败'); + } + }; + + // ------------------------------------------------------------------------- + // Render + // ------------------------------------------------------------------------- + + if (fetchError || rounds.length === 0) { + return ( +
+
+

分享链接无效或已过期

+

{fetchError || '没有可回放的对话内容'}

+
+
+ ); + } + + return ( + <> + + {firstQuestion ? `${firstQuestion.slice(0, 60)}${firstQuestion.length > 60 ? '…' : ''} · DB-GPT 回放` : 'DB-GPT 对话回放'} + + +
+ {/* ---------------------------------------------------------------- */} + {/* Top bar */} + {/* ---------------------------------------------------------------- */} +
+
+ {/* Logo / brand */} + DB-GPT +
+ 回放 + {firstQuestion && ( + + {firstQuestion} + + )} +
+ + {/* Playback controls */} +
+ {/* Speed selector */} +
+ {SPEED_OPTIONS.map(opt => ( + + ))} +
+ + {/* Divider */} +
+ + {/* Play / Pause */} + {state.done ? ( + + ) : playing ? ( + + ) : ( + + )} + + {/* Skip to end */} + {!state.done && ( + + + +
+
+ + {/* ---------------------------------------------------------------- */} + {/* Progress bar */} + {/* ---------------------------------------------------------------- */} +
+
+
+
+
+
+
+ + {completedSteps} / {totalSteps} 步 + + {/* Round selector tabs */} + {rounds.length > 1 && ( +
+ {rounds.map((round, ri) => ( + + + + ))} +
+ )} +
+
+ + {/* ---------------------------------------------------------------- */} + {/* Main content: left (steps) + right (output) */} + {/* ---------------------------------------------------------------- */} +
+ {/* Left panel — all rounds stacked */} +
+ {rounds.map((_, ri) => { + const data = getVisibleRoundData(ri); + if (!data) return null; + + const isCurrentRound = ri === state.roundIndex; + const isPastRound = ri < state.roundIndex; + const isFutureRound = ri > state.roundIndex; + + // Future rounds are hidden until the replay reaches them + if (isFutureRound) return null; + + const isCollapsed = isPastRound; + + return ( + setRightPanelView('files') : undefined} + isCollapsed={isCollapsed} + onExpand={() => jumpToRound(ri)} + /> + ); + })} + + {/* Pending placeholder for rounds not yet reached */} + {rounds.length > 1 && state.roundIndex < rounds.length - 1 && ( +
+ {rounds.length - state.roundIndex - 1} 轮待回放... +
+ )} + {/* Bottom breathing room */} +
+
+ + {/* Right panel — outputs of the active step */} +
+ +
+
+
+ + ); +}; + +export const getServerSideProps: GetServerSideProps = async context => { + const { token } = context.params as { token: string }; + const apiBase = process.env.API_BASE_URL ?? 'http://127.0.0.1:5670'; + try { + const res = await fetch(`${apiBase}/api/v1/chat/share/${token}`); + if (!res.ok) { + return { props: { token, error: `分享链接无效或已过期 (${res.status})` } }; + } + const json = await res.json(); + const messages = json?.data?.messages ?? null; + if (!Array.isArray(messages)) { + return { props: { token, error: '数据格式错误' } }; + } + const firstQuestion: string = messages.find((m: any) => m.role === 'human')?.context ?? ''; + return { props: { token, messages, firstQuestion } }; + } catch (e: any) { + return { props: { token, error: e?.message || '加载失败' } }; + } +}; + +export default SharePage;