mirror of
https://github.com/csunny/DB-GPT.git
synced 2026-07-17 01:58:47 +00:00
feat: support chat share and playback
This commit is contained in:
15
assets/schema/upgrade/v0_8_0/upgrade_to_v0.8.0.sql
Normal file
15
assets/schema/upgrade/v0_8_0/upgrade_to_v0.8.0.sql
Normal file
@@ -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';
|
||||
637
assets/schema/upgrade/v0_8_0/v0.8.0.sql
Normal file
637
assets/schema/upgrade/v0_8_0/v0.8.0.sql
Normal file
@@ -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');
|
||||
@@ -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,
|
||||
]
|
||||
|
||||
@@ -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(),
|
||||
|
||||
1
packages/dbgpt-app/src/dbgpt_app/share/__init__.py
Normal file
1
packages/dbgpt-app/src/dbgpt_app/share/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Share link sub-package."""
|
||||
114
packages/dbgpt-app/src/dbgpt_app/share/models.py
Normal file
114
packages/dbgpt-app/src/dbgpt_app/share/models.py
Normal file
@@ -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
|
||||
@@ -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<ManusLeftPanelProps> = ({
|
||||
onArtifactClick,
|
||||
onArtifactDownload,
|
||||
onViewAllFiles,
|
||||
onShare,
|
||||
isCollapsed,
|
||||
onExpand,
|
||||
attachedFile,
|
||||
@@ -839,7 +841,7 @@ const ManusLeftPanel: React.FC<ManusLeftPanelProps> = ({
|
||||
)}
|
||||
|
||||
{artifacts && artifacts.length > 0 && (
|
||||
<div className='mt-5 px-1'>
|
||||
<div className='mt-5 px-1 pb-8'>
|
||||
<div className='flex flex-wrap gap-3'>
|
||||
{artifacts
|
||||
.filter(a => a.type === 'file' || a.type === 'html')
|
||||
@@ -866,7 +868,7 @@ const ManusLeftPanel: React.FC<ManusLeftPanelProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-1.5 mt-4'>
|
||||
<div className='flex items-center gap-1.5 mt-5'>
|
||||
<CheckOutlined className='text-xs text-emerald-500' />
|
||||
<span className='text-sm text-emerald-600 dark:text-emerald-400 font-medium'>任务已完成</span>
|
||||
</div>
|
||||
@@ -877,8 +879,10 @@ const ManusLeftPanel: React.FC<ManusLeftPanelProps> = ({
|
||||
{modelName && (
|
||||
<div className='px-4 py-2 border-t border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-900/50'>
|
||||
<div className='flex items-center justify-between text-[10px] text-gray-400'>
|
||||
<span>Model: {modelName}</span>
|
||||
{isWorking && <span className='animate-pulse'>Processing...</span>}
|
||||
<span>{`Model: ${modelName}`}</span>
|
||||
<div className='flex items-center gap-2'>
|
||||
{isWorking && <span className='animate-pulse'>Processing...</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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<ManusRightPanelProps> = ({
|
||||
outputs,
|
||||
isRunning,
|
||||
onRerun,
|
||||
onShare,
|
||||
terminalTitle = 'DB-GPT 的电脑',
|
||||
onCollapse,
|
||||
artifacts,
|
||||
@@ -1065,6 +1068,20 @@ const ManusRightPanel: React.FC<ManusRightPanelProps> = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{onShare && (
|
||||
<Tooltip title='分享此对话'>
|
||||
<Button
|
||||
type='text'
|
||||
size='small'
|
||||
icon={<LinkOutlined />}
|
||||
onClick={onShare}
|
||||
className='text-blue-500 hover:text-blue-600'
|
||||
>
|
||||
分享
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 }) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
@@ -2270,6 +2290,7 @@ const Playground: NextPage = () => {
|
||||
isRunning={isRunning}
|
||||
onCollapse={() => setRightPanelCollapsed(true)}
|
||||
onRerun={() => {}}
|
||||
onShare={!loading && !!conversationId ? handleShare : undefined}
|
||||
terminalTitle='DB-GPT 的电脑'
|
||||
artifacts={artifacts.filter(a => a.messageId === activeViewMsg?.id)}
|
||||
onArtifactClick={artifact => {
|
||||
|
||||
771
web/pages/share/[token].tsx
Normal file
771
web/pages/share/[token].tsx
Normal file
@@ -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<string, ManusExecutionOutput[]>;
|
||||
stepThoughts: Record<string, string>;
|
||||
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<string, ManusExecutionOutput[]>, finalContent: string): ArtifactItem[] {
|
||||
const artifacts: ArtifactItem[] = [];
|
||||
const now = Date.now();
|
||||
const seenCodeHashes = new Set<string>();
|
||||
|
||||
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<string, ManusExecutionOutput[]> = {};
|
||||
const stepThoughts: Record<string, string> = {};
|
||||
|
||||
(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<ReplayState>({
|
||||
roundIndex: 0,
|
||||
visibleStepCount: 0,
|
||||
activeStepId: null,
|
||||
showFinalForRound: false,
|
||||
completedRoundIndex: -1,
|
||||
done: false,
|
||||
});
|
||||
const [playing, setPlaying] = useState(false);
|
||||
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | 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<void>(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<SharePageProps> = ({ token, messages: initMessages, firstQuestion, error: initError }) => {
|
||||
const [fetchError] = useState<string | null>(initError || null);
|
||||
const [speed, setSpeed] = useState(1);
|
||||
const [rightPanelView, setRightPanelView] = useState<PanelView>('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 (
|
||||
<div className='flex items-center justify-center h-screen bg-white dark:bg-[#111217]'>
|
||||
<div className='text-center space-y-3'>
|
||||
<p className='text-2xl font-semibold text-gray-700 dark:text-gray-200'>分享链接无效或已过期</p>
|
||||
<p className='text-gray-400'>{fetchError || '没有可回放的对话内容'}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{firstQuestion ? `${firstQuestion.slice(0, 60)}${firstQuestion.length > 60 ? '…' : ''} · DB-GPT 回放` : 'DB-GPT 对话回放'}</title>
|
||||
</Head>
|
||||
|
||||
<div className='flex flex-col h-screen bg-white dark:bg-[#111217] overflow-hidden'>
|
||||
{/* ---------------------------------------------------------------- */}
|
||||
{/* Top bar */}
|
||||
{/* ---------------------------------------------------------------- */}
|
||||
<div className='flex items-center justify-between px-5 py-2.5 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-[#1a1b1e] flex-shrink-0'>
|
||||
<div className='flex items-center gap-3 min-w-0'>
|
||||
{/* Logo / brand */}
|
||||
<span className='font-bold text-base text-gray-800 dark:text-white flex-shrink-0'>DB-GPT</span>
|
||||
<div className='w-px h-4 bg-gray-200 dark:bg-gray-700 flex-shrink-0' />
|
||||
<span className='text-xs font-medium text-blue-500 bg-blue-50 dark:bg-blue-500/10 px-2 py-0.5 rounded-full flex-shrink-0'>回放</span>
|
||||
{firstQuestion && (
|
||||
<span className='text-sm text-gray-500 dark:text-gray-400 truncate max-w-[400px]' title={firstQuestion}>
|
||||
{firstQuestion}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Playback controls */}
|
||||
<div className='flex items-center gap-2'>
|
||||
{/* Speed selector */}
|
||||
<div className='flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5'>
|
||||
{SPEED_OPTIONS.map(opt => (
|
||||
<button
|
||||
key={opt.value}
|
||||
onClick={() => setSpeed(opt.value)}
|
||||
className={`px-2.5 py-1 rounded-md text-xs font-medium transition-colors ${
|
||||
speed === opt.value
|
||||
? 'bg-white dark:bg-gray-700 text-gray-800 dark:text-white shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className='w-px h-5 bg-gray-200 dark:bg-gray-700' />
|
||||
|
||||
{/* Play / Pause */}
|
||||
{state.done ? (
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={restart}
|
||||
>
|
||||
重新回放
|
||||
</Button>
|
||||
) : playing ? (
|
||||
<Button
|
||||
icon={<PauseCircleOutlined />}
|
||||
onClick={pause}
|
||||
>
|
||||
暂停
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<PlayCircleOutlined />}
|
||||
onClick={play}
|
||||
>
|
||||
{completedSteps === 0 ? '开始回放' : '继续'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Skip to end */}
|
||||
{!state.done && (
|
||||
<Tooltip title='跳到最后一步'>
|
||||
<Button
|
||||
icon={<StepForwardOutlined />}
|
||||
onClick={() => jumpToRound(rounds.length - 1)}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Divider */}
|
||||
<div className='w-px h-5 bg-gray-200 dark:bg-gray-700' />
|
||||
|
||||
{/* Copy share link — blue to match the UI theme */}
|
||||
<Tooltip title='复制分享链接,任何人可通过此链接回放对话'>
|
||||
<Button
|
||||
icon={<LinkOutlined />}
|
||||
onClick={handleCopyLink}
|
||||
style={{ color: '#3b82f6', borderColor: '#3b82f6' }}
|
||||
>
|
||||
分享
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ---------------------------------------------------------------- */}
|
||||
{/* Progress bar */}
|
||||
{/* ---------------------------------------------------------------- */}
|
||||
<div className='px-5 py-2 border-b border-gray-100 dark:border-gray-800/60 bg-gray-50/50 dark:bg-transparent flex-shrink-0'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='flex-1'>
|
||||
<div className='h-1 bg-gray-200 dark:bg-gray-800 rounded-full overflow-hidden'>
|
||||
<div
|
||||
className='h-full bg-blue-500 rounded-full transition-all duration-500 ease-out'
|
||||
style={{ width: `${progressPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span className='text-xs text-gray-400 whitespace-nowrap tabular-nums'>
|
||||
{completedSteps} / {totalSteps} 步
|
||||
</span>
|
||||
{/* Round selector tabs */}
|
||||
{rounds.length > 1 && (
|
||||
<div className='flex items-center gap-1'>
|
||||
{rounds.map((round, ri) => (
|
||||
<Tooltip key={ri} title={round.humanText ? round.humanText.slice(0, 40) : `第 ${ri + 1} 轮`}>
|
||||
<button
|
||||
onClick={() => jumpToRound(ri)}
|
||||
className={`w-5 h-5 rounded-full text-[10px] font-bold transition-colors ${
|
||||
ri === state.roundIndex
|
||||
? 'bg-blue-500 text-white'
|
||||
: ri <= state.completedRoundIndex
|
||||
? 'bg-emerald-100 dark:bg-emerald-900/40 text-emerald-600 dark:text-emerald-400'
|
||||
: 'bg-gray-200 dark:bg-gray-700 text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{ri + 1}
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ---------------------------------------------------------------- */}
|
||||
{/* Main content: left (steps) + right (output) */}
|
||||
{/* ---------------------------------------------------------------- */}
|
||||
<div className='flex-1 flex overflow-hidden'>
|
||||
{/* Left panel — all rounds stacked */}
|
||||
<div className='w-[42%] min-w-[360px] border-r border-gray-200 dark:border-gray-800 flex flex-col overflow-y-auto'>
|
||||
{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 (
|
||||
<ManusLeftPanel
|
||||
key={ri}
|
||||
sections={data.sections}
|
||||
activeStepId={isCurrentRound ? data.activeStepId : undefined}
|
||||
onStepClick={undefined /* read-only */}
|
||||
isWorking={isCurrentRound && playing && !state.showFinalForRound}
|
||||
userQuery={data.round.humanText}
|
||||
assistantText={data.showFinal ? data.round.finalContent : undefined}
|
||||
stepThoughts={data.round.stepThoughts}
|
||||
artifacts={data.showFinal ? data.round.artifacts : []}
|
||||
onViewAllFiles={data.showFinal ? () => setRightPanelView('files') : undefined}
|
||||
isCollapsed={isCollapsed}
|
||||
onExpand={() => jumpToRound(ri)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Pending placeholder for rounds not yet reached */}
|
||||
{rounds.length > 1 && state.roundIndex < rounds.length - 1 && (
|
||||
<div className='px-4 py-3 text-xs text-gray-400 text-center animate-pulse'>
|
||||
{rounds.length - state.roundIndex - 1} 轮待回放...
|
||||
</div>
|
||||
)}
|
||||
{/* Bottom breathing room */}
|
||||
<div className='flex-shrink-0 h-8' />
|
||||
</div>
|
||||
|
||||
{/* Right panel — outputs of the active step */}
|
||||
<div className='flex-1 overflow-hidden'>
|
||||
<ManusRightPanel
|
||||
activeStep={rightPanelActiveStep}
|
||||
outputs={rightPanelOutputs}
|
||||
isRunning={playing && !!activeStepId}
|
||||
artifacts={activeRoundData?.round.artifacts ?? []}
|
||||
panelView={rightPanelView}
|
||||
onPanelViewChange={setRightPanelView}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user