Compare commits
15 Commits
2e6312e84f
...
30031
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daabce8a5f | ||
|
|
5a51845e12 | ||
|
|
2c69bd7c91 | ||
|
|
a129211e5e | ||
|
|
e8279a2deb | ||
|
|
2408d980e5 | ||
|
|
5199dbb6e5 | ||
|
|
a71c43fa9a | ||
|
|
a7677c32fb | ||
|
|
8b8f4111e2 | ||
|
|
68c9acc85b | ||
|
|
1c07d4b9df | ||
|
|
fb39b66431 | ||
|
|
2734110636 | ||
|
|
8af9a4cb61 |
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
node_modules
|
||||
build
|
||||
@@ -8,12 +8,12 @@
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<script src="/node_modules/ace-builds/src-min-noconflict/ace.js" type="text/javascript"></script>
|
||||
<title>NPCs</title>
|
||||
<script type="module" crossorigin src="/assets/index-f344eac6.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-e0228455.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/acebuilds-fbc0ccc6.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/reactflow-10657c96.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/reactdrop-61b79f7b.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/reactflow-c250d835.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/reactdrop-be699031.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pdfjs-36654f0a.js">
|
||||
<link rel="stylesheet" href="/assets/index-e34caae6.css">
|
||||
<link rel="stylesheet" href="/assets/index-e06d93b0.css">
|
||||
</head>
|
||||
|
||||
<body id='body' style="width: 100%; height:100%">
|
||||
|
||||
@@ -57,7 +57,11 @@
|
||||
},
|
||||
"skills": {
|
||||
"manageTemplate": "Manage Skill Templates",
|
||||
"createNew": "Create New",
|
||||
"createNew": "Create New Skill",
|
||||
"customSkills": "Custom Skills",
|
||||
"chooseOnline": "Choose the online version",
|
||||
"executionSteps": "Skills are visualized through process orchestration to clarify task execution steps",
|
||||
"sceneTemplates": "We provide scene templates for your use and reference",
|
||||
"manageProjects": "Manage your personal projects here, including skill deployment and editing",
|
||||
"skillSearch": "Skill Search",
|
||||
"confirmDeleteSkill": "Confirm the deletion of this skill?",
|
||||
@@ -80,6 +84,7 @@
|
||||
"skillDescTooLong": "Skill description is too long, should not exceed 200 characters",
|
||||
"errorTitle": "Key Information Error",
|
||||
"onlineFailure": "Online failure",
|
||||
"onlineSuccessful": "Online Successful",
|
||||
"custom": "Custom",
|
||||
"skillTemplate": "Skill Template",
|
||||
"skillTemplateChoose": "You can choose a template from here to start, or create an advanced template",
|
||||
@@ -92,7 +97,23 @@
|
||||
"createdBy": "Created by",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"guideWords": "Guide Words"
|
||||
"guideWords": "Guide Words",
|
||||
"currentVersion": "Current Version: ",
|
||||
"importLocal": "Import local components",
|
||||
"save": "Saved",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"code": "Code",
|
||||
"simplify": "Simplify",
|
||||
"saveVersion": "SaveVersion",
|
||||
"deleteOrNot": "Do you want to delete",
|
||||
"version": "version",
|
||||
"saveSuccessful": "Save Successful",
|
||||
"supportVersions": "Support splitting into multiple version branches for development and comparison between versions.",
|
||||
"guideQuestions50": "Guide questions to a maximum of 50 characters",
|
||||
"promptWords1000": "The maximum number of prompt words is 1000 characters",
|
||||
"contactAdmin": "Please contact the administrator's online assistant",
|
||||
"deleteSure": "Are you sure you want to delete this tool?"
|
||||
},
|
||||
"chat": {
|
||||
"newChat": "New Chat",
|
||||
@@ -238,7 +259,7 @@
|
||||
"rootDirectory": "Root directory",
|
||||
"createOneKnowledge": "Create a knowledge base",
|
||||
"webSite": "Web site synchronization",
|
||||
"knowledge": "Knowledge base type",
|
||||
"knowledgeType": "Knowledge base type",
|
||||
"knowledgeDescription": "Knowledge base can be built by importing files, webpage links, or manually entering them",
|
||||
"webSiteDescription": "Web site synchronization allows you to directly use a webpage link to build a knowledge base",
|
||||
"baseSet": "Basic Settings",
|
||||
@@ -262,7 +283,27 @@
|
||||
"foldDesc": "This is a folder",
|
||||
"noFold": "We don't have a folder yet, create one quickly",
|
||||
"selectFold": "Please select a folder",
|
||||
"confirmDelete": "Confirm deletion"
|
||||
"confirmDelete": "Confirm deletion",
|
||||
"dataset": "Dataset",
|
||||
"config": "Configure",
|
||||
"queueLen": "Queue length",
|
||||
"allKnowledge": "All datasets",
|
||||
"file": "File",
|
||||
"dataTotal": "Total data volume",
|
||||
"lastUpdate": "Last update time",
|
||||
"state": "State",
|
||||
"syncData": "Synchronize data",
|
||||
"manualDataset": "Manual dataset",
|
||||
"textDataset": "Text dataset",
|
||||
"tableDataset": "Table Dataset",
|
||||
"createImport": "Create/Import",
|
||||
"knowledge": "Knowledge base",
|
||||
"web": "Website",
|
||||
"knowledgeId": "Knowledge Base ID",
|
||||
"knowledgeImg": "Knowledge Base Avatar",
|
||||
"indexModel": "Index model",
|
||||
"dataUp": "Single data upper limit",
|
||||
"introduce": "Introduce"
|
||||
},
|
||||
"code": {
|
||||
"editPythonCodeDescription": "Edit your Python code here. This code snippet accepts module imports and a function definition. Make sure your function returns a string.",
|
||||
@@ -402,6 +443,139 @@
|
||||
"previousPage": "Previous Page",
|
||||
"nextPage": "Next Page",
|
||||
"formatError": "Format Error",
|
||||
"port": "PORT",
|
||||
"cancle": "Cancle",
|
||||
"tip": "Prompt",
|
||||
"deleteAssistant": "Are you sure to delete this assistant?",
|
||||
"build": {
|
||||
"create": "Create",
|
||||
"assistant": "Assistant",
|
||||
"skill": "Skills",
|
||||
"tools": "Tools",
|
||||
"save": "Save",
|
||||
"online": "Online",
|
||||
"retry": "Retry",
|
||||
"use": "Use",
|
||||
"useAll": "Use All",
|
||||
"assistantConfiguration": "Assistant Configuration",
|
||||
"createAssistant": "Create a New Assistant",
|
||||
"assistantPortrait": "Assistant Portrait",
|
||||
"portraitOptimization": "Assistant portrait optimization",
|
||||
"automaticOptimization": "Automatic optimization",
|
||||
"createDescription": "Create your assistant by describing roles and tasks",
|
||||
"nextDescription": "Assistant can call multiple skills and tools",
|
||||
"searchAssistant": "Search for the assistant you need",
|
||||
"manageAssistant": "Manage your assistant on this page, including online and offline activities, editing, and more",
|
||||
"establishAssistant": "Create Assistant",
|
||||
"assistantName": "Assistant Name",
|
||||
"giveAssistantName": "Give the assistant a name",
|
||||
"whatWant": "What is the role of an assistant and what specific tasks do you want to complete?",
|
||||
"example": "Example",
|
||||
"exampleOne": "You are XX, with XX experience, skilled in XX, ...",
|
||||
"exampleTwo": "Your task is XX, and you need to follow the following steps to execute it:",
|
||||
"automaticallyConfigurations": "Automatically select relevant configurations for you",
|
||||
"prompt": "Provide a detailed and specific description of the interaction between the assistant and the user, such as the assistant's identity, specific methods and steps for completing tasks, tone of voice when answering questions, and what issues should be noted",
|
||||
"openingRemarks": "Opening remarks",
|
||||
"guidingQuestions": "Guiding Questions",
|
||||
"promptReplaced": "Prompt word replaced",
|
||||
"guideReplaced": "Guide word replaced",
|
||||
"openingReplaced": "Opening word replaced",
|
||||
"toolsReplaced": "Tools replaced",
|
||||
"skillsReplaced": "Skills replaced",
|
||||
"allReplaced": "All replaced",
|
||||
"basicConfiguration": "Basic configuration",
|
||||
"modelConfiguration": "AI Model configuration",
|
||||
"model": "Model",
|
||||
"temperature": "Temperature",
|
||||
"openingIntroduction": "Opening Introduction",
|
||||
"openingStatement": "Opening Statement",
|
||||
"assistantMessageFormat": "The assistant will send this message at the beginning of each conversation, supports Markdown format",
|
||||
"maximumPromptLength": "The prompt can be up to 1000 characters",
|
||||
"recommendQuestionsForUsers": "Provide recommended questions for users to guide them to ask. When there are more than 3, 3 will be randomly selected.",
|
||||
"maxCharacters50": "Up to 50 characters",
|
||||
"enterGuidingQuestions": "Please enter guiding questions",
|
||||
"knowledge": "Knowledge",
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
"autoCall": "Auto Call",
|
||||
"callingMethod": "Calling Method",
|
||||
"autoCallDescription": "Retrieve and recall the added knowledge base in every conversation.",
|
||||
"onDemandCall": "On-Demand Call",
|
||||
"onDemandCallDescription": "Prompt to call the RecallKnowledge (copyable) method in the assistant's profile (prompt words), and only retrieve the knowledge base when needed.",
|
||||
"createNewKnowledge": "Create New Knowledge Base",
|
||||
"refresh": "Refresh",
|
||||
"abilities": "Abilities",
|
||||
"skillDescription": "Implement complex and stable business process orchestration through a visual interface, such as project planning and report analysis.",
|
||||
"selectKnowledgeBase": "Please select a knowledge base",
|
||||
"searchBaseName": "Search for knowledge base name",
|
||||
"debugPreview": "Debug Preview",
|
||||
"addTool": "Add Tool",
|
||||
"search": "Search",
|
||||
"empty": "Nothing here",
|
||||
"onlineSA": "Go online skills&assistants",
|
||||
"params": "Parameters",
|
||||
"added": "Added",
|
||||
"add": "Add",
|
||||
"configurationUpdated": "Configuration updated",
|
||||
"addSkill": "Add Skill",
|
||||
"createSkill": "Create Skill",
|
||||
"nameRequired": "Name cannot be empty",
|
||||
"nameMaxLength": "Name can have at most 50 characters",
|
||||
"descMaxLength": "Description can have at most 1000 characters",
|
||||
"editAssistant": "Edit Assistant",
|
||||
"enterName": "Enter a name for the assistant",
|
||||
"assistantDesc": "Assistant Description",
|
||||
"enterDesc": "Introduce assistant functionality, visible in conversations and assistant pages",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"forBetter": "For better assistant effect, the description needs to be greater than 20 words",
|
||||
"forExample": "For example, the identity of an assistant, specific methods and steps for completing tasks, tone of voice when answering questions, and what issues to pay attention to, etc"
|
||||
},
|
||||
"tools1": {
|
||||
"addTool": "Add Tool",
|
||||
"createCustomTool": " Custom Tool",
|
||||
"builtinTools": "Built-in Tools",
|
||||
"customTools": "Custom Tools",
|
||||
"search": "Search",
|
||||
"empty": "Empty",
|
||||
"manageCustomTools": "Manage your custom tools on this page, including creating and editing them.",
|
||||
"name": "Name",
|
||||
"enterToolName": "Enter tool name",
|
||||
"openapiSchema": "OpenAPI Schema",
|
||||
"enterOpenAPISchema": "Enter your OpenAPI schema",
|
||||
"importFromUrl": "Import from URL",
|
||||
"examples": "Examples",
|
||||
"weatherJson": "Weather (JSON)",
|
||||
"petShopYaml": "Pet Shop (YAML)",
|
||||
"authenticationType": "Authentication Type",
|
||||
"authType": "Authentication Type",
|
||||
"none": "None",
|
||||
"apiKey": "API Key",
|
||||
"basic": "Basic",
|
||||
"bearer": "Bearer",
|
||||
"availableTools": "Available Tools",
|
||||
"description": "Description",
|
||||
"method": "Method",
|
||||
"path": "Path",
|
||||
"delete": "Delete",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
},
|
||||
"test": {
|
||||
"test": "Test",
|
||||
"addTest": "Please add test cases first",
|
||||
"uploadTest": "Upload test cases",
|
||||
"explain": "The test case is the input of the current component and only supports txt files, with a maximum of 20 lines",
|
||||
"testRun": "Test Run",
|
||||
"testCase": "Test Case",
|
||||
"run": "Run",
|
||||
"downloadResults": "Download Run Results",
|
||||
"testCases": "Enter test cases...",
|
||||
"parametersAndValues": "Parameters and Values",
|
||||
"parameter": "Parameter",
|
||||
"value": "Value",
|
||||
"result": "Test Result",
|
||||
"outResultPlaceholder": "Click the button to output the result"
|
||||
},
|
||||
"agents": {
|
||||
"AgentInitializer":{
|
||||
"display_name": "AgentInitializer",
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"pleaseEnterAccount": "请填写账号",
|
||||
"pleaseEnterPassword": "请填写密码",
|
||||
"accountTooShort": "账号过短",
|
||||
"passwordTooShort": "请填写密码,至少8位",
|
||||
"passwordError": "密码必须包含大小写字母、数字和字符!",
|
||||
"passwordTooShort": "请填写密码,至少6位",
|
||||
"passwordError": "密码必须包含字母、数字!",
|
||||
"passwordMismatch": "两次密码不一致",
|
||||
"registrationSuccess": "注册成功,请输入密码进行登录"
|
||||
},
|
||||
@@ -42,9 +42,9 @@
|
||||
"roleList": "角色列表",
|
||||
"confirmText": "是否删除",
|
||||
"roleName": "角色名称",
|
||||
"skillAuthorization": "NPC授权",
|
||||
"skillAuthorization": "能力授权",
|
||||
"knowledgeAuthorization": "知识库授权",
|
||||
"skillName": "NPC名称",
|
||||
"skillName": "能力名称",
|
||||
"creator": "创建人",
|
||||
"usePermission": "使用权限",
|
||||
"managePermission": "管理权限",
|
||||
@@ -55,40 +55,61 @@
|
||||
"language": "语言"
|
||||
},
|
||||
"skills": {
|
||||
"manageTemplate": "管理NPC模板",
|
||||
"createNew": "新建",
|
||||
"manageProjects": "这里管理您的个人项目,对NPC上下线、编辑等等",
|
||||
"skillSearch": "NPC搜索",
|
||||
"confirmDeleteSkill": "确认删除该NPC?",
|
||||
"backToSkillList": "返回NPC列表",
|
||||
"skillTemplateManagement": "NPC模板管理,模板对所有用户可见,支持拖拽排序、删除操作",
|
||||
"manageTemplate": "管理能力模板",
|
||||
"createNew": "新建能力",
|
||||
"customSkills": "自定义能力",
|
||||
"chooseOnline": "选择上线版本",
|
||||
"executionSteps": "能力通过可视化的流程编排,明确任务执行步骤",
|
||||
"sceneTemplates": "我们提供场景模板供您使用和参考",
|
||||
"manageProjects": "在此页面管理您的能力,对能力上下线、编辑等等",
|
||||
"skillSearch": "搜索您需要的能力",
|
||||
"confirmDeleteSkill": "确认删除该能力?",
|
||||
"backToSkillList": "返回能力列表",
|
||||
"skillTemplateManagement": "能力模板管理,模板对所有用户可见,支持拖拽排序、删除操作",
|
||||
"templateName": "模板名称",
|
||||
"templateDescription": "模板描述",
|
||||
"confirmText": "是否确认删除该NPC模板?",
|
||||
"skillSettings": "NPC设置",
|
||||
"confirmText": "是否确认删除该能力模板?",
|
||||
"skillSettings": "能力设置",
|
||||
"basicInfo": "基础信息",
|
||||
"skillName": "NPC名称",
|
||||
"skillName": "能力名称",
|
||||
"description": "描述",
|
||||
"parameterInfo": "参数信息",
|
||||
"advancedConfiguration": "高级配置",
|
||||
"nextStep": "下一步,高级配置",
|
||||
"skillNameRequired": "请填写NPC名称",
|
||||
"skillNameTooLong": "NPC名称过长,不要超过30字",
|
||||
"skillNameRequired": "请填写能力名称",
|
||||
"skillNameTooLong": "能力名称过长,不要超过30字",
|
||||
"skillNameExists": "该名称已存在",
|
||||
"skillDescRequired": "请填写NPC描述",
|
||||
"skillDescTooLong": "NPC描述过长,不要超过200字",
|
||||
"skillDescRequired": "请填写能力描述",
|
||||
"skillDescTooLong": "能力描述过长,不要超过200字",
|
||||
"errorTitle": "关键信息有误",
|
||||
"onlineFailure": "上线失败",
|
||||
"onlineSuccessful": "上线成功",
|
||||
"custom": "自定义",
|
||||
"skillTemplate": "NPC模板",
|
||||
"skillTemplate": "能力模板",
|
||||
"skillTemplateChoose": "您可以从这里挑选一个模板开始,或者自定义高级模板",
|
||||
"createTemplate": "创建模板",
|
||||
"createSuccessTitle": "NPC创建成功",
|
||||
"createSuccessTitle": "能力创建成功",
|
||||
"createFailureTitle": "创建失败",
|
||||
"createdBy": "创建用户",
|
||||
"offline": "下线",
|
||||
"online": "上线",
|
||||
"guideWords": "引导词"
|
||||
"guideWords": "引导词",
|
||||
"currentVersion": "当前版本:",
|
||||
"importLocal": "导入本地组件",
|
||||
"save": "保存",
|
||||
"import": "导入",
|
||||
"export": "导出",
|
||||
"code": "代码",
|
||||
"simplify": "简化",
|
||||
"saveVersion": "另存为新版本",
|
||||
"deleteOrNot": "是否删除",
|
||||
"version": "版本",
|
||||
"saveSuccessful": "保存成功",
|
||||
"supportVersions": "支持分成多个版本分支,分别进行开发以及版本间的比较。",
|
||||
"guideQuestions50": "引导问题最多50个字符",
|
||||
"promptWords1000": "提示词最多为1000个字符",
|
||||
"contactAdmin": "请联系管理员上线NPC",
|
||||
"deleteSure": "确认删除该工具?"
|
||||
},
|
||||
"chat": {
|
||||
"newChat": "新建会话",
|
||||
@@ -96,8 +117,8 @@
|
||||
"inputPlaceholder": "请输入问题",
|
||||
"uploadFileTooltip": "上传文件",
|
||||
"sendTooltip": "发送",
|
||||
"skillTempsTitle": "NPC选择",
|
||||
"skillTempsDesc": "选择一个您想使用的线上NPC",
|
||||
"skillTempsTitle": "能力选择",
|
||||
"skillTempsDesc": "选择一个您想使用的线上能力",
|
||||
"forms": "表单",
|
||||
"networkError": "网络连接出现错误,请尝试以下方法",
|
||||
"networkErrorList1": "操作不要过快",
|
||||
@@ -137,7 +158,7 @@
|
||||
"warningTooltip": "处理异常",
|
||||
"inProgressOnlineStatus": "上线中",
|
||||
"inProgressOfflineStatus": "下线中",
|
||||
"confirmModelOffline": "是否确认下线该模型,下线后使用该模型服务的NPC将无法正常工作",
|
||||
"confirmModelOffline": "是否确认下线该模型,下线后使用该模型服务的能力将无法正常工作",
|
||||
"confirmOfflineButtonText": "下线",
|
||||
"modelManagement": "模型管理",
|
||||
"modelFineTune": "模型Finetune",
|
||||
@@ -158,6 +179,7 @@
|
||||
"machineName": "服务名",
|
||||
"addOne": "加一条"
|
||||
},
|
||||
|
||||
"flow": {
|
||||
"unsavedChangesConfirmation": "您有未保存的更改,确定要离开吗?",
|
||||
"leave": "离开",
|
||||
@@ -172,7 +194,7 @@
|
||||
"searchComponent": "查找组件",
|
||||
"knowledgeBaseSelection": "知识库选择",
|
||||
"searchKnowledgeBase": "搜索知识库",
|
||||
"minimumParamSetDescription": "您可以在此设置NPC所需的最小参数集",
|
||||
"minimumParamSetDescription": "您可以在此设置能力所需的最小参数集",
|
||||
"paramList": "参数列表",
|
||||
"saveConfig": "保存配置",
|
||||
"componentLabel": "组件",
|
||||
@@ -181,9 +203,9 @@
|
||||
"parameterLabel": "参数",
|
||||
"notification": "消息",
|
||||
"noNewNotifications": "没有新的通知",
|
||||
"skillName": "NPC名",
|
||||
"skillName": "能力名",
|
||||
"nameTooLong": "名称过长",
|
||||
"skillDescription": "NPC描述",
|
||||
"skillDescription": "能力描述",
|
||||
"enterVarName": "请输入变量名",
|
||||
"varNameExists": "变量名重复",
|
||||
"text": "文本",
|
||||
@@ -230,7 +252,7 @@
|
||||
"rootDirectory": "根目录",
|
||||
"createOneKnowledge": "创建一个知识库",
|
||||
"webSite": "Web站点同步",
|
||||
"knowledge": "知识库类型",
|
||||
"knowledgeType": "知识库类型",
|
||||
"knowledgeDescription": "可通过导入文件、网页链接或手动录入形式构建知识库",
|
||||
"webSiteDescription": "Web站点同步允许你直接使用一个网页链接构建知识库",
|
||||
"baseSet": "基本设置",
|
||||
@@ -254,7 +276,27 @@
|
||||
"foldDesc": "这是一个文件夹",
|
||||
"noFold": "还没有文件夹,快快创建一个吧~",
|
||||
"selectFold": "请选择文件夹",
|
||||
"confirmDelete": "确定删除"
|
||||
"confirmDelete": "确定删除",
|
||||
"dataset": "数据集",
|
||||
"config": "配置",
|
||||
"queueLen": "排队长度",
|
||||
"allKnowledge": "全部数据集",
|
||||
"file": "文件",
|
||||
"dataTotal": "数据总量",
|
||||
"lastUpdate": "最后更新时间",
|
||||
"state": "状态",
|
||||
"syncData": "同步数据",
|
||||
"manualDataset": "手动数据集",
|
||||
"textDataset": "文本数据集",
|
||||
"tableDataset": "表格数据集",
|
||||
"createImport": "新建/导入",
|
||||
"knowledge": "知识库",
|
||||
"web": "网站",
|
||||
"knowledgeId": "知识库ID",
|
||||
"knowledgeImg": "知识库头像",
|
||||
"indexModel": "索引模型",
|
||||
"dataUp": "单条数据上限",
|
||||
"introduce": "介绍"
|
||||
},
|
||||
"code": {
|
||||
"editPythonCodeDescription": "编辑你的 Python 代码此代码片段接受模块导入和一个函数定义。确保您的函数返回一个字符串。",
|
||||
@@ -266,11 +308,11 @@
|
||||
"codeError": "这段代码有问题,请检查以下",
|
||||
"checkAndSave": "检查 & 保存",
|
||||
"export": "导出",
|
||||
"exportToJSON": "导出NPC到json文件中",
|
||||
"exportToJSON": "导出能力到json文件中",
|
||||
"keyInformationMissing": "您有一些关键信息没有填: ",
|
||||
"skillNameMissing": "请填写NPC名称",
|
||||
"skillNameMissing": "请填写能力名称",
|
||||
"useOwnAPIKeys": "使用自己的API keys",
|
||||
"exportSkill": "导出NPC",
|
||||
"exportSkill": "导出能力",
|
||||
"uploadFile": "上传文件",
|
||||
"clickOrDragHere": "点击或将文件拖拽到这里上传",
|
||||
"dropFileHere": "将文件拖拽到这里上传",
|
||||
@@ -287,7 +329,7 @@
|
||||
"file": "文件",
|
||||
"sizeExceedsLimit": "超过50M",
|
||||
"editDictionary": "编辑词典",
|
||||
"exportCodeDialogTip": "生成代码,将流程集成到外部应用程序中 (打开此页面前请先buildNPC)。",
|
||||
"exportCodeDialogTip": "生成代码,将流程集成到外部应用程序中 (打开此页面前请先build能力)。",
|
||||
"chunkOverlap": "切分文本重叠长度"
|
||||
},
|
||||
"report": {
|
||||
@@ -304,8 +346,8 @@
|
||||
"varLength": "长度不能超过"
|
||||
},
|
||||
"status": {
|
||||
"1004": "该NPC已被删除",
|
||||
"1008": "当前NPC未上线,无法直接对话",
|
||||
"1004": "该能力已被删除",
|
||||
"1008": "当前能力未上线,无法直接对话",
|
||||
"1005": ""
|
||||
},
|
||||
"all": "全部",
|
||||
@@ -394,6 +436,139 @@
|
||||
"previousPage": "上一页",
|
||||
"nextPage": "下一页",
|
||||
"formatError": "格式错误",
|
||||
"port": "服务端口",
|
||||
"cancle": "取消",
|
||||
"tip": "提示",
|
||||
"deleteAssistant": "确认删除该NPC?",
|
||||
"build": {
|
||||
"create": "创建",
|
||||
"assistant": "NPC",
|
||||
"skill": "能力",
|
||||
"tools": "工具",
|
||||
"save": "保存",
|
||||
"online": "上线",
|
||||
"retry": "重试",
|
||||
"use": "使用",
|
||||
"useAll": "全部使用",
|
||||
"assistantConfiguration": "NPC配置",
|
||||
"createAssistant": "新建NPC",
|
||||
"assistantPortrait": "NPC画像",
|
||||
"portraitOptimization": "NPC画像优化",
|
||||
"automaticOptimization": "自动优化",
|
||||
"createDescription": "通过描述角色和任务来创建你的NPC",
|
||||
"nextDescription": "NPC可以调用多个能力和工具",
|
||||
"searchAssistant": "搜索您需要的NPC",
|
||||
"manageAssistant": "在此页面管理您的NPC,对NPC上下线、编辑等等",
|
||||
"establishAssistant": "创建NPC",
|
||||
"assistantName": "NPC名称",
|
||||
"giveAssistantName": "给NPC取一个名字",
|
||||
"whatWant": "你希望NPC的角色是什么,具体完成什么任务?",
|
||||
"example": "示例",
|
||||
"exampleOne": "你是 XX,具有 XX 经验,擅长 XX,…",
|
||||
"exampleTwo": "你的任务是 XX ,需要按照以下步骤执行:",
|
||||
"automaticallyConfigurations": "自动为您选择相关配置",
|
||||
"prompt": "详细、具体地描述NPC与用户的交互方式,例如NPC的身份、完成任务的具体方法和步骤、回答问题时的语气以及应该注意什么问题等",
|
||||
"openingRemarks": "开场白",
|
||||
"guidingQuestions": "引导问题",
|
||||
"promptReplaced": "提示词已替换",
|
||||
"guideReplaced": "引导词已替换",
|
||||
"openingReplaced": "开场白已替换",
|
||||
"toolsReplaced": "工具已替换",
|
||||
"skillsReplaced": "能力已替换",
|
||||
"allReplaced": "已全部替换",
|
||||
"basicConfiguration": "基础配置",
|
||||
"modelConfiguration": "AI模型配置",
|
||||
"model": "模型",
|
||||
"temperature": "温度",
|
||||
"openingIntroduction": "开场引导",
|
||||
"openingStatement": "开场白",
|
||||
"assistantMessageFormat": "NPC将在每次对话开始时发送此信息,支持 Markdown 格式",
|
||||
"maximumPromptLength": "提示词最多为1000个字符",
|
||||
"recommendQuestionsForUsers": "为用户提供推荐问题,引导用户提问,超过3个时将随机选取3个",
|
||||
"maxCharacters50": "最多50个字符",
|
||||
"enterGuidingQuestions": "请输入引导问题",
|
||||
"knowledge": "知识",
|
||||
"knowledgeBase": "知识库",
|
||||
"autoCall": "自动调用",
|
||||
"callingMethod": "调用方式",
|
||||
"autoCallDescription": "每轮对话都会对添加的知识库进行检索召回。",
|
||||
"onDemandCall": "按需调用",
|
||||
"onDemandCallDescription": "在NPC画像(提示词)中提示调用 RecallKnowledge(可复制)方法,在有需要时才对知识库进行检索。",
|
||||
"createNewKnowledge": "新建知识库",
|
||||
"refresh": "刷新",
|
||||
"abilities": "能力",
|
||||
"skillDescription": "通过可视化界面实现复杂和稳定的业务流程编排,例如项目计划和报告分析",
|
||||
"selectKnowledgeBase": "请选择知识库",
|
||||
"searchBaseName": "搜索知识库名称",
|
||||
"debugPreview": "调试预览",
|
||||
"addTool": "添加工具",
|
||||
"search": "搜索",
|
||||
"empty": "空空如也",
|
||||
"onlineSA": "上线能力&NPC",
|
||||
"params": "参数",
|
||||
"added": "已添加",
|
||||
"add": "添加",
|
||||
"configurationUpdated": "配置已更新",
|
||||
"addSkill": "添加能力",
|
||||
"createSkill": "创建能力",
|
||||
"nameRequired": "名称不可为空",
|
||||
"nameMaxLength": "名称最多50个字符",
|
||||
"descMaxLength": "最多1000个字符",
|
||||
"editAssistant": "编辑NPC",
|
||||
"enterName": "给NPC取一个名字",
|
||||
"assistantDesc": "NPC描述",
|
||||
"enterDesc": "介绍NPC功能,描述在会话和NPC页面可见",
|
||||
"cancel": "取消",
|
||||
"confirm": "确认",
|
||||
"forBetter": "为了更好的NPC效果,描述需要大于20 个字",
|
||||
"forExample": "例如NPC的身份、完成任务的具体方法和步骤、回答问题时的语气以及应该注意什么问题等"
|
||||
},
|
||||
"tools1": {
|
||||
"addTool": "添加工具",
|
||||
"createCustomTool": "自定义工具",
|
||||
"builtinTools": "内置工具",
|
||||
"customTools": "自定义工具",
|
||||
"search": "搜索",
|
||||
"empty": "空空如也",
|
||||
"manageCustomTools": "在此页面管理您的自定义工具,对自定义工具创建、编辑等等",
|
||||
"name": "名称",
|
||||
"enterToolName": "输入工具名称",
|
||||
"openapiSchema": "OpenAPI Schema",
|
||||
"enterOpenAPISchema": "输入您的 OpenAPI schema",
|
||||
"importFromUrl": "从 URL 导入",
|
||||
"examples": "示例",
|
||||
"weatherJson": "天气(JSON)",
|
||||
"petShopYaml": "宠物商店(YAML)",
|
||||
"authenticationType": "鉴权方式",
|
||||
"authType": "认证类型",
|
||||
"none": "无",
|
||||
"apiKey": "API Key",
|
||||
"basic": "Basic",
|
||||
"bearer": "Bearer",
|
||||
"availableTools": "可用工具",
|
||||
"description": "描述",
|
||||
"method": "方法",
|
||||
"path": "路径",
|
||||
"delete": "删除",
|
||||
"cancel": "取消",
|
||||
"save": "保存"
|
||||
},
|
||||
"test": {
|
||||
"test": "测试",
|
||||
"addTest": "请先填写测试用例",
|
||||
"uploadTest": "上传测试用例",
|
||||
"explain": "为测试用例是当前组件的输入,只支持 txt 文件,最多 20 行",
|
||||
"testRun": "测试运行",
|
||||
"testCase": "测试用例",
|
||||
"run": "运行",
|
||||
"downloadResults": "下载运行结果",
|
||||
"testCases": "输入测试用例...",
|
||||
"parametersAndValues": "参数和值",
|
||||
"parameter": "参数",
|
||||
"value": "值",
|
||||
"result": "测试结果",
|
||||
"outResultPlaceholder": "点击按钮,输出结果"
|
||||
},
|
||||
"agents": {
|
||||
"AgentInitializer": {
|
||||
"display_name": "AgentInitializer",
|
||||
@@ -613,8 +788,8 @@
|
||||
},
|
||||
"autogen_roles": {
|
||||
"AutoGenAssistant": {
|
||||
"display_name": "自动生成助手",
|
||||
"description": "助手代理,设计用于使用大语言模型LLM解决任务。",
|
||||
"display_name": "自动生成NPC",
|
||||
"description": "NPC代理,设计用于使用大语言模型LLM解决任务。",
|
||||
"description_url": "https://lxm3ki8hexz.feishu.cn/wiki/Yz5GwMpO7ioj8nkRSZlcsriXnJ3",
|
||||
"template": {
|
||||
"model_name": {
|
||||
@@ -640,7 +815,7 @@
|
||||
}
|
||||
},
|
||||
"output_types":[
|
||||
"自动生成助手"
|
||||
"自动生成NPC"
|
||||
]
|
||||
},
|
||||
"AutoGenCoder": {
|
||||
@@ -2237,7 +2412,7 @@
|
||||
},
|
||||
"NPCsChatLLM": {
|
||||
"display_name": "NPCsChatLLM",
|
||||
"description": "`OpenAI`聊天大型语言模型API。",
|
||||
"description": "无缝切换支持的大语言模型",
|
||||
"description_url": "",
|
||||
"template": {
|
||||
"api_base": {
|
||||
|
||||
195
package-lock.json
generated
@@ -28,9 +28,11 @@
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
@@ -49,6 +51,7 @@
|
||||
"cmdk": "^0.2.0",
|
||||
"daisyui": "^3.6.3",
|
||||
"dompurify": "^3.0.5",
|
||||
"echarts": "^5.5.1",
|
||||
"esbuild": "^0.17.19",
|
||||
"i18next": "^23.6.0",
|
||||
"i18next-http-backend": "^2.2.2",
|
||||
@@ -85,7 +88,9 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^9.0.0",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
"web-vitals": "^2.1.4",
|
||||
"xlsx": "^0.18.5",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.1.62",
|
||||
@@ -2610,6 +2615,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slider": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-slider/-/react-slider-1.1.2.tgz",
|
||||
"integrity": "sha512-NKs15MJylfzVsCagVSWKhGGLNR1W9qWs+HtgbmjjVUB3B9+lb3PYoXxVju3kOrpf0VKyVCtZp+iTwVoqpa1Chw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/number": "1.0.1",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1",
|
||||
"@radix-ui/react-use-previous": "1.0.1",
|
||||
"@radix-ui/react-use-size": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
@@ -2687,6 +2725,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toast": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.1.5.tgz",
|
||||
"integrity": "sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||
"@radix-ui/react-portal": "1.0.4",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1",
|
||||
"@radix-ui/react-visually-hidden": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz",
|
||||
@@ -4555,6 +4627,14 @@
|
||||
"resolved": "https://registry.npmjs.org/add/-/add-2.0.6.tgz",
|
||||
"integrity": "sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q=="
|
||||
},
|
||||
"node_modules/adler-32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
|
||||
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
@@ -5415,6 +5495,18 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/cfb": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
|
||||
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"crc-32": "~1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -5846,6 +5938,14 @@
|
||||
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
|
||||
"integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w=="
|
||||
},
|
||||
"node_modules/codepage": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
|
||||
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -5978,6 +6078,17 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||
@@ -6668,6 +6779,20 @@
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
|
||||
"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.750",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.750.tgz",
|
||||
@@ -7286,6 +7411,14 @@
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/frac": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
|
||||
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
@@ -15254,6 +15387,17 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssf": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
|
||||
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||
"dependencies": {
|
||||
"frac": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/stdin-discarder": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz",
|
||||
@@ -16924,6 +17068,22 @@
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"node_modules/wmf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
|
||||
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
|
||||
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
@@ -16984,6 +17144,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
|
||||
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"cfb": "~1.2.1",
|
||||
"codepage": "~1.15.0",
|
||||
"crc-32": "~1.2.1",
|
||||
"ssf": "~0.11.2",
|
||||
"wmf": "~1.0.1",
|
||||
"word": "~0.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"xlsx": "bin/xlsx.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
@@ -17075,6 +17255,19 @@
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
|
||||
"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz",
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
@@ -44,6 +46,7 @@
|
||||
"cmdk": "^0.2.0",
|
||||
"daisyui": "^3.6.3",
|
||||
"dompurify": "^3.0.5",
|
||||
"echarts": "^5.5.1",
|
||||
"esbuild": "^0.17.19",
|
||||
"i18next": "^23.6.0",
|
||||
"i18next-http-backend": "^2.2.2",
|
||||
@@ -80,7 +83,9 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^9.0.0",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
"web-vitals": "^2.1.4",
|
||||
"xlsx": "^0.18.5",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev:docker": "vite --host 0.0.0.0",
|
||||
|
||||
@@ -57,7 +57,11 @@
|
||||
},
|
||||
"skills": {
|
||||
"manageTemplate": "Manage Skill Templates",
|
||||
"createNew": "Create New",
|
||||
"createNew": "Create New Skill",
|
||||
"customSkills": "Custom Skills",
|
||||
"chooseOnline": "Choose the online version",
|
||||
"executionSteps": "Skills are visualized through process orchestration to clarify task execution steps",
|
||||
"sceneTemplates": "We provide scene templates for your use and reference",
|
||||
"manageProjects": "Manage your personal projects here, including skill deployment and editing",
|
||||
"skillSearch": "Skill Search",
|
||||
"confirmDeleteSkill": "Confirm the deletion of this skill?",
|
||||
@@ -80,6 +84,7 @@
|
||||
"skillDescTooLong": "Skill description is too long, should not exceed 200 characters",
|
||||
"errorTitle": "Key Information Error",
|
||||
"onlineFailure": "Online failure",
|
||||
"onlineSuccessful": "Online Successful",
|
||||
"custom": "Custom",
|
||||
"skillTemplate": "Skill Template",
|
||||
"skillTemplateChoose": "You can choose a template from here to start, or create an advanced template",
|
||||
@@ -92,7 +97,23 @@
|
||||
"createdBy": "Created by",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"guideWords": "Guide Words"
|
||||
"guideWords": "Guide Words",
|
||||
"currentVersion": "Current Version: ",
|
||||
"importLocal": "Import local components",
|
||||
"save": "Saved",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"code": "Code",
|
||||
"simplify": "Simplify",
|
||||
"saveVersion": "SaveVersion",
|
||||
"deleteOrNot": "Do you want to delete",
|
||||
"version": "version",
|
||||
"saveSuccessful": "Save Successful",
|
||||
"supportVersions": "Support splitting into multiple version branches for development and comparison between versions.",
|
||||
"guideQuestions50": "Guide questions to a maximum of 50 characters",
|
||||
"promptWords1000": "The maximum number of prompt words is 1000 characters",
|
||||
"contactAdmin": "Please contact the administrator's online assistant",
|
||||
"deleteSure": "Are you sure you want to delete this tool?"
|
||||
},
|
||||
"chat": {
|
||||
"newChat": "New Chat",
|
||||
@@ -276,7 +297,13 @@
|
||||
"textDataset": "Text dataset",
|
||||
"tableDataset": "Table Dataset",
|
||||
"createImport": "Create/Import",
|
||||
"knowledge": "Knowledge base"
|
||||
"knowledge": "Knowledge base",
|
||||
"web": "Website",
|
||||
"knowledgeId": "Knowledge Base ID",
|
||||
"knowledgeImg": "Knowledge Base Avatar",
|
||||
"indexModel": "Index model",
|
||||
"dataUp": "Single data upper limit",
|
||||
"introduce": "Introduce"
|
||||
},
|
||||
"code": {
|
||||
"editPythonCodeDescription": "Edit your Python code here. This code snippet accepts module imports and a function definition. Make sure your function returns a string.",
|
||||
@@ -416,6 +443,139 @@
|
||||
"previousPage": "Previous Page",
|
||||
"nextPage": "Next Page",
|
||||
"formatError": "Format Error",
|
||||
"port": "PORT",
|
||||
"cancle": "Cancle",
|
||||
"tip": "Prompt",
|
||||
"deleteAssistant": "Are you sure to delete this assistant?",
|
||||
"build": {
|
||||
"create": "Create",
|
||||
"assistant": "Assistant",
|
||||
"skill": "Skills",
|
||||
"tools": "Tools",
|
||||
"save": "Save",
|
||||
"online": "Online",
|
||||
"retry": "Retry",
|
||||
"use": "Use",
|
||||
"useAll": "Use All",
|
||||
"assistantConfiguration": "Assistant Configuration",
|
||||
"createAssistant": "Create a New Assistant",
|
||||
"assistantPortrait": "Assistant Portrait",
|
||||
"portraitOptimization": "Assistant portrait optimization",
|
||||
"automaticOptimization": "Automatic optimization",
|
||||
"createDescription": "Create your assistant by describing roles and tasks",
|
||||
"nextDescription": "Assistant can call multiple skills and tools",
|
||||
"searchAssistant": "Search for the assistant you need",
|
||||
"manageAssistant": "Manage your assistant on this page, including online and offline activities, editing, and more",
|
||||
"establishAssistant": "Create Assistant",
|
||||
"assistantName": "Assistant Name",
|
||||
"giveAssistantName": "Give the assistant a name",
|
||||
"whatWant": "What is the role of an assistant and what specific tasks do you want to complete?",
|
||||
"example": "Example",
|
||||
"exampleOne": "You are XX, with XX experience, skilled in XX, ...",
|
||||
"exampleTwo": "Your task is XX, and you need to follow the following steps to execute it:",
|
||||
"automaticallyConfigurations": "Automatically select relevant configurations for you",
|
||||
"prompt": "Provide a detailed and specific description of the interaction between the assistant and the user, such as the assistant's identity, specific methods and steps for completing tasks, tone of voice when answering questions, and what issues should be noted",
|
||||
"openingRemarks": "Opening remarks",
|
||||
"guidingQuestions": "Guiding Questions",
|
||||
"promptReplaced": "Prompt word replaced",
|
||||
"guideReplaced": "Guide word replaced",
|
||||
"openingReplaced": "Opening word replaced",
|
||||
"toolsReplaced": "Tools replaced",
|
||||
"skillsReplaced": "Skills replaced",
|
||||
"allReplaced": "All replaced",
|
||||
"basicConfiguration": "Basic configuration",
|
||||
"modelConfiguration": "AI Model configuration",
|
||||
"model": "Model",
|
||||
"temperature": "Temperature",
|
||||
"openingIntroduction": "Opening Introduction",
|
||||
"openingStatement": "Opening Statement",
|
||||
"assistantMessageFormat": "The assistant will send this message at the beginning of each conversation, supports Markdown format",
|
||||
"maximumPromptLength": "The prompt can be up to 1000 characters",
|
||||
"recommendQuestionsForUsers": "Provide recommended questions for users to guide them to ask. When there are more than 3, 3 will be randomly selected.",
|
||||
"maxCharacters50": "Up to 50 characters",
|
||||
"enterGuidingQuestions": "Please enter guiding questions",
|
||||
"knowledge": "Knowledge",
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
"autoCall": "Auto Call",
|
||||
"callingMethod": "Calling Method",
|
||||
"autoCallDescription": "Retrieve and recall the added knowledge base in every conversation.",
|
||||
"onDemandCall": "On-Demand Call",
|
||||
"onDemandCallDescription": "Prompt to call the RecallKnowledge (copyable) method in the assistant's profile (prompt words), and only retrieve the knowledge base when needed.",
|
||||
"createNewKnowledge": "Create New Knowledge Base",
|
||||
"refresh": "Refresh",
|
||||
"abilities": "Abilities",
|
||||
"skillDescription": "Implement complex and stable business process orchestration through a visual interface, such as project planning and report analysis.",
|
||||
"selectKnowledgeBase": "Please select a knowledge base",
|
||||
"searchBaseName": "Search for knowledge base name",
|
||||
"debugPreview": "Debug Preview",
|
||||
"addTool": "Add Tool",
|
||||
"search": "Search",
|
||||
"empty": "Nothing here",
|
||||
"onlineSA": "Go online skills&assistants",
|
||||
"params": "Parameters",
|
||||
"added": "Added",
|
||||
"add": "Add",
|
||||
"configurationUpdated": "Configuration updated",
|
||||
"addSkill": "Add Skill",
|
||||
"createSkill": "Create Skill",
|
||||
"nameRequired": "Name cannot be empty",
|
||||
"nameMaxLength": "Name can have at most 50 characters",
|
||||
"descMaxLength": "Description can have at most 1000 characters",
|
||||
"editAssistant": "Edit Assistant",
|
||||
"enterName": "Enter a name for the assistant",
|
||||
"assistantDesc": "Assistant Description",
|
||||
"enterDesc": "Introduce assistant functionality, visible in conversations and assistant pages",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"forBetter": "For better assistant effect, the description needs to be greater than 20 words",
|
||||
"forExample": "For example, the identity of an assistant, specific methods and steps for completing tasks, tone of voice when answering questions, and what issues to pay attention to, etc"
|
||||
},
|
||||
"tools1": {
|
||||
"addTool": "Add Tool",
|
||||
"createCustomTool": " Custom Tool",
|
||||
"builtinTools": "Built-in Tools",
|
||||
"customTools": "Custom Tools",
|
||||
"search": "Search",
|
||||
"empty": "Empty",
|
||||
"manageCustomTools": "Manage your custom tools on this page, including creating and editing them.",
|
||||
"name": "Name",
|
||||
"enterToolName": "Enter tool name",
|
||||
"openapiSchema": "OpenAPI Schema",
|
||||
"enterOpenAPISchema": "Enter your OpenAPI schema",
|
||||
"importFromUrl": "Import from URL",
|
||||
"examples": "Examples",
|
||||
"weatherJson": "Weather (JSON)",
|
||||
"petShopYaml": "Pet Shop (YAML)",
|
||||
"authenticationType": "Authentication Type",
|
||||
"authType": "Authentication Type",
|
||||
"none": "None",
|
||||
"apiKey": "API Key",
|
||||
"basic": "Basic",
|
||||
"bearer": "Bearer",
|
||||
"availableTools": "Available Tools",
|
||||
"description": "Description",
|
||||
"method": "Method",
|
||||
"path": "Path",
|
||||
"delete": "Delete",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save"
|
||||
},
|
||||
"test": {
|
||||
"test": "Test",
|
||||
"addTest": "Please add test cases first",
|
||||
"uploadTest": "Upload test cases",
|
||||
"explain": "The test case is the input of the current component and only supports txt files, with a maximum of 20 lines",
|
||||
"testRun": "Test Run",
|
||||
"testCase": "Test Case",
|
||||
"run": "Run",
|
||||
"downloadResults": "Download Run Results",
|
||||
"testCases": "Enter test cases...",
|
||||
"parametersAndValues": "Parameters and Values",
|
||||
"parameter": "Parameter",
|
||||
"value": "Value",
|
||||
"result": "Test Result",
|
||||
"outResultPlaceholder": "Click the button to output the result"
|
||||
},
|
||||
"agents": {
|
||||
"AgentInitializer":{
|
||||
"display_name": "AgentInitializer",
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"pleaseEnterAccount": "请填写账号",
|
||||
"pleaseEnterPassword": "请填写密码",
|
||||
"accountTooShort": "账号过短",
|
||||
"passwordTooShort": "请填写密码,至少8位",
|
||||
"passwordError": "密码必须包含大小写字母、数字和字符!",
|
||||
"passwordTooShort": "请填写密码,至少6位",
|
||||
"passwordError": "密码必须包含字母、数字!",
|
||||
"passwordMismatch": "两次密码不一致",
|
||||
"registrationSuccess": "注册成功,请输入密码进行登录"
|
||||
},
|
||||
@@ -56,9 +56,13 @@
|
||||
},
|
||||
"skills": {
|
||||
"manageTemplate": "管理能力模板",
|
||||
"createNew": "新建",
|
||||
"manageProjects": "这里管理您的个人项目,对能力上下线、编辑等等",
|
||||
"skillSearch": "能力搜索",
|
||||
"createNew": "新建能力",
|
||||
"customSkills": "自定义能力",
|
||||
"chooseOnline": "选择上线版本",
|
||||
"executionSteps": "能力通过可视化的流程编排,明确任务执行步骤",
|
||||
"sceneTemplates": "我们提供场景模板供您使用和参考",
|
||||
"manageProjects": "在此页面管理您的能力,对能力上下线、编辑等等",
|
||||
"skillSearch": "搜索您需要的能力",
|
||||
"confirmDeleteSkill": "确认删除该能力?",
|
||||
"backToSkillList": "返回能力列表",
|
||||
"skillTemplateManagement": "能力模板管理,模板对所有用户可见,支持拖拽排序、删除操作",
|
||||
@@ -79,6 +83,7 @@
|
||||
"skillDescTooLong": "能力描述过长,不要超过200字",
|
||||
"errorTitle": "关键信息有误",
|
||||
"onlineFailure": "上线失败",
|
||||
"onlineSuccessful": "上线成功",
|
||||
"custom": "自定义",
|
||||
"skillTemplate": "能力模板",
|
||||
"skillTemplateChoose": "您可以从这里挑选一个模板开始,或者自定义高级模板",
|
||||
@@ -88,7 +93,23 @@
|
||||
"createdBy": "创建用户",
|
||||
"offline": "下线",
|
||||
"online": "上线",
|
||||
"guideWords": "引导词"
|
||||
"guideWords": "引导词",
|
||||
"currentVersion": "当前版本:",
|
||||
"importLocal": "导入本地组件",
|
||||
"save": "保存",
|
||||
"import": "导入",
|
||||
"export": "导出",
|
||||
"code": "代码",
|
||||
"simplify": "简化",
|
||||
"saveVersion": "另存为新版本",
|
||||
"deleteOrNot": "是否删除",
|
||||
"version": "版本",
|
||||
"saveSuccessful": "保存成功",
|
||||
"supportVersions": "支持分成多个版本分支,分别进行开发以及版本间的比较。",
|
||||
"guideQuestions50": "引导问题最多50个字符",
|
||||
"promptWords1000": "提示词最多为1000个字符",
|
||||
"contactAdmin": "请联系管理员上线NPC",
|
||||
"deleteSure": "确认删除该工具?"
|
||||
},
|
||||
"chat": {
|
||||
"newChat": "新建会话",
|
||||
@@ -158,6 +179,7 @@
|
||||
"machineName": "服务名",
|
||||
"addOne": "加一条"
|
||||
},
|
||||
|
||||
"flow": {
|
||||
"unsavedChangesConfirmation": "您有未保存的更改,确定要离开吗?",
|
||||
"leave": "离开",
|
||||
@@ -268,7 +290,13 @@
|
||||
"textDataset": "文本数据集",
|
||||
"tableDataset": "表格数据集",
|
||||
"createImport": "新建/导入",
|
||||
"knowledge": "知识库"
|
||||
"knowledge": "知识库",
|
||||
"web": "网站",
|
||||
"knowledgeId": "知识库ID",
|
||||
"knowledgeImg": "知识库头像",
|
||||
"indexModel": "索引模型",
|
||||
"dataUp": "单条数据上限",
|
||||
"introduce": "介绍"
|
||||
},
|
||||
"code": {
|
||||
"editPythonCodeDescription": "编辑你的 Python 代码此代码片段接受模块导入和一个函数定义。确保您的函数返回一个字符串。",
|
||||
@@ -408,6 +436,139 @@
|
||||
"previousPage": "上一页",
|
||||
"nextPage": "下一页",
|
||||
"formatError": "格式错误",
|
||||
"port": "服务端口",
|
||||
"cancle": "取消",
|
||||
"tip": "提示",
|
||||
"deleteAssistant": "确认删除该NPC?",
|
||||
"build": {
|
||||
"create": "创建",
|
||||
"assistant": "NPC",
|
||||
"skill": "能力",
|
||||
"tools": "工具",
|
||||
"save": "保存",
|
||||
"online": "上线",
|
||||
"retry": "重试",
|
||||
"use": "使用",
|
||||
"useAll": "全部使用",
|
||||
"assistantConfiguration": "NPC配置",
|
||||
"createAssistant": "新建NPC",
|
||||
"assistantPortrait": "NPC画像",
|
||||
"portraitOptimization": "NPC画像优化",
|
||||
"automaticOptimization": "自动优化",
|
||||
"createDescription": "通过描述角色和任务来创建你的NPC",
|
||||
"nextDescription": "NPC可以调用多个能力和工具",
|
||||
"searchAssistant": "搜索您需要的NPC",
|
||||
"manageAssistant": "在此页面管理您的NPC,对NPC上下线、编辑等等",
|
||||
"establishAssistant": "创建NPC",
|
||||
"assistantName": "NPC名称",
|
||||
"giveAssistantName": "给NPC取一个名字",
|
||||
"whatWant": "你希望NPC的角色是什么,具体完成什么任务?",
|
||||
"example": "示例",
|
||||
"exampleOne": "你是 XX,具有 XX 经验,擅长 XX,…",
|
||||
"exampleTwo": "你的任务是 XX ,需要按照以下步骤执行:",
|
||||
"automaticallyConfigurations": "自动为您选择相关配置",
|
||||
"prompt": "详细、具体地描述NPC与用户的交互方式,例如NPC的身份、完成任务的具体方法和步骤、回答问题时的语气以及应该注意什么问题等",
|
||||
"openingRemarks": "开场白",
|
||||
"guidingQuestions": "引导问题",
|
||||
"promptReplaced": "提示词已替换",
|
||||
"guideReplaced": "引导词已替换",
|
||||
"openingReplaced": "开场白已替换",
|
||||
"toolsReplaced": "工具已替换",
|
||||
"skillsReplaced": "能力已替换",
|
||||
"allReplaced": "已全部替换",
|
||||
"basicConfiguration": "基础配置",
|
||||
"modelConfiguration": "AI模型配置",
|
||||
"model": "模型",
|
||||
"temperature": "温度",
|
||||
"openingIntroduction": "开场引导",
|
||||
"openingStatement": "开场白",
|
||||
"assistantMessageFormat": "NPC将在每次对话开始时发送此信息,支持 Markdown 格式",
|
||||
"maximumPromptLength": "提示词最多为1000个字符",
|
||||
"recommendQuestionsForUsers": "为用户提供推荐问题,引导用户提问,超过3个时将随机选取3个",
|
||||
"maxCharacters50": "最多50个字符",
|
||||
"enterGuidingQuestions": "请输入引导问题",
|
||||
"knowledge": "知识",
|
||||
"knowledgeBase": "知识库",
|
||||
"autoCall": "自动调用",
|
||||
"callingMethod": "调用方式",
|
||||
"autoCallDescription": "每轮对话都会对添加的知识库进行检索召回。",
|
||||
"onDemandCall": "按需调用",
|
||||
"onDemandCallDescription": "在NPC画像(提示词)中提示调用 RecallKnowledge(可复制)方法,在有需要时才对知识库进行检索。",
|
||||
"createNewKnowledge": "新建知识库",
|
||||
"refresh": "刷新",
|
||||
"abilities": "能力",
|
||||
"skillDescription": "通过可视化界面实现复杂和稳定的业务流程编排,例如项目计划和报告分析",
|
||||
"selectKnowledgeBase": "请选择知识库",
|
||||
"searchBaseName": "搜索知识库名称",
|
||||
"debugPreview": "调试预览",
|
||||
"addTool": "添加工具",
|
||||
"search": "搜索",
|
||||
"empty": "空空如也",
|
||||
"onlineSA": "上线能力&NPC",
|
||||
"params": "参数",
|
||||
"added": "已添加",
|
||||
"add": "添加",
|
||||
"configurationUpdated": "配置已更新",
|
||||
"addSkill": "添加能力",
|
||||
"createSkill": "创建能力",
|
||||
"nameRequired": "名称不可为空",
|
||||
"nameMaxLength": "名称最多50个字符",
|
||||
"descMaxLength": "最多1000个字符",
|
||||
"editAssistant": "编辑NPC",
|
||||
"enterName": "给NPC取一个名字",
|
||||
"assistantDesc": "NPC描述",
|
||||
"enterDesc": "介绍NPC功能,描述在会话和NPC页面可见",
|
||||
"cancel": "取消",
|
||||
"confirm": "确认",
|
||||
"forBetter": "为了更好的NPC效果,描述需要大于20 个字",
|
||||
"forExample": "例如NPC的身份、完成任务的具体方法和步骤、回答问题时的语气以及应该注意什么问题等"
|
||||
},
|
||||
"tools1": {
|
||||
"addTool": "添加工具",
|
||||
"createCustomTool": "自定义工具",
|
||||
"builtinTools": "内置工具",
|
||||
"customTools": "自定义工具",
|
||||
"search": "搜索",
|
||||
"empty": "空空如也",
|
||||
"manageCustomTools": "在此页面管理您的自定义工具,对自定义工具创建、编辑等等",
|
||||
"name": "名称",
|
||||
"enterToolName": "输入工具名称",
|
||||
"openapiSchema": "OpenAPI Schema",
|
||||
"enterOpenAPISchema": "输入您的 OpenAPI schema",
|
||||
"importFromUrl": "从 URL 导入",
|
||||
"examples": "示例",
|
||||
"weatherJson": "天气(JSON)",
|
||||
"petShopYaml": "宠物商店(YAML)",
|
||||
"authenticationType": "鉴权方式",
|
||||
"authType": "认证类型",
|
||||
"none": "无",
|
||||
"apiKey": "API Key",
|
||||
"basic": "Basic",
|
||||
"bearer": "Bearer",
|
||||
"availableTools": "可用工具",
|
||||
"description": "描述",
|
||||
"method": "方法",
|
||||
"path": "路径",
|
||||
"delete": "删除",
|
||||
"cancel": "取消",
|
||||
"save": "保存"
|
||||
},
|
||||
"test": {
|
||||
"test": "测试",
|
||||
"addTest": "请先填写测试用例",
|
||||
"uploadTest": "上传测试用例",
|
||||
"explain": "为测试用例是当前组件的输入,只支持 txt 文件,最多 20 行",
|
||||
"testRun": "测试运行",
|
||||
"testCase": "测试用例",
|
||||
"run": "运行",
|
||||
"downloadResults": "下载运行结果",
|
||||
"testCases": "输入测试用例...",
|
||||
"parametersAndValues": "参数和值",
|
||||
"parameter": "参数",
|
||||
"value": "值",
|
||||
"result": "测试结果",
|
||||
"outResultPlaceholder": "点击按钮,输出结果"
|
||||
},
|
||||
"agents": {
|
||||
"AgentInitializer": {
|
||||
"display_name": "AgentInitializer",
|
||||
@@ -627,8 +788,8 @@
|
||||
},
|
||||
"autogen_roles": {
|
||||
"AutoGenAssistant": {
|
||||
"display_name": "自动生成助手",
|
||||
"description": "助手代理,设计用于使用大语言模型LLM解决任务。",
|
||||
"display_name": "自动生成NPC",
|
||||
"description": "NPC代理,设计用于使用大语言模型LLM解决任务。",
|
||||
"description_url": "https://lxm3ki8hexz.feishu.cn/wiki/Yz5GwMpO7ioj8nkRSZlcsriXnJ3",
|
||||
"template": {
|
||||
"model_name": {
|
||||
@@ -654,7 +815,7 @@
|
||||
}
|
||||
},
|
||||
"output_types":[
|
||||
"自动生成助手"
|
||||
"自动生成NPC"
|
||||
]
|
||||
},
|
||||
"AutoGenCoder": {
|
||||
@@ -2251,7 +2412,7 @@
|
||||
},
|
||||
"NPCsChatLLM": {
|
||||
"display_name": "NPCsChatLLM",
|
||||
"description": "`OpenAI`聊天大型语言模型API。",
|
||||
"description": "无缝切换支持的大语言模型",
|
||||
"description_url": "",
|
||||
"template": {
|
||||
"api_base": {
|
||||
|
||||
BIN
public/points.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/.DS_Store
vendored
13
src/App.css
@@ -191,9 +191,9 @@
|
||||
}
|
||||
|
||||
path.react-flow__edge-interaction:hover {
|
||||
stroke: #eebbbb;
|
||||
stroke: #ffe999;
|
||||
stroke-opacity: 1;
|
||||
stroke-width: 12;
|
||||
stroke-width: 10;
|
||||
}
|
||||
|
||||
.selection-tool-box {
|
||||
@@ -208,3 +208,12 @@ path.react-flow__edge-interaction:hover {
|
||||
z-index: 99;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bs-chat-bg {
|
||||
width: 100%;
|
||||
/* background: url(/points.png) 0 100% repeat-x; */
|
||||
background-size: 10px 432px;
|
||||
/* background: rgba(0,0,0,0.9); */
|
||||
/* background-color: #000; */
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import "./App.css";
|
||||
import ErrorAlert from "./alerts/error";
|
||||
import NoticeAlert from "./alerts/notice";
|
||||
import SuccessAlert from "./alerts/success";
|
||||
import { Toaster } from "./components/bs-ui/toast";
|
||||
import { alertContext } from "./contexts/alertContext";
|
||||
import { locationContext } from "./contexts/locationContext";
|
||||
import { userContext } from "./contexts/userContext";
|
||||
@@ -215,6 +216,8 @@ export default function App() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 新弹窗 */}
|
||||
<Toaster></Toaster>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -171,12 +171,14 @@ export default function GenericNode({ data, xPos, yPos, selected }: {
|
||||
</div >
|
||||
|
||||
|
||||
<div className="generic-node-desc nodrag" onKeyDown={e => e.stopPropagation()}>
|
||||
<div className="generic-node-desc-text" onClick={() => data.node.description_url && openPopUp(<DescriptionModel data={data.node.description_url} />)}>{data.node.description}</div>
|
||||
{/* <div className="generic-node-desc nodrag" onKeyDown={e => e.stopPropagation()}>
|
||||
<div className="generic-node-desc-text" onClick={() => data.node.description_url && openPopUp(<DescriptionModel data={data.node.description_url} />)}>{data.node.description}</div> */}
|
||||
{/*=======*/}
|
||||
{/* <div className="generic-node-desc nodrag">*/}
|
||||
{/* <div className="generic-node-desc-text">{data.node.description}</div>*/}
|
||||
{/*>>>>>>> bisheng_github*/}
|
||||
<div className="generic-node-desc nodrag">
|
||||
<div className="generic-node-desc-text">{data.node.description}</div>
|
||||
<>
|
||||
{Object.keys(data.node.template)
|
||||
.filter((t) => t.charAt(0) !== "_")
|
||||
@@ -230,7 +232,7 @@ export default function GenericNode({ data, xPos, yPos, selected }: {
|
||||
type={data.node.template[t].type}
|
||||
optionalHandle={data.node.template[t].input_types}
|
||||
onChange={() => fouceUpdateNode(!_)}
|
||||
nodeColorsP={nodeColors}
|
||||
// nodeColorsP={nodeColors}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
@@ -258,7 +260,7 @@ export default function GenericNode({ data, xPos, yPos, selected }: {
|
||||
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
||||
type={data.node.base_classes.join("|")}
|
||||
left={false}
|
||||
nodeColorsP={nodeColors}
|
||||
// nodeColorsP={nodeColors}
|
||||
/>
|
||||
{data.type === 'Report' && <div className="w-full bg-muted px-5 py-2">
|
||||
<Link to={`/report/${flowId}`}><Button variant="outline" className="px-10">Edit</Button></Link>
|
||||
|
||||
BIN
src/assets/.DS_Store
vendored
BIN
src/assets/chat/.DS_Store
vendored
BIN
src/assets/chat/copy.png
Normal file
|
After Width: | Height: | Size: 915 B |
BIN
src/assets/chat/xiazai.png
Normal file
|
After Width: | Height: | Size: 936 B |
BIN
src/assets/chat/xinjian1.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/chat/xinjian2.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
src/assets/knowledge/red-delete-icon.png
Normal file
|
After Width: | Height: | Size: 656 B |
BIN
src/assets/knowledge/set-active-icon.png
Normal file
|
After Width: | Height: | Size: 928 B |
BIN
src/assets/npc/.DS_Store
vendored
BIN
src/assets/npc/border-r.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src/assets/npc/gongjuAdd.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/npc/gongjuBianji.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/npc/gongjuIcon.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/npc/gongjuIcon1.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/npc/gongjuIcon3.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/npc/jianhua.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/npc/lingcun.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/npc/nengliIcon.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/npc/nengliIcon1.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/npc/npcIcon.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src/assets/npc/tuichu.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/npc/xiaoxi.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/npc/zidingyijia.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/npc/图层 1863@2x (1).png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
src/assets/npc/矩形 1 拷贝 8@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/toolbar/.DS_Store
vendored
Normal file
BIN
src/assets/toolbar/version.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/components/.DS_Store
vendored
Normal file
142
src/components/Chart/index.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import * as echarts from "echarts";
|
||||
|
||||
function Chart({ options }) {
|
||||
const chartRef = useRef(null);
|
||||
let chartInstance = null;
|
||||
|
||||
options = eval('('+options.substring(8)+')');
|
||||
// options = {
|
||||
// "chartType": "line",
|
||||
// "xData": [
|
||||
// "江苏",
|
||||
// "浙江",
|
||||
// "广东",
|
||||
// "广州",
|
||||
// "北京",
|
||||
// "山西",
|
||||
// "河北"
|
||||
// ],
|
||||
// "data": {
|
||||
// "2023上半年":['120%', '132%', '101%', '134%', '90%', '230%', '210%'],
|
||||
// "2024上半年":['220%', '182%', '191%', '234%', '290%', '330%', '310%']
|
||||
// },
|
||||
// "legend": [],
|
||||
// "pieData": []
|
||||
// }
|
||||
options.legend = Object.keys(options.data);
|
||||
options.pieData = [];
|
||||
options.series = [];
|
||||
|
||||
options.xData.map((data,index) =>{
|
||||
if(options.data[options.legend[0]][index] &&options.data[options.legend[0]][index].indexOf("%") !== -1){
|
||||
options.pieData.push({'name':data,'value':options.data[options.legend[0]][index].split("%").join("")})
|
||||
}else{
|
||||
options.pieData.push({'name':data,'value':options.data[options.legend[0]][index]})
|
||||
}
|
||||
})
|
||||
options.legend.map((data,index) =>{
|
||||
let list = [];
|
||||
options.data[data].map(data1 =>{
|
||||
console.log(data1.indexOf('%'))
|
||||
if(data1.indexOf('%') !== -1){
|
||||
list.push(data1.split("%").join(""))
|
||||
}else{
|
||||
list.push(data1)
|
||||
}
|
||||
});
|
||||
options.series.push({
|
||||
name: data,
|
||||
type: options.chartType,
|
||||
data: list,
|
||||
})
|
||||
})
|
||||
console.log(options)
|
||||
let options1;
|
||||
if(options.chartType == "pie"){
|
||||
options1 = {
|
||||
tooltip: {},
|
||||
series: [
|
||||
{
|
||||
name: 'Access From',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
label: {
|
||||
normal: {
|
||||
|
||||
textStyle: { // 提示文字的样式
|
||||
color: '#fff',
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
},
|
||||
data: options.pieData,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}else{
|
||||
options1 = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
data: options.xData,
|
||||
},
|
||||
yAxis: {},
|
||||
series: options.series,
|
||||
};
|
||||
}
|
||||
// 定义渲染函数
|
||||
function renderChart() {
|
||||
try {
|
||||
// `echarts.getInstanceByDom` 可以从已经渲染成功的图表中获取实例,其目的就是在 options 发生改变的时候,不需要
|
||||
// 重新创建图表,而是复用该图表实例,提升性能
|
||||
const renderedInstance = echarts.getInstanceByDom(chartRef.current);
|
||||
if (renderedInstance) {
|
||||
chartInstance = renderedInstance;
|
||||
} else {
|
||||
chartInstance = echarts.init(chartRef.current);
|
||||
}
|
||||
chartInstance.setOption(options1);
|
||||
} catch (error) {
|
||||
console.error("error", error.message);
|
||||
chartInstance && chartInstance.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// 定义窗口大小发生改变执行的回调函数
|
||||
function resizeHandler() {
|
||||
chartInstance.resize();
|
||||
}
|
||||
|
||||
// 页面初始化时,开始渲染图表
|
||||
useEffect(() => {
|
||||
renderChart();
|
||||
|
||||
return () => {
|
||||
// 销毁图表实例,释放内存
|
||||
chartInstance && chartInstance.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 监听窗口大小改变
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", resizeHandler);
|
||||
return () => window.removeEventListener("resize", resizeHandler);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ width: "800px", height: "400px" }} ref={chartRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Chart;
|
||||
@@ -35,11 +35,11 @@ interface IProps<T> {
|
||||
}
|
||||
|
||||
export const gradients = [
|
||||
'bg-amber-500',
|
||||
'bg-orange-600',
|
||||
'bg-teal-500',
|
||||
'bg-purple-600',
|
||||
'bg-blue-700'
|
||||
'bg-[#FF9B25]',
|
||||
'bg-[#04BCD2]',
|
||||
'bg-[#2586FF]',
|
||||
'bg-[#D855D3]',
|
||||
'bg-[#C04B51]'
|
||||
]
|
||||
|
||||
// 'bg-slate-600',
|
||||
@@ -60,8 +60,11 @@ export const gradients = [
|
||||
// 'bg-fuchsia-700',
|
||||
// 'bg-pink-600',
|
||||
// 'bg-rose-600'
|
||||
export function TitleIconBg({ id, className = '', children = <SkillIcon /> }) {
|
||||
return <div className={cname(`rounded-sm flex justify-center items-center ${gradients[parseInt(id + '', 16) % gradients.length]}`, className)}>{children}</div>
|
||||
export function TitleIconBg({ img, id, className = '', children = <SkillIcon /> }) {
|
||||
if(img) return <div className={cname(`flex justify-center items-center cursor-pointer overflow-hidden`, className)} style={{borderRadius:"7px"}}>
|
||||
{children}
|
||||
</div>
|
||||
return <div className={cname(`flex justify-center items-center cursor-pointer ${id!="" && gradients[parseInt(id + '', 16) % gradients.length]}`, className)} style={{borderRadius:"7px"}}>{children}</div>
|
||||
}
|
||||
|
||||
export default function CardComponent<T>({
|
||||
|
||||
@@ -8,6 +8,9 @@ import { useTranslation } from "react-i18next";
|
||||
import { useMessageStore } from "./messageStore";
|
||||
import GuideQuestions from "./GuideQuestions";
|
||||
import { ClearIcon } from "@/components/bs-icons/clear";
|
||||
import duihua_send from "../../../assets/chat/duihua-send.png";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { StopCircle } from "lucide-react";
|
||||
|
||||
export default function ChatInput({ clear, form, questions, inputForm, wsUrl, onBeforSend }) {
|
||||
const { toast } = useToast()
|
||||
@@ -21,7 +24,8 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
const { messages, chatId, createSendMsg, createWsMsg, updateCurrentMessage, destory, setShowGuideQuestion } = useMessageStore()
|
||||
const currentChatIdRef = useRef(null)
|
||||
const inputRef = useRef(null)
|
||||
|
||||
// 停止状态
|
||||
const [isStop, setIsStop] = useState(true)
|
||||
/**
|
||||
* 记录会话切换状态,等待消息加载完成时,控制表单在新会话自动展开
|
||||
*/
|
||||
@@ -87,6 +91,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
// 锁定 input
|
||||
setInputLock({ locked: true, reason: '' })
|
||||
await createWebSocket(chatId)
|
||||
// console.log(wsMsg,inputKey);
|
||||
sendWsMsg(wsMsg)
|
||||
|
||||
// 滚动聊天到底
|
||||
@@ -95,7 +100,13 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
messageDom.scrollTop = messageDom.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
const stop = async () => {
|
||||
const [wsMsg] = onBeforSend('', '')
|
||||
wsMsg.action = "stop"
|
||||
sendWsMsg(wsMsg)
|
||||
// console.log(wsMsg);
|
||||
// sendWsMsg(wsMsg)
|
||||
}
|
||||
const sendWsMsg = async (msg) => {
|
||||
try {
|
||||
wsRef.current.send(JSON.stringify(msg))
|
||||
@@ -157,6 +168,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
ws.onerror = (ev) => {
|
||||
wsRef.current = null
|
||||
console.error('链接异常error', ev);
|
||||
setIsStop(true)
|
||||
toast({
|
||||
title: `${t('chat.networkError')}:`,
|
||||
variant: 'error',
|
||||
@@ -177,13 +189,18 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
|
||||
// 接受 ws 消息
|
||||
const handleWsMessage = (data) => {
|
||||
// console.log(data)
|
||||
if (Array.isArray(data) && data.length) return
|
||||
if (data.type === 'start') {
|
||||
if (data.type === "begin") {
|
||||
setIsStop(false)
|
||||
}else if (data.type === 'start') {
|
||||
createWsMsg(data)
|
||||
} else if (data.type === 'stream') {
|
||||
updateCurrentMessage({
|
||||
flow_id: data.flow_id,
|
||||
chat_id: data.chat_id,
|
||||
message: data.message,
|
||||
reasoning_content: data.reasoning_content,
|
||||
thought: data.intermediate_steps
|
||||
})
|
||||
} else if (['end', 'end_cover'].includes(data.type)) {
|
||||
@@ -196,6 +213,7 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
liked: 0
|
||||
}, data.type === 'end_cover')
|
||||
} else if (data.type === "close") {
|
||||
setIsStop(true)
|
||||
setInputLock({ locked: false, reason: '' })
|
||||
}
|
||||
|
||||
@@ -230,12 +248,12 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
// setInputEmpty(textarea.value.trim() === '')
|
||||
}
|
||||
|
||||
return <div className="absolute bottom-0 w-full pt-1 bg-[#fff] dark:bg-[#1B1B1B]">
|
||||
<div className={`relative ${clear && 'pl-9'}`}>
|
||||
return <div className="absolute bottom-0 w-full bg-[#fff] dark:bg-[#000000]">
|
||||
<div className={`relative pt-[10px]`}>
|
||||
{/* form */}
|
||||
{
|
||||
formShow && <div className="relative">
|
||||
<div className="absolute left-0 border bottom-2 bg-[#fff] px-4 py-2 rounded-md w-[50%] min-w-80">
|
||||
<div className="absolute left-0 bottom-2 bg-[#1a1a1a] px-4 py-2 rounded-md w-[50%] min-w-80">
|
||||
{inputForm}
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,14 +266,14 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
onClick={handleClickGuideWord}
|
||||
/>
|
||||
{/* clear */}
|
||||
<div className="flex absolute left-0 top-4 z-10">
|
||||
{/* <div className="flex absolute left-0 top-4 z-10">
|
||||
{
|
||||
clear && <div
|
||||
className={`w-6 h-6 rounded-sm hover:bg-gray-200 cursor-pointer flex justify-center items-center `}
|
||||
onClick={() => { !inputLock.locked && destory() }}
|
||||
><ClearIcon className={!showWhenLocked && inputLock.locked ? 'text-gray-400' : 'text-gray-950'} ></ClearIcon></div>
|
||||
}
|
||||
</div>
|
||||
</div> */}
|
||||
{/* form */}
|
||||
<div className="flex absolute left-3 top-4 z-10">
|
||||
{
|
||||
@@ -266,30 +284,40 @@ export default function ChatInput({ clear, form, questions, inputForm, wsUrl, on
|
||||
}
|
||||
</div>
|
||||
{/* send */}
|
||||
<div className="flex gap-2 absolute right-3 top-4 z-10">
|
||||
<div className="flex gap-2 absolute right-[2.5%] z-10">
|
||||
<div
|
||||
id="bs-send-btn"
|
||||
className="w-6 h-6 rounded-sm hover:bg-gray-200 cursor-pointer flex justify-center items-center"
|
||||
className="w-[68px] h-[40px] bg-[#FFD54C] cursor-pointer flex justify-center items-center"
|
||||
onClick={() => { !inputLock.locked && handleSendClick() }}
|
||||
><SendIcon className={inputLock.locked ? 'text-gray-400' : 'text-gray-950'}></SendIcon></div>
|
||||
style={{borderRadius:"20px"}}
|
||||
>
|
||||
{/* <SendIcon className={inputLock.locked ? 'text-gray-400' : 'text-gray-950'}></SendIcon> */}
|
||||
<img src={duihua_send} className="w-[20px]" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
{/* question */}
|
||||
<Textarea
|
||||
<textarea
|
||||
id="bs-send-input"
|
||||
ref={inputRef}
|
||||
rows={1}
|
||||
style={{ height: 56 }}
|
||||
style={{ height: 34 }}
|
||||
disabled={inputLock.locked}
|
||||
onInput={handleTextAreaHeight}
|
||||
placeholder={inputLock.locked ? inputLock.reason : t('chat.inputPlaceholder')}
|
||||
className={"resize-none py-4 pr-10 text-md min-h-6 max-h-[200px] scrollbar-hide dark:bg-[#2A2B2E] text-gray-800" + (form && ' pl-10')}
|
||||
// className={"resize-none py-4 pr-10 text-md min-h-6 max-h-[200px] scrollbar-hide dark:bg-[#2A2B2E] text-gray-800" + (form && ' pl-10')}
|
||||
className="questionTextarea w-full resize-none border-none bg-transparent outline-none max-h-[160px]"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
!inputLock.locked && handleSendClick()
|
||||
}
|
||||
}}
|
||||
></Textarea>
|
||||
></textarea>
|
||||
{!isStop && <div className=" absolute w-full flex justify-center bottom-32 pointer-events-none">
|
||||
<Button className="rounded-full pointer-events-auto" variant="outline" disabled={isStop} onClick={() => { setIsStop(true); stop(); }}><StopCircle className="mr-2" />Stop</Button>
|
||||
</div>}
|
||||
|
||||
{/* <p className="w-[100%] text-center text-[#666666]">内容由AI生成,仅供参考</p> */}
|
||||
</div>
|
||||
<p className="text-center text-sm pt-2 pb-4 text-gray-400">{appConfig.dialogTips}</p>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,9 @@ import { WordIcon } from "@/components/bs-icons/office";
|
||||
import { checkSassUrl } from "@/pages/ChatAppPage/components/FileView";
|
||||
import { downloadFile } from "@/util/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import npcIcon from "../../../assets/npc/npcIcon.png";
|
||||
import nengliIcon from "../../../assets/npc/nengliIcon.png";
|
||||
import { TitleIconBg } from "../cardComponent";
|
||||
|
||||
// 颜色列表
|
||||
const colorList = [
|
||||
@@ -19,7 +22,7 @@ const colorList = [
|
||||
"#95A5A6"
|
||||
]
|
||||
|
||||
export default function FileBs({ data }) {
|
||||
export default function FileBs({ data,flow_type }) {
|
||||
const { t } = useTranslation()
|
||||
const avatarColor = colorList[(data.sender?.split('').reduce((num, s) => num + s.charCodeAt(), 0) || 0) % colorList.length]
|
||||
|
||||
@@ -33,15 +36,16 @@ export default function FileBs({ data }) {
|
||||
<div className="w-fit min-h-8 rounded-2xl px-6 py-4 max-w-[90%]">
|
||||
{data.sender && <p className="text-primary text-xs mb-2" style={{ background: avatarColor }}>{data.sender}</p>}
|
||||
<div className="flex gap-2 ">
|
||||
<div className="w-6 h-6 min-w-6 flex justify-center items-center rounded-full" style={{ background: avatarColor }} ><AvatarIcon /></div>
|
||||
{data.flow_id && <TitleIconBg className="w-[40px] h-[40px]" img={data.avatar_img} id={data.avatar_color ? data.avatar_color : data.flow_id} ><img src={data.avatar_img ? data.avatar_img : (flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>}
|
||||
{/* <div className="w-6 h-6 min-w-6 flex justify-center items-center rounded-full" style={{ background: avatarColor }} ><AvatarIcon /></div> */}
|
||||
<div
|
||||
className="flex gap-2 w-52 border border-gray-200 shadow-sm bg-gray-50 px-4 py-2 rounded-sm cursor-pointer"
|
||||
className="flex gap-2 w-52 shadow-sm bg-[#1a1a1a] px-4 py-2 rounded-sm cursor-pointer"
|
||||
onClick={() => handleDownloadFile(data.files[0])}
|
||||
>
|
||||
<div className="flex items-center"><WordIcon /></div>
|
||||
<div className="flex items-center text-[#43AFD2]"><WordIcon /></div>
|
||||
<div>
|
||||
<h1 className="text-sm font-bold">{data.files[0]?.file_name}</h1>
|
||||
<p className="text-xs text-gray-400 mt-1">点击下载</p>
|
||||
<h1 className="text-sm font-bold text-[#43AFD2]">{data.files[0]?.file_name}</h1>
|
||||
<p className="text-xs text-[#666] mt-1">点击下载</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,13 +27,13 @@ export default function GuideQuestions({ locked, chatId, questions, onClick }) {
|
||||
if (locked || !words.length) return null
|
||||
|
||||
if (showGuideQuestion) return <div className="relative">
|
||||
<div className="absolute left-0 bottom-0">
|
||||
<p className="text-gray-950 text-sm mb-2 bg-[rgba(255,255,255,0.8)] rounded-md w-fit px-2 py-1">{t('chat.recommendationQuestions')}</p>
|
||||
<div className="absolute left-[14px] bottom-0">
|
||||
<p className="text-[#fff] mb-[10px] bg-[]">推荐问题</p>
|
||||
{
|
||||
words.map((question, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="w-fit bg-[#d4dffa] border-2 border-gray-50 shadow-md text-gray-600 rounded-md mb-1 px-4 py-1 text-sm cursor-pointer"
|
||||
className="w-fit bg-[#52430c] shadow-md text-[#fff] rounded-md mb-1 px-4 py-1 text-sm cursor-pointer"
|
||||
onClick={() => {
|
||||
setShowGuideQuestion(false)
|
||||
onClick(question)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { LoadIcon } from "@/components/bs-icons/loading";
|
||||
import { CodeBlock } from "@/modals/formModal/chatMessage/codeBlock";
|
||||
import { ChatMessageType } from "@/types/chat";
|
||||
import { copyText } from "@/utils";
|
||||
import { useMemo, useRef } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import rehypeMathjax from "rehype-mathjax";
|
||||
import remarkGfm from "remark-gfm";
|
||||
@@ -11,7 +11,18 @@ import remarkMath from "remark-math";
|
||||
import MessageButtons from "./MessageButtons";
|
||||
import SourceEntry from "./SourceEntry";
|
||||
import { useMessageStore } from "./messageStore";
|
||||
|
||||
import robot from "../../../assets/robot.png";
|
||||
import robotU from "../../../assets/robotU.png";
|
||||
import robot2 from "../../../assets/robot2.png";
|
||||
import robot3 from "../../../assets/robot3.png";
|
||||
import btnEdit from "../../../assets/chat/btn-edit.png";
|
||||
import btnReSend from "../../../assets/chat/btn-reSend.png";
|
||||
import btnDel from "../../../assets/chat/btn-del.png";
|
||||
import npcIcon from "../../../assets/npc/npcIcon.png";
|
||||
import nengliIcon from "../../../assets/npc/nengliIcon.png";
|
||||
import { TitleIconBg } from "../cardComponent";
|
||||
import Thumbs from "@/pages/ChatAppPage/components/Thumbs";
|
||||
import Chart from "@/components/Chart"
|
||||
// 颜色列表
|
||||
const colorList = [
|
||||
"#111",
|
||||
@@ -27,7 +38,7 @@ const colorList = [
|
||||
"#95A5A6"
|
||||
]
|
||||
|
||||
export default function MessageBs({ data, onUnlike = () => { }, onSource }: { data: ChatMessageType, onUnlike?: any, onSource?: any }) {
|
||||
export default function MessageBs({ data, onUnlike = () => { }, flow_type, onSource }: { data: ChatMessageType, flow_type: any, onUnlike?: any, onSource?: any }) {
|
||||
const avatarColor = colorList[
|
||||
(data.sender?.split('').reduce((num, s) => num + s.charCodeAt(), 0) || 0) % colorList.length
|
||||
]
|
||||
@@ -70,33 +81,119 @@ export default function MessageBs({ data, onUnlike = () => { }, onSource }: { da
|
||||
[data.message, data.message.toString()]
|
||||
)
|
||||
|
||||
const mkdownReasoning = useMemo(
|
||||
() => (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
linkTarget="_blank"
|
||||
className="bs-mkdown inline-block break-all max-w-full text-sm text-text-answer "
|
||||
components={{
|
||||
code: ({ node, inline, className, children, ...props }) => {
|
||||
if (children.length) {
|
||||
if (children[0] === "▍") {
|
||||
return (<span className="form-modal-markdown-span"> ▍ </span>);
|
||||
}
|
||||
|
||||
children[0] = (children[0] as string).replace("`▍`", "▍");
|
||||
}
|
||||
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
|
||||
return !inline ? (
|
||||
<CodeBlock
|
||||
key={Math.random()}
|
||||
language={(match && match[1]) || ""}
|
||||
value={String(children).replace(/\n$/, "")}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<code className={className} {...props}> {children} </code>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{data.reasoning_content ? data.reasoning_content.toString() : ""}
|
||||
</ReactMarkdown>
|
||||
),
|
||||
[data.reasoning_content, data.reasoning_content ? data.reasoning_content.toString() : ""]
|
||||
)
|
||||
|
||||
const messageRef = useRef<HTMLDivElement>(null)
|
||||
const handleCopyMessage = () => {
|
||||
copyText(messageRef.current)
|
||||
}
|
||||
|
||||
// let shenduOff = useRef(true)
|
||||
const [shenduOff, setShenduOff] = useState(true)
|
||||
const chatId = useMessageStore(state => state.chatId)
|
||||
|
||||
return <div className="flex w-full py-1">
|
||||
<div className="w-fit max-w-[90%]">
|
||||
<div className="w-[100%]">
|
||||
{data.sender && <p className="text-gray-600 text-xs mb-2">{data.sender}</p>}
|
||||
<div className="min-h-8 px-6 py-4 rounded-2xl bg-[#F5F6F8] dark:bg-[#313336]">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-6 h-6 min-w-6 flex justify-center items-center rounded-full" style={{ background: avatarColor }} ><AvatarIcon /></div>
|
||||
<div className="flex items-start avatarZk">
|
||||
{/* {(data.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || data.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[50px]" alt=""/>}
|
||||
{data.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[50px]" alt=""/>}
|
||||
{(data.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && data.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && data.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[50px]" alt=""/>} */}
|
||||
{data.flow_id && <TitleIconBg className="w-[40px] h-[40px] mr-[10px]" img={flow_type.avatar_img} id={flow_type.avatar_color ? flow_type.avatar_color : data.flow_id} ><img src={flow_type.avatar_img ? flow_type.avatar_img : (flow_type.type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>}
|
||||
|
||||
<div ref={messageRef} className="text-sm max-w-[calc(100%-100px)]">
|
||||
{/* <div className="chat-start-zk relative">
|
||||
{data.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>} */}
|
||||
{/* @user */}
|
||||
{/* {data.receiver && <p className="text-blue-500 text-sm">@ {data.receiver.user_name}</p>} */}
|
||||
{/* 光标 */}
|
||||
{/* {data.message.toString() && !data.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>} */}
|
||||
{/* </div> */}
|
||||
{/* <div className="App">
|
||||
<Chart options={data.message} />
|
||||
</div>
|
||||
{data.message.toString() ?
|
||||
<div ref={messageRef} className="text-sm max-w-[calc(100%-24px)]">
|
||||
<div className="chat-start-zk relative">
|
||||
{mkdown}
|
||||
{data.receiver && <p className="text-blue-500 text-sm">@ {data.receiver.user_name}</p>}
|
||||
|
||||
</div>
|
||||
: <div><LoadIcon className="text-gray-400" /></div>
|
||||
} */}
|
||||
{(data.message.toString() || (data.reasoning_content && data.reasoning_content.toString())) && data.message.toString().includes('```chart') && <Chart options={data.message} />}
|
||||
{(data.message.toString() || (data.reasoning_content && data.reasoning_content.toString())) && !data.message.toString().includes('```chart') && <div className="chat-start-zk relative">
|
||||
{(data.reasoning_content && data.reasoning_content.toString()) && <div className="shendu">
|
||||
<div className="shenduBtn" onClick={() => {setShenduOff(!shenduOff)}}>深度思考 <svg className={`${!shenduOff && 'transform180'}`} width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5.786 4.167L2.765 1.259c-.416-.4-.985-.482-1.273-.183-.287.298-.183.864.233 1.264l3.021 2.908c.416.4.986.482 1.273.184.287-.299.183-.865-.233-1.265z" fill="currentColor"></path><path d="M8.197 1.206L5.288 4.208c-.4.413-.484.982-.187 1.27.298.289.864.187 1.265-.227L9.274 2.25c.401-.414.485-.983.187-1.271-.297-.288-.863-.187-1.264.227z" fill="currentColor"></path></svg></div>
|
||||
{shenduOff && <div className="shenduText">{mkdownReasoning}</div>}
|
||||
</div>}
|
||||
<div className="mt-[10px]">{mkdown}</div>
|
||||
{/* @user */}
|
||||
{data.receiver && <p className="text-blue-500 text-sm">@ {data.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{/* {data.message.toString() && !data.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>} */}
|
||||
</div>
|
||||
: <div><LoadIcon className="text-gray-400" /></div>
|
||||
</div>}
|
||||
{(!data.message.toString() && !data.reasoning_content.toString()) && <div><LoadIcon className="text-gray-400" /></div>}
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!data.id && data.end && <Thumbs
|
||||
id={data.id}
|
||||
data={data.liked}
|
||||
onCopy={handleCopyMessage}
|
||||
onDislike={onUnlike}
|
||||
className="chat-start-btn"
|
||||
></Thumbs>
|
||||
}
|
||||
</div>
|
||||
{/* <div className="flex gap-2">
|
||||
<div className="w-6 h-6 min-w-6 flex justify-center items-center rounded-full" style={{ background: avatarColor }} ><AvatarIcon /></div>
|
||||
{data.message.toString() ?
|
||||
<div ref={messageRef} className="text-sm max-w-[calc(100%-24px)]">
|
||||
{mkdown} */}
|
||||
{/* @user */}
|
||||
{/* {data.receiver && <p className="text-blue-500 text-sm">@ {data.receiver.user_name}</p>} */}
|
||||
{/* 光标 */}
|
||||
{/* {data.message.toString() && !data.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>} */}
|
||||
{/* </div>
|
||||
: <div><LoadIcon className="text-gray-400" /></div>
|
||||
}
|
||||
</div> */}
|
||||
</div>
|
||||
{/* 附加信息 */}
|
||||
{
|
||||
{/* {
|
||||
!!data.id && data.end && <div className="flex justify-between mt-2">
|
||||
<SourceEntry
|
||||
extra={data.extra}
|
||||
@@ -115,7 +212,7 @@ export default function MessageBs({ data, onUnlike = () => { }, onSource }: { da
|
||||
onCopy={handleCopyMessage}
|
||||
></MessageButtons>
|
||||
</div>
|
||||
}
|
||||
} */}
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import RunLog from "./RunLog";
|
||||
import Separator from "./Separator";
|
||||
import { useMessageStore } from "./messageStore";
|
||||
|
||||
export default function MessagePanne({ useName, guideWord, loadMore }) {
|
||||
export default function MessagePanne({ useName, guideWord, loadMore, flow_type }) {
|
||||
const { t } = useTranslation()
|
||||
const { chatId, messages } = useMessageStore()
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function MessagePanne({ useName, guideWord, loadMore }) {
|
||||
return () => messagesRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}, [messagesRef.current, messages, chatId]);
|
||||
|
||||
return <div id="message-panne" ref={messagesRef} className="h-full overflow-y-auto scrollbar-hide pt-12 pb-60">
|
||||
return <div id="message-panne" ref={messagesRef} className="h-full overflow-y-auto scrollbar-hide pt-[50px] pb-60 px-3">
|
||||
{guideWord && <MessageBs
|
||||
key={9999}
|
||||
data={{ message: guideWord, isSend: false, chatKey: '', end: true, user_name: '' }} />}
|
||||
@@ -75,7 +75,6 @@ export default function MessagePanne({ useName, guideWord, loadMore }) {
|
||||
} else if (msg.thought) {
|
||||
type = 'system'
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'user':
|
||||
return <MessageUser key={msg.id} useName={useName} data={msg} />;
|
||||
@@ -83,15 +82,17 @@ export default function MessagePanne({ useName, guideWord, loadMore }) {
|
||||
return <MessageBs
|
||||
key={msg.id}
|
||||
data={msg}
|
||||
flow_type={flow_type}
|
||||
onUnlike={(chatId) => { thumbRef.current?.openModal(chatId) }}
|
||||
onSource={(data) => { sourceRef.current?.openModal(data) }}
|
||||
/>;
|
||||
case 'system':
|
||||
return <MessageSystem key={msg.id} data={msg} />;
|
||||
// return <RunLog key={msg.id} data={msg} />;
|
||||
case 'separator':
|
||||
return <Separator key={msg.id} text={msg.message || t('chat.roundOver')} />;
|
||||
case 'file':
|
||||
return <FileBs key={msg.id} data={msg} />;
|
||||
return <FileBs key={msg.id} data={msg} flow_type={flow_type}/>;
|
||||
case 'runLog':
|
||||
return <RunLog key={msg.id} data={msg} />;
|
||||
default:
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function MessageSystem({ data }) {
|
||||
() => (
|
||||
data.thought && <ReactMarkdown
|
||||
linkTarget="_blank"
|
||||
className="bs-mkdown text-gray-600 dark:text-[white] inline-block break-all max-w-full text-sm [&>pre]:text-wrap"
|
||||
className="markdown text-gray-600 inline-block break-all max-w-full text-sm"
|
||||
>
|
||||
{data.thought.toString()}
|
||||
</ReactMarkdown>
|
||||
@@ -33,9 +33,10 @@ export default function MessageSystem({ data }) {
|
||||
)
|
||||
|
||||
const border = { system: 'border-slate-500', question: 'border-amber-500', processing: 'border-cyan-600', answer: 'border-lime-600', report: 'border-slate-500', guide: 'border-none' }
|
||||
const style = { system: 'style-system', question: 'style-question', processing: 'border-cyan-600', answer: 'style-answer', report: 'style-system' }
|
||||
|
||||
return <div className="py-1">
|
||||
<div className={`relative rounded-sm px-6 py-4 border text-sm ${data.category === 'guide' ? 'bg-[#EDEFF6]' : 'bg-slate-50'} ${border[data.category || 'system']}`}>
|
||||
<div className={`log rounded-xl whitespace-pre-wrap mt-[14px] relative ${style[data.category || 'system']}`}>
|
||||
{logMkdown}
|
||||
{data.category === 'report' && <CopyIcon className=" absolute right-4 top-2 cursor-pointer" onClick={(e) => handleCopy(e.target.parentNode)}></CopyIcon>}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,9 @@ import { ChatMessageType } from "@/types/chat";
|
||||
import { MagnifyingGlassIcon, Pencil2Icon, ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { useContext } from "react";
|
||||
import { useMessageStore } from "./messageStore";
|
||||
import robotU from "../../../assets/robotU.png";
|
||||
import btnEdit from "../../../assets/chat/btn-edit.png";
|
||||
import btnReSend from "../../../assets/chat/btn-reSend.png";
|
||||
|
||||
export default function MessageUser({ useName, data }: { data: ChatMessageType }) {
|
||||
const msg = data.message[data.chatKey]
|
||||
@@ -25,18 +28,34 @@ export default function MessageUser({ useName, data }: { data: ChatMessageType }
|
||||
}
|
||||
|
||||
return <div className="flex justify-end w-full py-1">
|
||||
<div className="w-fit min-h-8 max-w-[90%]">
|
||||
<div className="flex items-start avatarZk">
|
||||
<div className="mr-[10px]">
|
||||
<div className="chat-end-zk">
|
||||
{/* {chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>} */}
|
||||
{msg}
|
||||
</div>
|
||||
{!Array.isArray(data.message.data) && <div className='chat-end-btn'>
|
||||
{!running && <img src={btnEdit} onClick={() => handleResend(false)} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{!running && <img src={btnReSend} onClick={() => handleResend(true)} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[28px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>}
|
||||
</div>
|
||||
{/* <p className="mr-[20px] text-[14px]">{userName}</p> */}
|
||||
<img src={robotU} className="w-[50px]" alt=""/>
|
||||
</div>
|
||||
{/* <div className="w-fit min-h-8 max-w-[90%]">
|
||||
{useName && <p className="text-gray-600 text-xs mb-2 text-right">{useName}</p>}
|
||||
<div className="rounded-2xl px-6 py-4 bg-[#EEF2FF] dark:bg-[#333A48]">
|
||||
<div className="mr-[14px] rounded-2xl px-6 py-4 bg-[#EEF2FF] dark:bg-[#333A48]">
|
||||
<div className="flex gap-2 ">
|
||||
<div className="text-[#0D1638] dark:text-[#CFD5E8] text-sm break-all whitespace-break-spaces">{msg}</div>
|
||||
<div className="w-6 h-6 min-w-6"><img src="/user.png" alt="" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
{/* 附加信息 */}
|
||||
{
|
||||
{/* {
|
||||
// 数组类型的 data通常是文件上传消息,不展示附加按钮
|
||||
!Array.isArray(data.message.data) && <div className="flex justify-between mt-2">
|
||||
!Array.isArray(data.message.data) && <div className="flex justify-between mt-2 mr-[14px]">
|
||||
<span></span>
|
||||
<div className="flex gap-2 text-gray-400 cursor-pointer self-end">
|
||||
{!running && <Pencil2Icon className="hover:text-gray-500" onClick={() => handleResend(false)} />}
|
||||
@@ -45,6 +64,6 @@ export default function MessageUser({ useName, data }: { data: ChatMessageType }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -34,6 +34,8 @@ export default function RunLog({ data }) {
|
||||
// if (!knowledge) throw new Error('调试日志无法匹配到使用的知识库详情,id:' + data.message.tool_key)
|
||||
|
||||
title = knowledge ? `${data.end ? '已搜索' : '正在搜索'} ${knowledge.name}` : '知识库已被删除,无法获取知识库名'
|
||||
} else if (data.category === 'processing') {
|
||||
title = data.thought.slice(0, 50)+"..."
|
||||
}
|
||||
return [title, lost]
|
||||
}, [assistantState, data])
|
||||
@@ -41,16 +43,16 @@ export default function RunLog({ data }) {
|
||||
return <div className="py-1">
|
||||
<div className="rounded-sm border">
|
||||
<div className="flex justify-between items-center px-4 py-2 cursor-pointer" onClick={() => setOpen(!open)}>
|
||||
<div className="flex items-center font-bold gap-2 text-sm">
|
||||
<div className="flex items-center font-bold gap-2 text-sm text-[#fff]">
|
||||
{
|
||||
data.end ? <ToastIcon type={lost ? 'error' : 'success'} /> :
|
||||
<LoadIcon className="text-primary duration-300" />
|
||||
}
|
||||
<span>{title}</span>
|
||||
<span className="text-[#666]">{title}</span>
|
||||
</div>
|
||||
<CaretDownIcon className={open && 'rotate-180'} />
|
||||
</div>
|
||||
<div className={cname('bg-gray-100 px-4 py-2 text-gray-500 overflow-hidden text-sm ', open ? 'h-auto' : 'h-0 p-0')}>
|
||||
<div className={cname('bg-[#0B1F26] px-4 py-2 text-[#ccc] overflow-hidden text-sm ', open ? 'h-auto' : 'h-0 p-0')}>
|
||||
<p>{data.thought}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import ChatInput from "./ChatInput";
|
||||
import MessagePanne from "./MessagePanne";
|
||||
|
||||
export default function ChatComponent({ clear = false, questions = [], form = false, useName, inputForm = null, guideWord, wsUrl, onBeforSend, loadMore = () => { } }) {
|
||||
|
||||
export default function ChatComponent({ clear = false, questions = [], form = false, useName, inputForm = null, guideWord, wsUrl, onBeforSend, type, loadMore = () => { } }) {
|
||||
return <div className="relative h-full">
|
||||
<MessagePanne useName={useName} guideWord={guideWord} loadMore={loadMore}></MessagePanne>
|
||||
<MessagePanne useName={useName} guideWord={guideWord} loadMore={loadMore} flow_type={type}></MessagePanne>
|
||||
<ChatInput clear={clear} questions={questions} form={form} wsUrl={wsUrl} inputForm={inputForm} onBeforSend={onBeforSend} ></ChatInput>
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -26,8 +26,8 @@ type State = {
|
||||
}
|
||||
|
||||
type Actions = {
|
||||
loadHistoryMsg: (flowid: string, chatId: string) => Promise<void>;
|
||||
loadMoreHistoryMsg: (flowid: string) => Promise<void>;
|
||||
loadHistoryMsg: (flowid: string, chatId: string, flow_type: string) => Promise<void>;
|
||||
loadMoreHistoryMsg: (flowid: string, flow_type: string) => Promise<void>;
|
||||
destory: () => void;
|
||||
createSendMsg: (inputs: any, inputKey?: string) => void;
|
||||
createWsMsg: (data: any) => void;
|
||||
@@ -50,9 +50,10 @@ const handleHistoryMsg = (data: any[]): ChatMessageType[] => {
|
||||
.replace(/'/g, '"'); // 将单引号替换为双引号
|
||||
return data.map(item => {
|
||||
// let count = 0
|
||||
let { message, files, is_bot, intermediate_steps, ...other } = item
|
||||
let { message,reasoning_content, files, is_bot, intermediate_steps, ...other } = item
|
||||
try {
|
||||
message = message && message[0] === '{' ? JSON.parse(message) : message || ''
|
||||
reasoning_content = reasoning_content && reasoning_content[0] === '{' ? JSON.parse(reasoning_content) : reasoning_content || ''
|
||||
} catch (e) {
|
||||
// 未考虑的情况暂不处理
|
||||
console.error('消息 to JSON error :>> ', e);
|
||||
@@ -64,6 +65,7 @@ const handleHistoryMsg = (data: any[]): ChatMessageType[] => {
|
||||
files: files ? JSON.parse(files) : [],
|
||||
isSend: !is_bot,
|
||||
message,
|
||||
reasoning_content,
|
||||
thought: intermediate_steps,
|
||||
noAccess: true
|
||||
}
|
||||
@@ -81,20 +83,20 @@ export const useMessageStore = create<State & Actions>((set, get) => ({
|
||||
setShowGuideQuestion(bln: boolean) {
|
||||
set({ showGuideQuestion: bln })
|
||||
},
|
||||
async loadHistoryMsg(flowid, chatId) {
|
||||
const res = await getChatHistory(flowid, chatId, 30, 0)
|
||||
async loadHistoryMsg(flowid, chatId, flow_type) {
|
||||
const res = await getChatHistory(flowid, chatId, 30, 0, flow_type)
|
||||
const msgs = handleHistoryMsg(res)
|
||||
currentChatId = chatId
|
||||
set({ historyEnd: false, messages: msgs.reverse() })
|
||||
},
|
||||
async loadMoreHistoryMsg(flowid) {
|
||||
async loadMoreHistoryMsg(flowid, flow_type) {
|
||||
if (get().running) return // 会话进行中禁止加载more历史
|
||||
if (get().historyEnd) return // 没有更多历史纪录
|
||||
const chatId = get().chatId
|
||||
const prevMsgs = get().messages
|
||||
// 最后一条消息id不存在,忽略 loadmore
|
||||
if (!prevMsgs[0]?.id) return
|
||||
const res = await getChatHistory(flowid, chatId, 10, prevMsgs[0]?.id || 0)
|
||||
const res = await getChatHistory(flowid, chatId, 10, prevMsgs[0]?.id || 0, flow_type)
|
||||
// 过滤非同一会话消息
|
||||
if (res[0]?.chat_id !== currentChatId) {
|
||||
return console.warn('loadMoreHistoryMsg chatId not match, ignore')
|
||||
@@ -134,6 +136,7 @@ export const useMessageStore = create<State & Actions>((set, get) => ({
|
||||
newChat.push({
|
||||
isSend: false,
|
||||
message: runLogsTypes.includes(data.category) ? JSON.parse(data.message) : '',
|
||||
reasoning_content: runLogsTypes.includes(data.category) ? JSON.parse(data.reasoning_content) : '',
|
||||
chatKey: '',
|
||||
thought: data.intermediate_steps || '',
|
||||
category: data.category || '',
|
||||
@@ -165,6 +168,7 @@ export const useMessageStore = create<State & Actions>((set, get) => ({
|
||||
...wsdata,
|
||||
id: isRunLog ? wsdata.extra : wsdata.messageId, // 每条消息必唯一
|
||||
message: isRunLog ? JSON.parse(wsdata.message) : currentMessage.message + wsdata.message,
|
||||
reasoning_content: isRunLog ? JSON.parse(wsdata.reasoning_content) : currentMessage.reasoning_content + wsdata.reasoning_content,
|
||||
thought: currentMessage.thought + (wsdata.thought ? `${wsdata.thought}\n` : ''),
|
||||
files: wsdata.files || null,
|
||||
category: wsdata.category || '',
|
||||
@@ -172,6 +176,7 @@ export const useMessageStore = create<State & Actions>((set, get) => ({
|
||||
}
|
||||
|
||||
messages[currentMessageIndex] = newCurrentMessage
|
||||
// console.log(messages,currentMessageIndex,newCurrentMessage)
|
||||
// 会话特殊处理,兼容后端的缺陷
|
||||
if (!isRunLog) {
|
||||
// start - end 之间没有内容删除load
|
||||
@@ -242,6 +247,7 @@ const bsMsgItem = {
|
||||
id: Math.random() * 1000000,
|
||||
isSend: false,
|
||||
message: '',
|
||||
reasoning_content: '',
|
||||
chatKey: '',
|
||||
thought: '',
|
||||
category: '',
|
||||
|
||||
@@ -5,10 +5,20 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SearchInput } from "../../bs-ui/input";
|
||||
import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "../../bs-ui/sheet";
|
||||
import CardComponent from "../cardComponent";
|
||||
import CardComponent, { TitleIconBg } from "../cardComponent";
|
||||
import { SkillIcon } from "@/components/bs-icons/skill";
|
||||
import { AssistantIcon } from "@/components/bs-icons/assistant";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import borderR from "../../../assets/npc/border-r.png";
|
||||
import { SpotlightCard } from "@lobehub/ui";
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import robot from "../../../assets/robot.png";
|
||||
import robot2 from "../../../assets/robot2.png";
|
||||
import robot3 from "../../../assets/robot3.png";
|
||||
import zidingyi1 from "../../../assets/npc/zidingyi1.png";
|
||||
import zidingyi2 from "../../../assets/npc/zidingyi2.png";
|
||||
import npcIcon from "../../../assets/npc/npcIcon.png";
|
||||
import nengliIcon from "../../../assets/npc/nengliIcon.png";
|
||||
|
||||
export default function SkillChatSheet({ children, onSelect }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -32,19 +42,68 @@ export default function SkillChatSheet({ children, onSelect }) {
|
||||
return allDataRef.current.filter(el => el.name.toLowerCase().includes(keyword.toLowerCase()))
|
||||
}, [keyword])
|
||||
|
||||
const render = (item: any) => (
|
||||
<Flexbox align={'flex-start'} className={`selectNpcFlexbox relative`} onClick={() => { onSelect(item); setOpen(false) }}>
|
||||
{/* <Avatar size={24} src={item.favicon} style={{ flex: 'none' }} /> */}
|
||||
{/* <Flexbox>
|
||||
<div style={{ fontSize: 15, fontWeight: 600 }}>{item.name}</div>
|
||||
<div style={{ opacity: 0.6 }}>{item.name}</div>
|
||||
</Flexbox> */}
|
||||
{/* <Card key={item.id} className="w-[300px] overflow-hidden cursor-pointer" onClick={() => onSelect(item)}>
|
||||
<CardHeader>
|
||||
<CardTitle className=" flex items-center gap-2">
|
||||
<div className={"rounded-full w-[30px] h-[30px] " + gradients[parseInt(item.id, 16) % gradients.length]}></div>
|
||||
<span>{item.name}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="">{item.description}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card> */}
|
||||
<div className="npcInfoItemBg">
|
||||
<span>
|
||||
<span>
|
||||
<div>
|
||||
<TitleIconBg className="w-[160px] h-[160px] min-w-[160px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : (item.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{/* {(item.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || item.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[42px]" alt=""/>}
|
||||
{item.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[42px]" alt=""/>}
|
||||
{(item.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && item.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && item.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[42px]" alt=""/>} */}
|
||||
{/* <img src={robot} className="w-[42px]" alt=""/> */}
|
||||
<TitleIconBg className="w-[40px] h-[40px] min-w-[40px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : (item.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
|
||||
<div>
|
||||
<p>{item.name}</p>
|
||||
<div>
|
||||
{/* <div>绘画类</div>
|
||||
<div>绘画类</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-[10px] test-[13px]">{item.desc}</p>
|
||||
<div className={`absolute right-0 top-0 w-[41px] h-[16px] flex justify-center items-center text-[9px] ${item.flow_type === 'flow' ? 'text-[#333333] bg-[#FFD54C]' : 'text-[#FFFFFF] bg-[#2586FF]'}`} style={{borderRadius:"0px 10px 0px 7px",fontWeight:"bold"}}>
|
||||
{item.flow_type === 'flow' ? '能力' : 'NPC'}
|
||||
</div>
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
return <Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
{children}
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:min-w-[966px] bg-gray-100">
|
||||
<SheetContent className="sm:min-w-[1000px] bg-[#1A1A1A]">
|
||||
<div className="flex h-full" onClick={e => e.stopPropagation()}>
|
||||
<div className="w-fit p-6">
|
||||
<SheetTitle>{t('chat.dialogueSelection')}</SheetTitle>
|
||||
<SheetDescription>{t('chat.chooseSkillOrAssistant')}</SheetDescription>
|
||||
<SearchInput value={keyword} placeholder={t('chat.search')} className="my-6" onChange={(e) => setKeyword(e.target.value)} />
|
||||
<div className="xinDuiHua-boxR">
|
||||
{/* <img src={borderR} className="w-[30px] h-[100%]" alt="" /> */}
|
||||
</div>
|
||||
<div className="flex-1 min-w-[696px] bg-[#fff] p-5 pt-12 h-full flex flex-wrap gap-1.5 overflow-y-auto scrollbar-hide content-start">
|
||||
{
|
||||
<div className="w-[280px] p-6">
|
||||
<SheetTitle>选择对话</SheetTitle>
|
||||
<SheetDescription className="text-[#999999]">选择一个您想使用的上线NPC或能力</SheetDescription>
|
||||
<SearchInput value={keyword} placeholder="搜索" className="my-6" onChange={(e) => setKeyword(e.target.value)} />
|
||||
</div>
|
||||
<div className="w-[690px] overflow-y-auto bg-[#000000] scrollbar-hide skillSheet">
|
||||
{/* {
|
||||
options.length ? options.map((flow, i) => (
|
||||
<CardComponent key={i}
|
||||
id={i + 1}
|
||||
@@ -55,7 +114,7 @@ export default function SkillChatSheet({ children, onSelect }) {
|
||||
icon={flow.flow_type === 'flow' ? SkillIcon : AssistantIcon}
|
||||
footer={
|
||||
<Badge className={`absolute right-0 bottom-0 rounded-none rounded-br-md ${flow.flow_type === 'flow' && 'bg-gray-950'}`}>
|
||||
{flow.flow_type === 'flow' ? '技能' : '助手'}
|
||||
{flow.flow_type === 'flow' ? '技能' : 'NPC'}
|
||||
</Badge>
|
||||
}
|
||||
onClick={() => { onSelect(flow); setOpen(false) }}
|
||||
@@ -64,7 +123,8 @@ export default function SkillChatSheet({ children, onSelect }) {
|
||||
<p className="text-sm text-muted-foreground mb-3">{t('build.empty')}</p>
|
||||
<Button className="w-[200px]" onClick={() => navigate('/build/assist')}>{t('build.onlineSA')}</Button>
|
||||
</div>
|
||||
}
|
||||
} */}
|
||||
<SpotlightCard items={options} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/>
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
|
||||
@@ -10,8 +10,18 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "../../bs-ui/sheet";
|
||||
import CardComponent from "../cardComponent";
|
||||
import CardComponent, { TitleIconBg } from "../cardComponent";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SpotlightCard } from "@lobehub/ui";
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import robot from "../../../assets/robot.png";
|
||||
import robot2 from "../../../assets/robot2.png";
|
||||
import robot3 from "../../../assets/robot3.png";
|
||||
import zidingyi1 from "../../../assets/npc/zidingyi1.png";
|
||||
import zidingyi2 from "../../../assets/npc/zidingyi2.png";
|
||||
import borderR from "../../../assets/npc/border-r.png";
|
||||
import npcIcon from "../../../assets/npc/npcIcon.png";
|
||||
import nengliIcon from "../../../assets/npc/nengliIcon.png";
|
||||
|
||||
export default function SkillSheet({ select, children, onSelect }) {
|
||||
const [keyword, setKeyword] = useState("");
|
||||
@@ -24,7 +34,6 @@ export default function SkillSheet({ select, children, onSelect }) {
|
||||
return res;
|
||||
})
|
||||
);
|
||||
|
||||
const handleSearch = (e) => {
|
||||
const { value } = e.target;
|
||||
setKeyword(value);
|
||||
@@ -37,12 +46,70 @@ export default function SkillSheet({ select, children, onSelect }) {
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
||||
const render = (item: any) => (
|
||||
<Flexbox align={'flex-start'} className={`selectNpcFlexbox`}>
|
||||
{/* <Avatar size={24} src={item.favicon} style={{ flex: 'none' }} /> */}
|
||||
{/* <Flexbox>
|
||||
<div style={{ fontSize: 15, fontWeight: 600 }}>{item.name}</div>
|
||||
<div style={{ opacity: 0.6 }}>{item.name}</div>
|
||||
</Flexbox> */}
|
||||
{/* <Card key={item.id} className="w-[300px] overflow-hidden cursor-pointer" onClick={() => onSelect(item)}>
|
||||
<CardHeader>
|
||||
<CardTitle className=" flex items-center gap-2">
|
||||
<div className={"rounded-full w-[30px] h-[30px] " + gradients[parseInt(item.id, 16) % gradients.length]}></div>
|
||||
<span>{item.name}</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="">{item.description}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card> */}
|
||||
<div className="npcInfoItemBg">
|
||||
<span>
|
||||
<span>
|
||||
<div>
|
||||
<TitleIconBg className="w-[160px] h-[160px] min-w-[160px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : (item.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<TitleIconBg className="w-[40px] h-[40px] min-w-[40px]" img={item.avatar_img} id={item.avatar_color ? item.avatar_color : item.id} ><img src={item.avatar_img ? item.avatar_img : (item.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
|
||||
<div>
|
||||
<p>{item.name}</p>
|
||||
<div>
|
||||
{/* <div>绘画类</div>
|
||||
<div>绘画类</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-[10px] test-[13px]">{item.description}</p>
|
||||
<div className="flex justify-end mb-[14px] mr-[14px] mt-[14px]">
|
||||
{select.some((_) => _.id === item.id) ? (
|
||||
<Button size="sm" className="h-6 bg-[#FFD025]" disabled>
|
||||
{t("build.added")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-6 bg-[#FFD025]"
|
||||
onClick={() => onSelect(item)}
|
||||
>
|
||||
{t("build.add")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>{children}</SheetTrigger>
|
||||
<SheetContent className="bg-gray-100 sm:min-w-[966px]">
|
||||
<SheetContent className="bg-[#1A1A1A] sm:min-w-[1000px]">
|
||||
<div className="flex h-full" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="w-fit p-6">
|
||||
<div className="xinDuiHua-boxR">
|
||||
{/* <img src={borderR} className="w-[30px] h-[100%]" alt="" /> */}
|
||||
</div>
|
||||
<div className="w-[280px] p-6">
|
||||
<SheetTitle>{t("build.addSkill")}</SheetTitle>
|
||||
<SearchInput
|
||||
value={keyword}
|
||||
@@ -50,47 +117,47 @@ export default function SkillSheet({ select, children, onSelect }) {
|
||||
className="my-6"
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<Button className="w-full" onClick={toCreateFlow}>
|
||||
{/* <Button className="w-full" onClick={toCreateFlow}>
|
||||
{t("build.createSkill")}
|
||||
</Button>
|
||||
</Button> */}
|
||||
<div className="w-[244px] h-[34px] flex items-center justify-center bg-[#FFD025] text-[#000] cursor-pointer" style={{borderRadius:"34px"}} onClick={toCreateFlow}>{t("build.createSkill")}</div>
|
||||
</div>
|
||||
<div className="flex h-full min-w-[696px] flex-1 flex-wrap content-start gap-1.5 overflow-y-auto bg-[#fff] p-5 pt-12 scrollbar-hide">
|
||||
<div className="w-[690px] overflow-y-auto bg-[#000000] scrollbar-hide skillSheet">
|
||||
{onlineFlows[0] ? (
|
||||
onlineFlows.map((flow, i) => (
|
||||
<CardComponent
|
||||
key={i}
|
||||
id={i + 1}
|
||||
data={flow}
|
||||
title={flow.name}
|
||||
description={flow.description}
|
||||
type="sheet"
|
||||
footer={
|
||||
<div className="flex justify-end">
|
||||
{select.some((_) => _.id === flow.id) ? (
|
||||
<Button size="sm" className="h-6" disabled>
|
||||
{t("build.added")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-6"
|
||||
onClick={() => onSelect(flow)}
|
||||
>
|
||||
{t("build.add")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
))
|
||||
// onlineFlows.map((flow, i) => (
|
||||
// <CardComponent
|
||||
// key={i}
|
||||
// id={i + 1}
|
||||
// data={flow}
|
||||
// title={flow.name}
|
||||
// description={flow.description}
|
||||
// type="sheet"
|
||||
// footer={
|
||||
// <div className="flex justify-end">
|
||||
// {select.some((_) => _.id === flow.id) ? (
|
||||
// <Button size="sm" className="h-6" disabled>
|
||||
// {t("build.added")}
|
||||
// </Button>
|
||||
// ) : (
|
||||
// <Button
|
||||
// size="sm"
|
||||
// className="h-6"
|
||||
// onClick={() => onSelect(flow)}
|
||||
// >
|
||||
// {t("build.add")}
|
||||
// </Button>
|
||||
// )}
|
||||
// </div>
|
||||
// }
|
||||
// />
|
||||
// ))
|
||||
<SpotlightCard items={onlineFlows} renderItem={render} className="mt-[14px] skillSheetSpotlightCard"/>
|
||||
) : (
|
||||
<div className="flex w-full flex-col items-center justify-center pt-40">
|
||||
<p className="mb-3 text-sm text-muted-foreground">
|
||||
<p className="mb-3 text-sm text-[#fff]">
|
||||
{t("build.empty")}
|
||||
</p>
|
||||
<Button className="w-[200px]" onClick={toCreateFlow}>
|
||||
{t("build.createSkill")}
|
||||
</Button>
|
||||
<div className="w-[200px] h-[34px] flex items-center justify-center bg-[#FFD025] text-[#000] cursor-pointer" style={{borderRadius:"34px"}} onClick={toCreateFlow}>{t("build.createSkill")}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,11 @@ import { useTranslation } from "react-i18next";
|
||||
import { PersonIcon, StarFilledIcon } from "@radix-ui/react-icons";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import sousuo from "../../../assets/npc/sousuo.png";
|
||||
import gongjuAdd from "../../../assets/npc/gongjuAdd.png";
|
||||
import gongjuIcon from "../../../assets/npc/gongjuIcon.png";
|
||||
import gongjuIcon1 from "../../../assets/npc/gongjuIcon1.png";
|
||||
import borderR from "../../../assets/npc/border-r.png";
|
||||
|
||||
export default function ToolsSheet({ select, onSelect, children }) {
|
||||
const { t } = useTranslation()
|
||||
@@ -34,35 +39,51 @@ export default function ToolsSheet({ select, onSelect, children }) {
|
||||
<SheetTrigger asChild>
|
||||
{children}
|
||||
</SheetTrigger>
|
||||
<SheetContent className="w-[1000px] sm:max-w-[1000px] bg-gray-100">
|
||||
<SheetContent className="w-[1000px] sm:max-w-[1000px] bg-[#1a1a1a]">
|
||||
<div className="flex h-full" onClick={e => e.stopPropagation()}>
|
||||
<div className="w-fit p-6">
|
||||
<div className="xinDuiHua-boxR">
|
||||
{/* <img src={borderR} className="w-[30px] h-[100%]" alt="" /> */}
|
||||
</div>
|
||||
<div className="w-[280px] p-6">
|
||||
<SheetTitle>{t('build.addTool')}</SheetTitle>
|
||||
<SearchInput placeholder={t('build.search')} className="mt-6" onChange={(e) => setKeyword(e.target.value)} />
|
||||
<Button
|
||||
<div className="relative mt-[14px]">
|
||||
<img src={sousuo} className="absolute w-[14px] left-[14px] top-[10px]" alt="" />
|
||||
<input placeholder="搜索"
|
||||
className="w-[237px] h-[34px] bg-[#1A1A1A] text-[#fff] pl-[40px]"
|
||||
style={{borderRadius:"17px",outline:"none"}}
|
||||
onChange={(e) => setKeyword(e.target.value)} type="text" />
|
||||
</div>
|
||||
{/* <SearchInput placeholder={t('build.search')} className="mt-6" onChange={(e) => setKeyword(e.target.value)} /> */}
|
||||
{/* <Button
|
||||
className="mt-4 w-full"
|
||||
onClick={() => window.open("/build/tools")}
|
||||
>
|
||||
{t('create')}{t("tools.createCustomTool")}
|
||||
</Button>
|
||||
</Button> */}
|
||||
<div className="w-[237px] h-[27px] bg-[#FFD025] mt-[14px] flex justify-center items-center border-radius-14 cursor-pointer" onClick={() => window.open("/build/tools")}>
|
||||
<img src={gongjuAdd} className="w-[14px]" alt="" />
|
||||
<span className="text-[#333333] ml-[12px]">自定义工具</span>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:bg-muted-foreground/10 transition-all duration-200 ${type === 'default' && 'bg-muted-foreground/10'}`}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:bg-muted-foreground/10 transition-all duration-200 ${type === 'default' && 'bg-[#2A271D] text-[#FFD54C]'}`}
|
||||
onClick={() => setType('default')}
|
||||
>
|
||||
<PersonIcon />
|
||||
<span>{t('tools.builtinTools')}</span>
|
||||
{/* <PersonIcon /> */}
|
||||
{type === "default" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
<span className={`ml-[8px] text-[#999999] ${type === "default" && "text-[#FFD025]"}`}>内置工具</span>
|
||||
</div>
|
||||
<div
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:bg-muted-foreground/10 transition-all duration-200 mt-1 ${type === 'custom' && 'bg-muted-foreground/10'}`}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:bg-muted-foreground/10 transition-all duration-200 mt-1 ${type === 'custom' && 'bg-[#2A271D] text-[#FFD54C]'}`}
|
||||
onClick={() => setType('custom')}
|
||||
>
|
||||
<StarFilledIcon />
|
||||
<span>{t('tools.customTools')}</span>
|
||||
{type === "custom" ? <img src={gongjuIcon1} className="w-[14px]" alt="" /> : <img src={gongjuIcon} className="w-[14px]" alt="" />}
|
||||
{/* <StarFilledIcon /> */}
|
||||
<span className={`ml-[8px] text-[#999999] ${type === "custom" && "text-[#FFD025]"}`}>自定义工具</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 bg-[#fff] p-5 pt-12 h-full overflow-auto scrollbar-hide">
|
||||
<div className="w-[690px] flex-1 bg-[#121212] p-5 pt-12 h-full overflow-auto scrollbar-hide">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{
|
||||
options.length ? options.map(el => (
|
||||
|
||||
@@ -5,6 +5,6 @@ export const WordIcon = forwardRef<
|
||||
SVGSVGElement & { className: any },
|
||||
React.PropsWithChildren<{ className?: string }>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const _className = 'transition text-gray-950 ' + (className || '')
|
||||
const _className = 'transition text-[#43AFD2] ' + (className || '')
|
||||
return <Word ref={ref} {...props} className={_className} />;
|
||||
});
|
||||
|
||||
BIN
src/components/bs-ui/.DS_Store
vendored
Normal file
@@ -32,7 +32,7 @@ const AccordionTrigger = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronRightIcon color="#111" className="mx-2 h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||
<ChevronRightIcon color="#fff" className="mx-2 h-4 w-4 shrink-0 text-[#666666] transition-transform duration-200" />
|
||||
{children}
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
|
||||
@@ -6,6 +6,7 @@ import i18next from "i18next"
|
||||
import { useRef, useState } from "react"
|
||||
import { TipIcon } from "@/components/bs-icons/tip"
|
||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||
import { createRoot } from "react-dom/client"
|
||||
|
||||
interface ConfirmParams {
|
||||
title?: string
|
||||
@@ -77,7 +78,9 @@ function ConfirmWrapper() {
|
||||
el.id = 'confirm-wrap'
|
||||
document.body.append(el)
|
||||
}
|
||||
ReactDOM.render(<ConfirmWrapper />, el);
|
||||
// ReactDOM.render(<ConfirmWrapper />, el);
|
||||
const root = createRoot(el);
|
||||
root.render(<ConfirmWrapper />);
|
||||
})();
|
||||
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@ export default function ActionButton({
|
||||
...props
|
||||
}) {
|
||||
|
||||
return <div className="flex items-center">
|
||||
return <div className="flex items-center bg-[#1A1A1A]">
|
||||
<>
|
||||
{buttonTipContent ? <TooltipProvider>
|
||||
<Tooltip delayDuration={delayDuration}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant={variant} className={`rounded-r-none ${className}`} {...props}>{children}</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-[#fff] text-gray-800">
|
||||
<TooltipContent className="bg-[#1A1A1A] text-[#CCCCCC]">
|
||||
{buttonTipContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -34,10 +34,11 @@ export default function ActionButton({
|
||||
<Button
|
||||
size="icon"
|
||||
variant={variant}
|
||||
className="rounded-l-none ml-[1px] [&[data-state=open]>svg]:rotate-180"
|
||||
className=" ml-[1px] [&[data-state=open]>svg]:rotate-180 bg-[#262626] w-[18px] h-[18px] mr-[5px]"
|
||||
style={{borderRadius:"3px"}}
|
||||
><CaretDownIcon /></Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 p-0" align={align}>
|
||||
<PopoverContent className="w-80 p-0 bg-[#1A1A1A]" align={align}>
|
||||
{dropDown}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -91,7 +91,7 @@ const ButtonNumber = React.forwardRef<HTMLButtonElement, {
|
||||
setValue(updateValue)
|
||||
onChange?.(updateValue)
|
||||
}
|
||||
return (<div className={cname("flex items-center border input-border bg-gray-50 rounded-md", className)}>
|
||||
return (<div className={cname("flex items-center border input-border bg-[#1a1a1a] rounded-md", className)}>
|
||||
<Button variant="ghost" size={size} disabled={value === min} onClick={valueReduce}>-</Button>
|
||||
<span className="min-w-10 block text-center">{value}</span>
|
||||
<Button variant="ghost" size={size} disabled={value === max} onClick={valueAdd}>+</Button>
|
||||
|
||||
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-5 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-[#262626] p-5 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -86,7 +86,7 @@ const DialogTitle = React.forwardRef<
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"text-base font-semibold leading-none tracking-tight",
|
||||
"text-base font-semibold leading-none tracking-tight text-[#FFFFFF]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -100,7 +100,7 @@ const DialogDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cname("text-sm text-muted-foreground", className)}
|
||||
className={cname("text-sm text-[#999]", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -3,6 +3,7 @@ import { cname } from "../utils"
|
||||
import { SearchIcon } from "../../bs-icons/search"
|
||||
import { generateUUID } from "../utils"
|
||||
import { MinusCircledIcon } from "@radix-ui/react-icons"
|
||||
import sousuo from "../../../assets/npc/sousuo1.png"
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> { }
|
||||
|
||||
@@ -12,7 +13,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cname(
|
||||
"flex h-9 w-full rounded-md border border-input bg-[#FAFBFC] px-3 py-1 text-sm text-[#111] shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex h-9 w-full rounded-md border border-input bg-[#FAFBFC] px-3 py-1 text-sm text-[#111] shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 ",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
@@ -27,8 +28,9 @@ Input.displayName = "Input"
|
||||
const SearchInput = React.forwardRef<HTMLInputElement, InputProps & { inputClassName?: string, iconClassName?: string }>(
|
||||
({ className, inputClassName, iconClassName, ...props }, ref) => {
|
||||
return <div className={cname("relative", className)}>
|
||||
<SearchIcon className={cname("h-5 w-5 absolute left-2 top-2", iconClassName)} />
|
||||
<Input type="text" ref={ref} className={cname("pl-8 bg-search-input", inputClassName)} {...props}></Input>
|
||||
{/* <SearchIcon className={cname("h-5 w-5 absolute left-2 top-2 text-[#666666]", iconClassName)} /> */}
|
||||
<img src={sousuo} alt="" className="w-[14px] absolute left-[14px] top-[10px]" />
|
||||
<Input type="text" ref={ref} className={cname("w-[244px] h-[34px] pl-[40px] npcInput3", inputClassName)} {...props}></Input>
|
||||
</div>
|
||||
}
|
||||
)
|
||||
@@ -113,7 +115,7 @@ const InputList = React.forwardRef<HTMLDivElement, InputProps & {
|
||||
<Input
|
||||
key={item.id}
|
||||
defaultValue={item.value}
|
||||
className={cname('pr-8', inputClassName)}
|
||||
className={cname('pr-8 npcInput2', inputClassName)}
|
||||
placeholder={props.placeholder || ''}
|
||||
onChange={(e) => handleChange(e.target.value, item.id, index)}
|
||||
onInput={(e) => {
|
||||
|
||||
@@ -28,13 +28,13 @@ const RadioGroupItem = React.forwardRef<
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"aspect-square h-4 w-4 rounded-full border-2 border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"aspect-square h-4 w-4 rounded-full border-2 border-[#FFD025] text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||
<span className="w-1.5 h-1.5 bg-primary rounded-full"></span>
|
||||
<span className="w-1.5 h-1.5 bg-[#FFD025] rounded-full"></span>
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ const SelectTrigger = React.forwardRef<
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"group flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-[#fcfdff] px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 data-[placeholder]:text-gray-400",
|
||||
"group flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-[#1a1a1a] px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 data-[placeholder]:text-gray-400",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -79,7 +79,7 @@ const SelectContent = React.forwardRef<
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-[#DEE3EF] bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md box-shadow border-[#DEE3EF] bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
@@ -124,7 +124,7 @@ const SelectItem = React.forwardRef<
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-[#EBF0FF] focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-[#1a1a1a] focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { SearchInput } from "../input"
|
||||
const MultiItem = ({ active, children, value, onClick }) => {
|
||||
|
||||
return <div key={value}
|
||||
className={`relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 mb-1 text-sm outline-none hover:bg-[#EBF0FF] hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 break-all
|
||||
${active && 'bg-[#EBF0FF]'}`}
|
||||
className={`relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 mb-1 text-sm outline-none hover:bg-[#1a1a1a] hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 break-all
|
||||
${active && 'bg-[#1a1a1a]'}`}
|
||||
onClick={() => { onClick(value) }}
|
||||
>
|
||||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
@@ -93,7 +93,7 @@ const MultiSelect = ({
|
||||
setOptionFilter(newValues)
|
||||
}
|
||||
return <Select {...props} required onOpenChange={(e) => !e && setOptionFilter(options)}>
|
||||
<SelectTrigger className="mt-2 h-auto">
|
||||
<SelectTrigger className="mt-2 h-auto bg-[#1a1a1a]">
|
||||
{
|
||||
values.length
|
||||
? <div className="flex flex-wrap">
|
||||
|
||||
@@ -40,7 +40,7 @@ const sheetVariants = cva(
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
"inset-y-0 right-0 h-full w-3/4 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -17,8 +17,8 @@ const Slider = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-[#997e1f]">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-[#ffd025]" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
|
||||
@@ -87,7 +87,8 @@ const TableCell = React.forwardRef<
|
||||
<td
|
||||
ref={ref}
|
||||
className={cname(
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] bg-[#FBFBFB] first:rounded-l-md last:rounded-r-md group-odd:bg-[#f4f5f8] group-hover:bg-[#ebf0ff]",
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] bg-[#2B2B2B] first:rounded-l-md last:rounded-r-md group-odd:bg-[#1a1a1a]",
|
||||
// group-hover:bg-[#1a1a1a]
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -32,10 +32,10 @@ const toastVariants = cva(
|
||||
error: "error border-[#D8341E] bg-[#FFF2F0] self-end",
|
||||
},
|
||||
message: {
|
||||
info: "shadow-xl bg-[#fff] self-center",
|
||||
success: "shadow-xl bg-[#fff] self-center",
|
||||
warning: "shadow-xl bg-[#fff] self-center",
|
||||
error: "shadow-xl bg-[#fff] self-center",
|
||||
info: "shadow-xl bg-[#333] self-center",
|
||||
success: "shadow-xl bg-[#333] self-center",
|
||||
warning: "shadow-xl bg-[#333] self-center",
|
||||
error: "shadow-xl bg-[#333] self-center",
|
||||
}
|
||||
},
|
||||
defaultVariants: {},
|
||||
@@ -99,7 +99,7 @@ const ToastTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cname("text-sm font-semibold [&+div]:text-xs group-[.info]:text-[#024FE5] group-[.success]:text-[#0BA95D] group-[.warning]:text-[#EA991F] group-[.error]:text-[#D8341E]", className)}
|
||||
className={cname("text-sm font-semibold text-[#fff] [&+div]:text-xs group-[.info]:text-[#024FE5] group-[.success]:text-[#fff] group-[.warning]:text-[#EA991F] group-[.error]:text-[#D8341E]", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -111,7 +111,7 @@ const ToastDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cname("text-sm opacity-90", className)}
|
||||
className={cname("text-sm text-[#999]", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -18,7 +18,7 @@ const TooltipContent = React.forwardRef<
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cname(
|
||||
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
"z-50 overflow-hidden rounded-md bg-[#1a1a1a] px-3 py-1.5 text-xs text-[#ccc] animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -52,13 +52,13 @@ export default function BuildTrigger({
|
||||
/**
|
||||
* 拦截flow,过滤node数据,去除groupNode
|
||||
*/
|
||||
try {
|
||||
flow.data.nodes = flow?.data?.nodes?.filter(node => {
|
||||
return node.id.indexOf('groupNode') < 0;
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
// try {
|
||||
// flow.data.nodes = flow?.data?.nodes?.filter(node => {
|
||||
// return node.id.indexOf('groupNode') < 0;
|
||||
// })
|
||||
// } catch (e) {
|
||||
// console.log(e)
|
||||
// }
|
||||
|
||||
const allNodesValid = await streamNodeData(flow);
|
||||
await enforceMinimumLoadingTime(startTime, minimumLoadingTime); // 200内完成streamNodeData,阻塞剩余时间;否则不阻塞(最大等待200)
|
||||
@@ -79,7 +79,7 @@ export default function BuildTrigger({
|
||||
}
|
||||
async function streamNodeData(flow: FlowType) {
|
||||
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
||||
const { flowId } = await postBuildInit(flow);
|
||||
const { flowId } = await postBuildInit({ flow });
|
||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||
let validationResults = [];
|
||||
let finished = false;
|
||||
|
||||
@@ -107,10 +107,10 @@ export default function InputFileComponent({
|
||||
onClick={handleButtonClick}
|
||||
className={
|
||||
editNode
|
||||
? "input-edit-node input-dialog text-muted-foreground"
|
||||
? "input-edit-node input-dialog text-[#999]"
|
||||
: disabled
|
||||
? "input-disable input-dialog input-primary"
|
||||
: "input-dialog input-primary text-muted-foreground"
|
||||
: "input-dialog input-primary text-[#999]"
|
||||
}
|
||||
>
|
||||
{myValue !== "" ? myValue : placeholder}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ReactNode, createContext, useContext, useState } from "react";
|
||||
import { addEdge } from "reactflow";
|
||||
import { updateFlowApi } from "../controllers/API/flow";
|
||||
import { APIClassType, APITemplateType } from "../types/api";
|
||||
import { FlowType, NodeType } from "../types/flow";
|
||||
import { FlowType, FlowVersionItem, NodeType } from "../types/flow";
|
||||
import { TabsContextType, TabsState } from "../types/tabs";
|
||||
import { generateUUID, updateTemplate } from "../utils";
|
||||
import { alertContext } from "./alertContext";
|
||||
@@ -28,6 +28,8 @@ const TabsContextInitialValue: TabsContextType = {
|
||||
selection: { nodes: any; edges: any },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||
) => { },
|
||||
version: null,
|
||||
setVersion: (version: FlowVersionItem | null) => ""
|
||||
};
|
||||
|
||||
export const TabsContext = createContext<TabsContextType>(
|
||||
@@ -36,6 +38,7 @@ export const TabsContext = createContext<TabsContextType>(
|
||||
|
||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
const [flow, setFlow] = useState<FlowType>(null);
|
||||
const [version, setVersion] = useState<FlowVersionItem | null>(null);
|
||||
// flowid: formKeysData
|
||||
const [tabsState, setTabsState] = useState<TabsState>({});
|
||||
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
|
||||
@@ -289,6 +292,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
});
|
||||
}
|
||||
|
||||
// 上线版本的版本 id
|
||||
const [onlineVid, setOnlineVid] = useState(0);
|
||||
const updateOnlineVid = (vid: number) => {
|
||||
setOnlineVid(flow.status === 2 ? vid : 0);
|
||||
}
|
||||
|
||||
return (
|
||||
<TabsContext.Provider
|
||||
value={{
|
||||
@@ -313,7 +322,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
getNodeId,
|
||||
tabsState,
|
||||
setTabsState,
|
||||
paste
|
||||
paste,
|
||||
version,
|
||||
setVersion,
|
||||
isOnlineVersion: () => version.id === onlineVid,
|
||||
updateOnlineVid
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -22,8 +22,8 @@ export const getAssistantsApi = async (page, limit, name): Promise<AssistantItem
|
||||
};
|
||||
|
||||
// 创建助手
|
||||
export const createAssistantsApi = async (name, prompt) => {
|
||||
return await axios.post(`/api/v1/assistant`, { name, prompt, logo: '' })
|
||||
export const createAssistantsApi = async (name, prompt, avatar_img, avatar_color) => {
|
||||
return await axios.post(`/api/v1/assistant`, { name, prompt, logo: '', avatar_img, avatar_color })
|
||||
};
|
||||
|
||||
// 获取助手详情
|
||||
|
||||
@@ -172,7 +172,9 @@ export async function deleteFlowFromDatabase(flowId: string) {
|
||||
export const createCustomFlowApi = async (params: {
|
||||
name: string,
|
||||
description: string,
|
||||
guide_word: string
|
||||
guide_word: string,
|
||||
avatar_img: string,
|
||||
avatar_color: string
|
||||
}, userName: string) => {
|
||||
const response: FlowType = await axios.post("/api/v1/flows/", {
|
||||
...params,
|
||||
@@ -201,7 +203,9 @@ export async function updateFlowApi(
|
||||
name: updatedFlow.name,
|
||||
data: updatedFlow.data,
|
||||
description: updatedFlow.description,
|
||||
guide_word: updatedFlow.guide_word
|
||||
guide_word: updatedFlow.guide_word,
|
||||
avatar_img: updatedFlow.avatar_img,
|
||||
avatar_color: updatedFlow.avatar_color,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -224,7 +228,7 @@ export async function updataOnlineState(id, updatedFlow, open) {
|
||||
* @throws .
|
||||
*/
|
||||
export async function readOnlineFlows(page: number = 1, searchKey: string = "") {
|
||||
const { data, total }: { data: any, total: number } = await axios.get(`/api/v1/flows/?page_num=${page}&page_size=${100}&status=2&name=${searchKey}`);
|
||||
const data: { data: any, total: number } = await axios.get(`/api/v1/flows/?page_num=${page}&page_size=${100}&status=2&name=${searchKey}`);
|
||||
return data;
|
||||
}
|
||||
// export async function readOnlineFlows(page: number = 1) {
|
||||
|
||||
@@ -107,9 +107,9 @@ export function updateTempApi(temp_id, data) {
|
||||
* 获取知识库列表
|
||||
*
|
||||
*/
|
||||
export async function readFileLibDatabase(page = 1, pageSize = 40, name = '', parentId = '',type = '') {
|
||||
export async function readFileLibDatabase(page = 1, pageSize = 40, name = '') {
|
||||
try {
|
||||
const response: { data: any[], total: number } = await axios.get(`/api/v1/knowledge/?page_num=${page}&page_size=${pageSize}&name=${name}&parent_id=${parentId}&type=${type}`);
|
||||
const response: { data: any[], total: number } = await axios.get(`/api/v1/knowledge/?page_num=${page}&page_size=${pageSize}&name=${name}`);
|
||||
// const { data, total } = response
|
||||
return response;
|
||||
} catch (error) {
|
||||
@@ -143,6 +143,13 @@ export async function uploadLibFile(data, config) {
|
||||
return await axios.post(`/api/v1/knowledge/upload`, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传能力头像
|
||||
*/
|
||||
export async function uploadNpcHeaderLibFile(data, config) {
|
||||
return await axios.post(`/api/v1/knowledge/upload_npc_header`, data, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定上传文件
|
||||
* file_path knowledge_id chunck_size
|
||||
@@ -167,6 +174,23 @@ export async function updateFileLib(data) {
|
||||
return await axios.put(`/api/v1/knowledge/update`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改支持库
|
||||
*
|
||||
*/
|
||||
export async function getFileLibById(id) {
|
||||
return await axios.get(`/api/v1/knowledge/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 修改支持库文件
|
||||
*
|
||||
*/
|
||||
export async function updateFile(data) {
|
||||
return await axios.put(`/api/v1/knowledge/file/update`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除支持库
|
||||
*
|
||||
@@ -276,8 +300,37 @@ export const deleteChatApi = (chatId) => {
|
||||
* @param id flow_id chat_id - .
|
||||
* @returns {Promise<any>} his data.
|
||||
*/
|
||||
export async function getChatHistory(flowId: string, chatId: string, pageSize: number, id?: number): Promise<any[]> {
|
||||
return await axios.get(`/api/v1/chat/history?flow_id=${flowId}&chat_id=${chatId}&page_size=${pageSize}&id=${id || ''}`);
|
||||
export interface MessageDB {
|
||||
/** 场景 */
|
||||
category: string;
|
||||
chat_id: string;
|
||||
create_time: string;
|
||||
extra: string;
|
||||
/** 文件列表 */
|
||||
files: string;
|
||||
flow_id: string;
|
||||
id: number;
|
||||
/** 日志 */
|
||||
intermediate_steps: string;
|
||||
/** 机器人回复 */
|
||||
is_bot: boolean;
|
||||
/** 已点赞 */
|
||||
liked: number;
|
||||
/** 消息内容 */
|
||||
message: string;
|
||||
receiver: null;
|
||||
remark: null;
|
||||
sender: string;
|
||||
solved: number;
|
||||
/** 有溯源 */
|
||||
source: number;
|
||||
type: string;
|
||||
update_time: string;
|
||||
user_id: number;
|
||||
}
|
||||
|
||||
export async function getChatHistory(flowId: string, chatId: string, pageSize: number, id: number, flow_type: string): Promise<MessageDB[]> {
|
||||
return await axios.get(`/api/v1/chat/history?flow_id=${flowId}&chat_id=${chatId}&page_size=${pageSize}&id=${id || ''}&flow_type=${flow_type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,9 +364,11 @@ export async function getVersion() {
|
||||
*
|
||||
*/
|
||||
export async function getBuildStatus(
|
||||
flowId: string
|
||||
flowId: string,
|
||||
versionId?: number
|
||||
): Promise<BuildStatusTypeAPI> {
|
||||
return await axios.get(`/api/v1/build/${flowId}/status`);
|
||||
const qstr = versionId ? `?version_id=${versionId}` : "";
|
||||
return await axios.get(`/api/v1/build/${flowId}/status${qstr}`);
|
||||
}
|
||||
|
||||
//docs for postbuildinit
|
||||
@@ -323,11 +378,14 @@ export async function getBuildStatus(
|
||||
* @returns {Promise<InitTypeAPI>} A promise that resolves to an AxiosResponse containing the build status.
|
||||
*
|
||||
*/
|
||||
export async function postBuildInit(
|
||||
flow: FlowType,
|
||||
export async function postBuildInit(data: {
|
||||
flow: FlowType
|
||||
chatId?: string
|
||||
): Promise<any> {
|
||||
return await axios.post(`/api/v1/build/init/${flow.id}`, chatId ? { chat_id: chatId } : flow);
|
||||
versionId?: number
|
||||
}): Promise<any> {
|
||||
const { flow, chatId, versionId } = data;
|
||||
const qstr = versionId ? `?version_id=${versionId}` : ''
|
||||
return await axios.post(`/api/v1/build/init/${flow.id}${qstr}`, chatId ? { chat_id: chatId } : flow);
|
||||
}
|
||||
|
||||
// fetch(`/upload/${id}`, {
|
||||
|
||||
@@ -56,6 +56,12 @@ export async function setSysConfigApi(data) {
|
||||
export async function getRoleSkillsApi(params): Promise<{ data: any[], total: number }> {
|
||||
return await axios.get(`/api/v1/role_access/flow`, { params });
|
||||
}
|
||||
/**
|
||||
* 根据角色获取技能列表
|
||||
*/
|
||||
export async function getRoleAssistApi(params): Promise<{ data: any[], total: number }> {
|
||||
return await axios.get(`/api/v1/role_access/list_type`, { params });
|
||||
}
|
||||
/**
|
||||
* 根据角色获取知识库列表
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { toast } from "@/components/bs-ui/toast/use-toast";
|
||||
import axios from "axios";
|
||||
|
||||
import i18next from "i18next";
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
const customAxios = axios.create({
|
||||
// 配置
|
||||
});
|
||||
@@ -23,7 +23,12 @@ customAxios.interceptors.response.use(function (response) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
// app 弹窗
|
||||
window.errorAlerts([error.message])
|
||||
toast({
|
||||
title: `${i18next.t('prompt')}`,
|
||||
variant: 'error',
|
||||
description: error.message
|
||||
})
|
||||
// window.errorAlerts([error.message])
|
||||
return Promise.reject(null);
|
||||
})
|
||||
|
||||
@@ -38,7 +43,11 @@ export function captureAndAlertRequestErrorHoc(apiFunc, iocFunc?) {
|
||||
console.log('error :>> ', error);
|
||||
iocFunc?.(error)
|
||||
// 弹窗
|
||||
window.errorAlerts([error])
|
||||
toast({
|
||||
title: `${i18next.t('prompt')}`,
|
||||
variant: 'error',
|
||||
description: typeof error === 'string' ? error : JSON.stringify(error)
|
||||
})
|
||||
console.error('逻辑异常 :>> ', error);
|
||||
return false
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function MainLayout() {
|
||||
})
|
||||
}
|
||||
return <div className="flex">
|
||||
<div className="bg-white h-screen w-[84px] border-r dark:shadow-slate-700 relative text-center">
|
||||
<div className="bg-white h-screen w-[84px] dark:shadow-slate-700 relative text-center">
|
||||
<Link className="inline-block mt-[21px]" to='/'><img src={login} className="w-[42px] h-[42px]" alt="" /></Link>
|
||||
{/* <h1 className="text-white font-bold text-xl text-center">{t('title')}</h1> */}
|
||||
<nav className="mt-8">
|
||||
@@ -59,7 +59,7 @@ export default function MainLayout() {
|
||||
}
|
||||
</nav>
|
||||
<div className="absolute left-0 bottom-0 w-full p-2">
|
||||
<Link to={"https://lxm3ki8hexz.feishu.cn/wiki/space/7363217715992264708?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home"} target="_blank">
|
||||
<Link to={"https://lxm3ki8hexz.feishu.cn/wiki/space/7374266943353815043?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home"} target="_blank">
|
||||
<div className="flex justify-center items-center pb-[40px]" style={{flexDirection:"column"}}>
|
||||
<img src={shuoming} className="w-[20px]" alt="" />
|
||||
<span className="text-[11px] mt-[7px]" style={{color:"#666666"}}>说明</span>
|
||||
@@ -134,7 +134,7 @@ export default function MainLayout() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="flex-1 overflow-hidden xinDuiHua-box">
|
||||
<ErrorBoundary
|
||||
onReset={() => window.location.href = window.location.href}
|
||||
FallbackComponent={CrashErrorComponent}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uploadLibFile } from "../../controllers/API";
|
||||
import { uploadLibFile, uploadNpcHeaderLibFile } from "../../controllers/API";
|
||||
|
||||
// Function to upload the file with progress tracking
|
||||
export const uploadFileWithProgress = async (file, callback): Promise<any> => {
|
||||
@@ -29,3 +29,32 @@ export const uploadFileWithProgress = async (file, callback): Promise<any> => {
|
||||
// Handle errors
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadNpcHeaderLibFileWithProgress = async (file, callback): Promise<any> => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const config = {
|
||||
headers: { 'Content-Type': 'multipart/form-data;charset=utf-8' },
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const { loaded, total } = progressEvent;
|
||||
const progress = Math.round((loaded * 100) / total);
|
||||
console.log(`Upload progress: ${file.name} ${progress}%`);
|
||||
callback(progress)
|
||||
// You can update your UI with the progress information here
|
||||
},
|
||||
};
|
||||
|
||||
// Convert the FormData to binary using the FileReader API
|
||||
const data = await uploadNpcHeaderLibFile(formData, config);
|
||||
|
||||
console.log('Upload complete:', data);
|
||||
return data
|
||||
// Handle the response data as needed
|
||||
} catch (error) {
|
||||
console.error('Error uploading file:', error);
|
||||
return ''
|
||||
// Handle errors
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import { copyText, programmingLanguages } from "../../../../utils";
|
||||
import copy from "../../../../assets/chat/copy.png"
|
||||
import xiazai from "../../../../assets/chat/xiazai.png"
|
||||
|
||||
interface Props {
|
||||
language: string;
|
||||
@@ -42,17 +44,19 @@ export function CodeBlock({ language, value }) {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
return (
|
||||
<div className="codeblock font-sans text-[16px]">
|
||||
<div className="codeblock font-sans text-[16px] daimaStyle">
|
||||
<div className="code-block-modal">
|
||||
<span className="code-block-modal-span">{language}</span>
|
||||
<span className="code-block-modal-span text-[#999999] text-[14px]">{language}</span>
|
||||
|
||||
<div className="flex items-center">
|
||||
<button className="code-block-modal-button" onClick={copyToClipboard}>
|
||||
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
|
||||
{isCopied ? "Copied!" : "Copy code"}
|
||||
{/* {isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />} */}
|
||||
<img src={copy} alt="" className="w-[14px]" />
|
||||
{/* {isCopied ? "Copied!" : "Copy code"} */}
|
||||
</button>
|
||||
<button className="code-block-modal-button" onClick={downloadAsFile}>
|
||||
<IconDownload size={18} />
|
||||
<button className="code-block-modal-button ml-[10px]" onClick={downloadAsFile}>
|
||||
{/* <IconDownload size={18} /> */}
|
||||
<img src={xiazai} alt="" className="w-[14px]" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ChatMessageType } from "../../../types/chat";
|
||||
import { classNames } from "../../../utils";
|
||||
import FileCard from "../fileComponent";
|
||||
import { CodeBlock } from "./codeBlock";
|
||||
import Chart from "@/components/Chart";
|
||||
export default function ChatMessage({
|
||||
chat,
|
||||
lockChat,
|
||||
@@ -26,6 +27,8 @@ export default function ChatMessage({
|
||||
const [hidden, setHidden] = useState(true);
|
||||
const template = chat.template;
|
||||
const [promptOpen, setPromptOpen] = useState(false);
|
||||
|
||||
console.log(chat.message)
|
||||
return (
|
||||
<div
|
||||
className={classNames("form-modal-chat-position", chat.isSend ? "human-word" : "robert-word")}
|
||||
@@ -76,6 +79,9 @@ export default function ChatMessage({
|
||||
<div className="w-full">
|
||||
<div className="w-full dark:text-white">
|
||||
<div className="w-full">
|
||||
|
||||
{chat.message.toString() && chat.message.toString().includes('```chart') && <Chart options={chat.message} />}
|
||||
|
||||
{useMemo(
|
||||
() => (
|
||||
<ReactMarkdown
|
||||
@@ -125,7 +131,9 @@ export default function ChatMessage({
|
||||
},
|
||||
}}
|
||||
>
|
||||
{chat.message.toString()}
|
||||
{chat.message.toString() && !chat.message.toString().includes('```chart') && chat.message.toString()}
|
||||
|
||||
{/* {chat.message.toString()} */}
|
||||
</ReactMarkdown>
|
||||
),
|
||||
[chat.message, chat.message.toString()]
|
||||
|
||||
BIN
src/pages/.DS_Store
vendored
@@ -8,7 +8,7 @@ import ChatPanne from "./components/ChatPanne";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { deleteChatApi, getChatsApi } from "../../controllers/API";
|
||||
import { captureAndAlertRequestErrorHoc } from "../../controllers/request";
|
||||
import { useDebounce } from "../../util/hook";
|
||||
import { useDebounce, useTable } from "../../util/hook";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import SkillTemps from "../SkillPage/components/SkillTemps";
|
||||
import duihuaDel from "../../assets/chat/duihua-del.png";
|
||||
@@ -59,10 +59,15 @@ export default function chatShare() {
|
||||
//
|
||||
const { flow: initFlow } = useContext(TabsContext);
|
||||
const [flow, setFlow] = useState<FlowType>(null)
|
||||
const [onlineFlows, setOnlineFlows] = useState([])
|
||||
useEffect(() => {
|
||||
readOnlineFlows().then(setOnlineFlows)
|
||||
}, [])
|
||||
const {
|
||||
data: onlineFlows,
|
||||
loading,
|
||||
search,
|
||||
} = useTable<FlowType>({}, (param) =>
|
||||
readOnlineFlows(param.page, param.keyword).then((res) => {
|
||||
return res;
|
||||
})
|
||||
);
|
||||
const [chatId, setChatId] = useState<string>('')
|
||||
console.log(flowId,libId,tweak)
|
||||
useEffect(() => {
|
||||
@@ -72,21 +77,35 @@ export default function chatShare() {
|
||||
setChatId(generateUUID(32))
|
||||
})
|
||||
}, [flowId])
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
if (chat.chat_id === chatId) return
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
if (chat.chat_id === chatId) return
|
||||
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
|
||||
setFlow(flow)
|
||||
setChatId(chat.chat_id)
|
||||
}, 100, false)
|
||||
|
||||
const wsUrl = useMemo(() => {
|
||||
const params = [];
|
||||
|
||||
if (libId) params.push(`knowledge_id=${libId}`);
|
||||
if (tweak) params.push(`tweak=${tweak}`);
|
||||
|
||||
const paramStr = params.length > 0 ? `${params.join('&')}` : '';
|
||||
|
||||
return `/api/v2/chat/ws/${flowId}?type=L1&${paramStr}`
|
||||
}, [libId, tweak])
|
||||
|
||||
const [data] = useState<any>({ id: flowId, chatId: generateUUID(32), type: 'flow' })
|
||||
|
||||
setFlow(flow)
|
||||
setChatId(chat.chat_id)
|
||||
}, 100, false)
|
||||
if (!flowId) return <div>请选择技能</div>
|
||||
|
||||
return <div className="chatShare">
|
||||
@@ -122,13 +141,14 @@ export default function chatShare() {
|
||||
</div>
|
||||
</div> */}
|
||||
{/* chat */}
|
||||
{flow
|
||||
{/* {flow
|
||||
? <div className="flex-1 chat-box relative">
|
||||
{flow && <ChatPanne version='v2' queryString={queryString} chatId={chatId} flow={flow} />}
|
||||
</div>
|
||||
:<div className="flex-1 chat-box h-screen overflow-hidden relative">
|
||||
<p className="text-center mt-[100px] text-sm text-gray-600">{t('chat.selectChat')}</p>
|
||||
</div>}
|
||||
</div>} */}
|
||||
<ChatPanne customWsHost={wsUrl} data={data} />
|
||||
</div>
|
||||
{/* {flow ? <ChatPanne version='v2' queryString={queryString} chatId={chatId} flow={flow} /> : null} */}
|
||||
{/* 选择对话技能 */}
|
||||
|
||||
@@ -235,23 +235,24 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
// if (chat.isSend) return chat.files.length ? <>
|
||||
// 发送消息
|
||||
if (chat.isSend) return <div className="flex flex-col items-end">
|
||||
<div className="flex items-center avatarZk">
|
||||
<p className="mr-[20px] text-[14px]">{userName}</p>
|
||||
<div className="flex items-start avatarZk">
|
||||
<div className="mr-[10px]">
|
||||
<div className="chat-end-zk">
|
||||
{chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>}
|
||||
{chat.message[chat.chatKey]}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!disabledReSend && <img src={btnEdit} onClick={() => !disabledReSend && onEdit(chat.message[chat.chatKey])} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{!disabledReSend && <img src={btnReSend} onClick={() => !disabledReSend && onReSend(chat.message[chat.chatKey])} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[28px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
{/* <p className="mr-[20px] text-[14px]">{userName}</p> */}
|
||||
<img src={robotU} className="w-[50px]" alt=""/>
|
||||
</div>
|
||||
<div className="mt-[10px] mr-[60px]">
|
||||
<div className="chat-end-zk">
|
||||
{chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>}
|
||||
{chat.message[chat.chatKey]}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!disabledReSend && <img src={btnEdit} onClick={() => !disabledReSend && onEdit(chat.message[chat.chatKey])} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{!disabledReSend && <img src={btnReSend} onClick={() => !disabledReSend && onReSend(chat.message[chat.chatKey])} className="w-[28px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[28px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* 文件 */ }
|
||||
// <div className="chat chat-end">
|
||||
@@ -275,11 +276,12 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
{/* <div className="chat-image avatar">
|
||||
<div className="w-[40px] h-[40px] rounded-full flex items-center justify-center" style={{ background: avatarColor }}><Bot color="#fff" size={28} /></div>
|
||||
</div> */}
|
||||
<div className="flex items-center avatarZk">
|
||||
<div className="flex items-start avatarZk">
|
||||
{(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[50px]" alt=""/>}
|
||||
{chat.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[50px]" alt=""/>}
|
||||
{(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[50px]" alt=""/>}
|
||||
<p className="ml-[20px] text-[14px]">{userName}</p>
|
||||
|
||||
</div>
|
||||
{chat.sender && <div className="chat-header text-gray-400 text-sm">{chat.sender}</div>}
|
||||
<Card className={`my-2 w-[200px] relative ${chat.files[0]?.file_url && 'cursor-pointer'}`} onClick={() => handleDownloadFile(chat.files[0])}>
|
||||
@@ -295,33 +297,34 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
{/* <div className="chat-image avatar">
|
||||
<div className="w-[40px] h-[40px] rounded-full flex items-center justify-center" style={{ background: avatarColor }}><Bot color="#fff" size={28} /></div>
|
||||
</div> */}
|
||||
<div className="flex items-center avatarZk">
|
||||
<div className="flex items-start avatarZk">
|
||||
{(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} className="w-[50px]" alt=""/>}
|
||||
{chat.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} className="w-[50px]" alt=""/>}
|
||||
{(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} className="w-[50px]" alt=""/>}
|
||||
<p className="ml-[20px] text-[14px]">{userName}</p>
|
||||
{/* <p className="ml-[20px] text-[14px]">{userName}</p> */}
|
||||
<div ref={textRef} className={`min-h-8 min-w-[110px] max-w-[50vw] ml-[10px] ${chat.id && chat.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{chat.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{chat.receiver && <p className="text-blue-500 text-sm">@ {chat.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{chat.message.toString() && !chat.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!chat.id && chat.end && <Thumbs
|
||||
id={chat.id}
|
||||
data={chat.liked}
|
||||
onCopy={handleCopy}
|
||||
onDislike={onDislike}
|
||||
className="chat-start-btn"
|
||||
></Thumbs>
|
||||
// className={`absolute w-full left-0 bottom-[8px] justify-end pr-5`}></Thumbs>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{/* {chat.sender && <div className="chat-header text-gray-400 text-sm">{chat.sender}</div>} */}
|
||||
<div ref={textRef} className={`min-h-8 min-w-[110px] max-w-[50vw] mt-[10px] ml-[60px] ${chat.id && chat.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{chat.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{chat.receiver && <p className="text-blue-500 text-sm">@ {chat.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{chat.message.toString() && !chat.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!chat.id && chat.end && <Thumbs
|
||||
id={chat.id}
|
||||
data={chat.liked}
|
||||
onCopy={handleCopy}
|
||||
onDislike={onDislike}
|
||||
className="chat-start-btn"
|
||||
></Thumbs>
|
||||
// className={`absolute w-full left-0 bottom-[8px] justify-end pr-5`}></Thumbs>
|
||||
}
|
||||
</div>
|
||||
|
||||
{chat.source !== SourceType.NONE && chat.end && sourceContent(chat.source)}
|
||||
</div>
|
||||
};
|
||||
|
||||
948
src/pages/ChatAppPage/components/ChatPanne1 copy.tsx
Normal file
@@ -0,0 +1,948 @@
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { ClipboardList, FileInput, FileText, Send, StopCircle } from "lucide-react";
|
||||
import { forwardRef, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ShadTooltip from "../../../components/ShadTooltipComponent";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { TabsContext } from "../../../contexts/tabsContext";
|
||||
import { getChatHistory, postBuildInit, postValidatePrompt } from "../../../controllers/API";
|
||||
import { Variable } from "../../../controllers/API/flow";
|
||||
import { sendAllProps } from "../../../types/api";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { FlowType, NodeType } from "../../../types/flow";
|
||||
import { validateNode } from "../../../utils";
|
||||
import { ChatMessage } from "./ChatMessage";
|
||||
import ChatReportForm from "./ChatReportForm";
|
||||
import ResouceModal from "./ResouceModal";
|
||||
import ThumbsMessage from "./ThumbsMessage";
|
||||
import NpcInfo from "./NpcInfo";
|
||||
import Quote from "./quote";
|
||||
import { locationContext } from "../../../contexts/locationContext";
|
||||
import CompleteRecords from "./completeRecords";
|
||||
import DataContent from "./dataContent";
|
||||
import DialogueDetails from "./dialogueDetails";
|
||||
import robot from "../../../assets/robot.png";
|
||||
import robot2 from "../../../assets/robot2.png";
|
||||
import robot3 from "../../../assets/robot3.png";
|
||||
import duihuaGengduo from "../../../assets/chat/duihua-gengduo.png";
|
||||
import duihuaSend from "../../../assets/chat/duihua-send.png";
|
||||
|
||||
interface Iprops {
|
||||
chatId: string
|
||||
flow: FlowType
|
||||
queryString?: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
export default forwardRef(function ChatPanne({ chatId, flow, queryString, version = 'v1' }: Iprops,ref) {
|
||||
const { t } = useTranslation()
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
|
||||
const { isRoom, isForm, isReport, checkPrompt } = useFlowState(flow)
|
||||
|
||||
// build
|
||||
const build = useBuild(flow, chatId)
|
||||
// 消息列表
|
||||
const { messages, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll } = useMessages(chatId, flow)
|
||||
// ws通信
|
||||
const { stop, connectWS, begin: chating, checkReLinkWs, sendAll } = useWebsocket(chatId, flow, setChatHistory, queryString, version)
|
||||
// 停止状态
|
||||
const [isStop, setIsStop] = useState(true)
|
||||
// 输入框状态
|
||||
const { inputState, inputEmpty, inputDisabled, inputRef,
|
||||
formShow, setFormShow,
|
||||
setInputState, setInputEmpty, handleTextAreaHeight } = useInputState({ flow, chatId, chating, messages, isForm, isReport })
|
||||
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
// npc信息
|
||||
const [isNpcInfo, setIsNpcInfo] = useState(false)
|
||||
|
||||
// 知识库引用信息
|
||||
const [isQuote, setIsQuote] = useState(false)
|
||||
|
||||
// 完整对话记录
|
||||
const [isCompleteRecords, setIsCompleteRecords] = useState(false)
|
||||
|
||||
// 数据内容
|
||||
const [isDataContent, setIsDataContent] = useState(false)
|
||||
|
||||
// 对话详情
|
||||
const [isDialogueDetails, setIsDialogueDetails] = useState(false)
|
||||
|
||||
// 开始构建&切换初始化会话
|
||||
const initChat = async () => {
|
||||
await checkPrompt(flow)
|
||||
await build()
|
||||
const historyData = version === 'v1' ? await loadHistory() : (initGuide(), [])
|
||||
await connectWS({ setInputState, setIsStop, changeHistoryByScroll })
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
// 第一条消息,用来初始化会话
|
||||
sendAll({
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
inputs: {},
|
||||
flow_id: flow.id,
|
||||
chat_id: chatId
|
||||
})
|
||||
|
||||
changeHistoryByScroll.current = false
|
||||
// 自动聚焦
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 500);
|
||||
|
||||
const isNewChat = historyData.length === 0 || historyData[0].id === 9999
|
||||
setFormShow(isNewChat && isForm)
|
||||
}
|
||||
useEffect(() => {
|
||||
initChat()
|
||||
}, [flow])
|
||||
|
||||
// sendmsg user name
|
||||
const sendUserName = useMemo(() => {
|
||||
const node = flow.data.nodes.find(el => el.data.type === 'AutoGenUser')
|
||||
return node?.data.node.template['name'].value || ''
|
||||
}, [flow])
|
||||
|
||||
const handleSend = async () => {
|
||||
const msg = inputRef.current?.value
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ''
|
||||
inputRef.current.style.height = 'auto'
|
||||
}
|
||||
setInputEmpty(true)
|
||||
}, 100);
|
||||
|
||||
if (msg.trim() === '') return
|
||||
|
||||
setInputState({ lock: true, errorMsg: '' });
|
||||
let inputs = tabsState[flow.id].formKeysData.input_keys;
|
||||
const input = inputs.find((el: any) => !el.type)
|
||||
const inputKey = input ? Object.keys(input)[0] : '';
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: true,
|
||||
message: { ...input, [inputKey]: msg },
|
||||
chatKey: inputKey,
|
||||
thought: '',
|
||||
category: '',
|
||||
files: [],
|
||||
end: false,
|
||||
user_name: ""
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
|
||||
await checkReLinkWs(async () => {
|
||||
// await build()
|
||||
await connectWS({ setInputState, setIsStop, changeHistoryByScroll })
|
||||
})
|
||||
|
||||
const chatInfo = {
|
||||
chat_id: chatId,
|
||||
flow_id: flow.id,
|
||||
inputs: { ...input, [inputKey]: msg }
|
||||
}
|
||||
// @ts-ignore
|
||||
isRoom && chating ? sendAll({ action: "continue", ...chatInfo })
|
||||
: sendAll({
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
...chatInfo
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 报表请求
|
||||
const sendReport = (items: Variable[], str) => {
|
||||
let inputs = tabsState[flow.id].formKeysData.input_keys;
|
||||
const input = inputs.find((el: any) => !el.type)
|
||||
const inputKey = input ? Object.keys(input)[0] : '';
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: true,
|
||||
message: { ...input, [inputKey]: str },
|
||||
chatKey: inputKey,
|
||||
thought: '',
|
||||
category: '',
|
||||
files: [],
|
||||
end: false,
|
||||
user_name: ""
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
|
||||
const data = items.map(item => ({
|
||||
id: item.nodeId,
|
||||
name: item.name,
|
||||
file_path: item.type === 'file' ? item.value : '',
|
||||
value: item.type === 'file' ? '' : item.value
|
||||
}))
|
||||
|
||||
setIsStop(false)
|
||||
setFormShow(false)
|
||||
|
||||
sendAll({
|
||||
inputs: {
|
||||
...input,
|
||||
[inputKey]: str,
|
||||
data
|
||||
},
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
chat_id: chatId,
|
||||
flow_id: flow.id,
|
||||
});
|
||||
}
|
||||
|
||||
// 溯源
|
||||
const [souce, setSouce] = useState<ChatMessageType>(null)
|
||||
|
||||
const thumbRef = useRef(null)
|
||||
|
||||
return <div className="overflow-hidden relative duihua-chat">
|
||||
<div className="absolute duihua-chat-top">
|
||||
<div>
|
||||
{/* <img src={robot} alt=""/> */}
|
||||
{(flow.id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || flow.id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} alt=""/>}
|
||||
{flow.id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} alt=""/>}
|
||||
{(flow.id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && flow.id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && flow.id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} alt=""/>}
|
||||
<p>{flow.name}</p>
|
||||
{/* <div className="cursor-pointer">3条记录</div> */}
|
||||
{/* <div className="cursor-pointer">模型列表</div> */}
|
||||
</div>
|
||||
<div className="cursor-pointer" onClick={() => setIsNpcInfo(!isNpcInfo)}>
|
||||
{/* <img src={duihuaGengduo} alt=""/> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="chata mt-[70px]" style={{ height: 'calc(100% - 170px)' }}>
|
||||
{/* 会话记录 */}
|
||||
<div ref={messagesRef} className={`chat-panne h-full overflow-y-scroll no-scrollbar px-[35px] ${isRoom || isReport ? 'pb-0' : 'pb-0'}`}>
|
||||
{
|
||||
messages.map((c, i) => <ChatMessage
|
||||
key={c.id || i}
|
||||
userName={sendUserName}
|
||||
chat={c}
|
||||
disabledReSend={inputDisabled}
|
||||
showSearch={!!appConfig.dialogQuickSearch}
|
||||
onSource={() => setSouce(c)}
|
||||
onDislike={(chatId) => { thumbRef.current?.openModal(chatId) }}
|
||||
onReSend={(msg) => {
|
||||
inputRef.current.value = msg
|
||||
handleSend()
|
||||
}}
|
||||
onEdit={(msg) => { inputRef.current.value = msg; setInputEmpty(!msg) }}
|
||||
onSearch={(msg) => window.open(appConfig.dialogQuickSearch + encodeURIComponent(msg))}
|
||||
></ChatMessage>)
|
||||
}
|
||||
</div>
|
||||
{/* 输入框 */}
|
||||
{version != "v3" && <div className="absolute w-full bottom-0 duihua-input-box pb-[40px]">
|
||||
{/* <div className={`relative duihua-input
|
||||
${inputDisabled && 'bg-gray-200 dark:bg-gray-600'}`}> */}
|
||||
<div className={`relative duihua-input`}>
|
||||
<textarea id='input'
|
||||
ref={inputRef}
|
||||
disabled={inputDisabled} rows={1}
|
||||
className={`w-full resize-none border-none bg-transparent outline-none max-h-[160px]`}
|
||||
placeholder={ (inputDisabled ? "当前处于回复中或不支持输入状态" : t('chat.inputPlaceholder'))}
|
||||
onInput={handleTextAreaHeight}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) handleSend()
|
||||
}}></textarea>
|
||||
<div className="absolute right-0 bottom-0 w-[84px] duihua-input-btn cursor-pointer">
|
||||
{/* <ShadTooltip content={t('chat.sendTooltip')}>
|
||||
<button disabled={inputEmpty || inputDisabled} className=" disabled:text-gray-400" onClick={handleSend}><Send /></button>
|
||||
</ShadTooltip> */}
|
||||
<div className="duihua-input-btn-send">
|
||||
<img src={duihuaSend} onClick={handleSend} alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
{inputState.errorMsg && <div className="absolute top-0 left-0 w-full h-full text-center align-middle pt-4" style={{background:"#2E1212",color:"#FF6060"}}>{inputState.errorMsg}</div>}
|
||||
</div>
|
||||
<p className="mb-2 text-center text-gray-400 text-sm">{appConfig.dialogTips}</p>
|
||||
</div>}
|
||||
</div>
|
||||
{(isRoom || isReport) && <div className=" absolute w-full flex justify-center bottom-32 pointer-events-none">
|
||||
<Button className="rounded-full pointer-events-auto" variant="outline" disabled={isStop} onClick={() => { setIsStop(true); stop(); }}><StopCircle className="mr-2" />Stop</Button>
|
||||
</div>}
|
||||
{/* 源文件类型 */}
|
||||
<ResouceModal chatId={chatId} open={!!souce} data={souce} setOpen={() => setSouce(null)}></ResouceModal>
|
||||
{/* 表单 */}
|
||||
{isForm && formShow && <ChatReportForm flow={flow} onStart={sendReport} />}
|
||||
{/* 踩 反馈 */}
|
||||
<ThumbsMessage ref={thumbRef}></ThumbsMessage>
|
||||
|
||||
<NpcInfo isNpcInfo={isNpcInfo} setIsNpcInfo={setIsNpcInfo}></NpcInfo>
|
||||
|
||||
<Quote isQuote={isQuote} setIsQuote={setIsQuote}></Quote>
|
||||
|
||||
<CompleteRecords isCompleteRecords={isCompleteRecords} setIsCompleteRecords={setIsCompleteRecords} id={undefined}></CompleteRecords>
|
||||
|
||||
<DataContent isDataContent={isDataContent} setIsDataContent={setIsDataContent} id={undefined}></DataContent>
|
||||
|
||||
<DialogueDetails isDialogueDetails={isDialogueDetails} setIsDialogueDetails={setIsDialogueDetails}></DialogueDetails>
|
||||
</div>
|
||||
});
|
||||
/**
|
||||
* 输入框状态
|
||||
* 分析 flow状态
|
||||
* return 该技能含有表单、有报表、群聊
|
||||
* @returns
|
||||
*/
|
||||
const useInputState = ({ flow, chatId, chating, messages, isForm, isReport }) => {
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
|
||||
const [inputState, setInputState] = useState({
|
||||
lock: false,
|
||||
errorMsg: ''
|
||||
})
|
||||
// 输入问答
|
||||
const inputRef = useRef(null)
|
||||
useEffect(() => {
|
||||
!chating && setTimeout(() => {
|
||||
// 对话结束自动聚焦
|
||||
inputRef.current?.focus()
|
||||
}, 1000);
|
||||
}, [chating])
|
||||
// input 滚动
|
||||
const [inputEmpty, setInputEmpty] = useState(true)
|
||||
useEffect(() => {
|
||||
setInputEmpty(true)
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
}, [chatId])
|
||||
|
||||
// 获取上传file input
|
||||
const fileInputs = useMemo(() => {
|
||||
return tabsState[flow.id]?.formKeysData?.input_keys?.filter((input: any) => input.type === 'file')
|
||||
}, [tabsState, flow])
|
||||
|
||||
const handleTextAreaHeight = (e) => {
|
||||
const textarea = e.target
|
||||
textarea.style.height = 'auto'
|
||||
textarea.style.height = textarea.scrollHeight + 'px'
|
||||
setInputEmpty(textarea.value.trim() === '')
|
||||
}
|
||||
// input disabled
|
||||
const inputDisabled = useMemo(() => {
|
||||
return inputState.lock
|
||||
// 表单 && 没回话或只有一个引导词
|
||||
|| (isForm && (messages.length === 0 || (messages.length === 1 && messages[0].id === 9999)))
|
||||
|| isReport
|
||||
}, [inputState, fileInputs, isReport])
|
||||
|
||||
// 表单收起
|
||||
const [formShow, setFormShow] = useState(true)
|
||||
return {
|
||||
inputState, inputEmpty, inputDisabled, inputRef,
|
||||
formShow, setFormShow,
|
||||
setInputState, setInputEmpty, handleTextAreaHeight
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* flow state
|
||||
* 分析 flow状态
|
||||
* return 该技能含有表单、有报表、群聊
|
||||
* @returns
|
||||
*/
|
||||
const useFlowState = (flow: FlowType) => {
|
||||
const flowSate = useMemo(() => {
|
||||
// 是否群聊
|
||||
const isRoom = !!flow.data?.nodes.find(node => node.data.type === "AutoGenChain")
|
||||
// 是否展示表单
|
||||
const isForm = !!flow.data?.nodes.find(node => ["VariableNode", "InputFileNode"].includes(node.data.type))
|
||||
// 是否报表
|
||||
const isReport = !!flow.data?.nodes.find(node => "Report" === node.data.type)
|
||||
return { isRoom, isForm, isReport }
|
||||
}, [flow])
|
||||
|
||||
// propmt类型补充自定义字段
|
||||
const checkPrompt = async (_flow) => {
|
||||
const params = _flow.data.nodes.map(node => {
|
||||
const temps = []
|
||||
const temp = node.data.node.template
|
||||
Object.keys(temp).map(key => {
|
||||
const { type, value } = temp[key]
|
||||
if (type === 'prompt' && !!value) !temps.length && temps.push({ name: key, template: value, data: node.data })
|
||||
})
|
||||
return temps
|
||||
}).flat()
|
||||
|
||||
const promises = params.map(param => {
|
||||
return postValidatePrompt(param.name, param.template, param.data.node).then(res => {
|
||||
if (res) param.data.node = res.frontend_node
|
||||
})
|
||||
})
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
return { ...flowSate, checkPrompt }
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息列表模块
|
||||
* 翻页、追加、历史
|
||||
* @returns
|
||||
*/
|
||||
const useMessages = (chatId, flow) => {
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const lastIdRef = useRef(0)
|
||||
// 控制开启自动随消息滚动(临时方案)
|
||||
const changeHistoryByScroll = useRef(false)
|
||||
|
||||
const loadIdRef = useRef('') // 记录最后一个加载的 chatId
|
||||
// 获取聊天记录
|
||||
const loadHistory = async (lastId?: number) => {
|
||||
loadIdRef.current = chatId
|
||||
|
||||
const res = await getChatHistory(flow.id, chatId, lastId ? 10 : 30, lastId)
|
||||
const hisData = res.map(item => {
|
||||
// let count = 0
|
||||
let { message, files, is_bot, intermediate_steps, ...other } = item
|
||||
try {
|
||||
message = message && message[0] === '{' ? JSON.parse(message.replace(/([\t\n"])/g, '\\$1').replace(/'/g, '"')) : message || ''
|
||||
} catch (e) {
|
||||
// 未考虑的情况暂不处理
|
||||
}
|
||||
return {
|
||||
...other,
|
||||
chatKey: typeof message === 'string' ? undefined : Object.keys(message)[0],
|
||||
end: true,
|
||||
files: files ? JSON.parse(files) : [],
|
||||
isSend: !is_bot,
|
||||
message,
|
||||
thought: intermediate_steps,
|
||||
noAccess: true
|
||||
}
|
||||
})
|
||||
lastIdRef.current = hisData[hisData.length - 1]?.id || lastIdRef.current || 0 // 记录最后一个id
|
||||
|
||||
let historyData = []
|
||||
if (lastId) {
|
||||
historyData = [...hisData.reverse(), ...chatHistory]
|
||||
} else if (loadIdRef.current === chatId) { // 保证同一会话
|
||||
historyData = hisData.reverse()
|
||||
}
|
||||
setChatHistory(historyData)
|
||||
const pageSize = historyData.length < 30 ? 30 : 10 // 先偷懒
|
||||
if (hisData.length < pageSize) initGuide()
|
||||
return historyData
|
||||
}
|
||||
|
||||
const loadLock = useRef(false)
|
||||
const currentIdRef = useRef(0)
|
||||
const loadNextPage = async () => {
|
||||
if (loadLock.current) return
|
||||
if (currentIdRef.current === lastIdRef.current) return // 最后一个相同表示聊天记录已到顶
|
||||
loadLock.current = true
|
||||
currentIdRef.current = lastIdRef.current
|
||||
changeHistoryByScroll.current = true
|
||||
await loadHistory(currentIdRef.current)
|
||||
loadLock.current = false
|
||||
// 滚动 hack TODO 滚动翻页设计
|
||||
setTimeout(() => {
|
||||
changeHistoryByScroll.current = false
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const initGuide = () => {
|
||||
const guideMsg = {
|
||||
"category": "system",
|
||||
"chat_id": chatId,
|
||||
"end": true,
|
||||
"create_time": "",
|
||||
"extra": "{}",
|
||||
"files": [],
|
||||
"flow_id": flow.id,
|
||||
"id": 9999,
|
||||
"thought": flow.guide_word,
|
||||
"is_bot": true,
|
||||
"liked": 0,
|
||||
"message": '',
|
||||
"receiver": null,
|
||||
"remark": null,
|
||||
"sender": "",
|
||||
"solved": 0,
|
||||
isSend: false,
|
||||
"source": 0,
|
||||
"type": "end",
|
||||
"update_time": "",
|
||||
noAccess: true,
|
||||
"user_id": 0
|
||||
}
|
||||
flow.guide_word && setChatHistory((chatHistory) =>
|
||||
chatHistory[0]?.id === 9999 ? chatHistory : [guideMsg, ...chatHistory]
|
||||
)
|
||||
}
|
||||
|
||||
// 消息滚动
|
||||
const messagesRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (messagesRef.current) { // 滚动加载不触发
|
||||
// if (messagesRef.current && !changeHistoryByScroll.current) { // 滚动加载不触发
|
||||
console.log(1,messagesRef,changeHistoryByScroll)
|
||||
setTimeout(() => {
|
||||
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
|
||||
}, 100);
|
||||
}
|
||||
}, [chatHistory, changeHistoryByScroll]);
|
||||
|
||||
// 消息滚动加载
|
||||
useEffect(() => {
|
||||
function handleScroll() {
|
||||
if (messagesRef.current.scrollTop <= 30) {
|
||||
loadNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
messagesRef.current?.addEventListener('scroll', handleScroll);
|
||||
return () => messagesRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}, [messagesRef.current, chatHistory, chatId]);
|
||||
|
||||
return {
|
||||
messages: chatHistory, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket 通信
|
||||
* 建立连接、重连、断开、接收、发送
|
||||
* @returns
|
||||
*/
|
||||
const useWebsocket = (chatId, flow, setChatHistory, queryString, version) => {
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
// 接收ws状态
|
||||
const [begin, setBegin] = useState(false)
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
const chatIdRef = useRef(chatId);
|
||||
useEffect(() => {
|
||||
chatIdRef.current = chatId;
|
||||
}, [chatId])
|
||||
|
||||
function heartbeat() {
|
||||
if (!ws.current) return;
|
||||
if (ws.current.readyState !== 1) return;
|
||||
ws.current.send("heartbeat");
|
||||
setTimeout(heartbeat, 30000);
|
||||
}
|
||||
|
||||
function getWebSocketUrl(flowId, isDevelopment = false) {
|
||||
const token = localStorage.getItem("ws_token") || '';
|
||||
|
||||
const isSecureProtocol = window.location.protocol === "https:";
|
||||
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
|
||||
const host = appConfig.websocketHost || window.location.host // isDevelopment ? "localhost:7860" : window.location.host;
|
||||
const chatEndpoint = version === 'v1' ? `/api/v1/chat/${flowId}?type=L1&chat_id=${chatId}&t=${token}`
|
||||
: `/api/v2/chat/ws/${flowId}?type=L1&chat_id=${chatId}${queryString}&t=${token}`
|
||||
|
||||
return `${webSocketProtocol}://${host}${chatEndpoint}`;
|
||||
}
|
||||
|
||||
const newChatStart = useRef(false) // 处理当前会话上下文丢失,阻止上一次打字机效果
|
||||
// 自动重连次数
|
||||
const tryReLinkCount = useRef(0)
|
||||
const reConnect = (params) => {
|
||||
if (tryReLinkCount.current <= 3) {
|
||||
connectWS(params)
|
||||
tryReLinkCount.current++
|
||||
} else {
|
||||
console.warn('超过最大重试次数 :>> ');
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
tryReLinkCount.current = 0
|
||||
newChatStart.current = true
|
||||
}, [chatId])
|
||||
|
||||
function connectWS(params) {
|
||||
const { setInputState, setIsStop, changeHistoryByScroll } = params
|
||||
if (ws.current) return Promise.resolve('ok');
|
||||
|
||||
// 连接断开重链接
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
const urlWs = getWebSocketUrl(
|
||||
flow.id,
|
||||
process.env.NODE_ENV === "development"
|
||||
);
|
||||
const newWs = new WebSocket(urlWs);
|
||||
newWs.onopen = () => {
|
||||
console.log("WebSocket connection established!");
|
||||
res('ok')
|
||||
// heartbeat()
|
||||
};
|
||||
newWs.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.chat_id !== chatIdRef.current) return
|
||||
console.log('newChatStart.current :>> ', newChatStart.current);
|
||||
|
||||
const errorMsg = data.category === 'error' ? data.intermediate_steps : ''
|
||||
|
||||
if (newChatStart.current) {
|
||||
if (data.type === 'close') {
|
||||
newChatStart.current = false
|
||||
return setInputState({ lock: false, errorMsg })
|
||||
} else {
|
||||
return setInputState({ lock: true, errorMsg })
|
||||
}
|
||||
}
|
||||
// 异常类型处理,提示
|
||||
if (errorMsg) return setInputState({ lock: true, errorMsg })
|
||||
|
||||
handleWsMessage({ data, setIsStop, setInputState, changeHistoryByScroll });
|
||||
// get chat history
|
||||
// 群聊@自己时,开启input
|
||||
if (data.type === 'end' && data.receiver?.is_self) {
|
||||
setInputState({ lock: false, errorMsg: '' })
|
||||
}
|
||||
};
|
||||
newWs.onclose = (event) => {
|
||||
ws.current = null
|
||||
|
||||
handleOnClose({ event, setIsStop, setInputState });
|
||||
// reConnect(params)
|
||||
};
|
||||
newWs.onerror = (ev) => {
|
||||
ws.current = null
|
||||
|
||||
console.error('链接异常error', ev);
|
||||
setIsStop(true)
|
||||
|
||||
setErrorData({
|
||||
title: `${t('chat.networkError')}:`,
|
||||
list: [
|
||||
t('chat.networkErrorList1'),
|
||||
t('chat.networkErrorList2'),
|
||||
t('chat.networkErrorList3')
|
||||
],
|
||||
});
|
||||
reConnect(params)
|
||||
};
|
||||
ws.current = newWs;
|
||||
console.log('newWs :>> ', newWs);
|
||||
} catch (error) {
|
||||
console.error('创建链接异常', error);
|
||||
rej(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var isStream = false;
|
||||
function handleWsMessage({ data, setIsStop, setInputState, changeHistoryByScroll }) {
|
||||
if (Array.isArray(data) && data.length) return
|
||||
if (data.type === "begin") {
|
||||
setBegin(true)
|
||||
setIsStop(false)
|
||||
changeHistoryByScroll.current = false
|
||||
}
|
||||
if (data.type === "close") {
|
||||
setBegin(false)
|
||||
setIsStop(true)
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
changeHistoryByScroll.current = true
|
||||
}
|
||||
if (data.type === "start") {
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: false,
|
||||
message: '',
|
||||
chatKey: '',
|
||||
thought: data.intermediate_steps || '',
|
||||
category: data.category || '',
|
||||
files: [],
|
||||
end: false
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
isStream = true;
|
||||
}
|
||||
if (data.type === "stream" && isStream) {
|
||||
updateLastMessage({ str: data.message, thought: data.intermediate_steps });
|
||||
}
|
||||
if (data.type === "end") {
|
||||
updateLastMessage({
|
||||
...data,
|
||||
str: data.message,
|
||||
files: data.files || null,
|
||||
end: true,
|
||||
thought: data.intermediate_steps || '',
|
||||
cate: data.category || '',
|
||||
messageId: data.message_id,
|
||||
noAccess: false,
|
||||
liked: 0
|
||||
});
|
||||
|
||||
isStream = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateLastMessage({ str, thought = '', end = false, files = [], cate = '', messageId = 0, source = false, noAccess = false, ...data }: {
|
||||
str: string;
|
||||
messageId?: number
|
||||
thought?: string;
|
||||
cate?: string;
|
||||
end?: boolean;
|
||||
files?: Array<any>;
|
||||
source?: boolean
|
||||
noAccess?: boolean
|
||||
}) {
|
||||
setChatHistory((old) => {
|
||||
const newChats = [...old]
|
||||
// console.log('newchats :>> ', newChats);
|
||||
let chatsLen = newChats.length
|
||||
const prevChat = newChats[chatsLen - 2]
|
||||
// hack 过滤重复最后消息
|
||||
if (end
|
||||
&& str
|
||||
&& chatsLen > 1
|
||||
&& str === prevChat.message
|
||||
// && data.sender === prevChat.sender
|
||||
&& !prevChat.thought) {
|
||||
newChats.splice(chatsLen - 2, 1) // 删上一条
|
||||
chatsLen = newChats.length
|
||||
}
|
||||
// 更新
|
||||
const lastChat = newChats[chatsLen - 1]
|
||||
const newLastChat = {
|
||||
...newChats[chatsLen - 1],
|
||||
...data,
|
||||
id: messageId,
|
||||
message: lastChat.message + str,
|
||||
thought: lastChat.thought + (thought ? `${thought}\n` : ''),
|
||||
files,
|
||||
category: cate,
|
||||
source,
|
||||
noAccess,
|
||||
end
|
||||
}
|
||||
newChats[chatsLen - 1] = newLastChat
|
||||
// start - end 之间没有内容删除load
|
||||
if (end && !(newLastChat.files.length || newLastChat.thought || newLastChat.message)) {
|
||||
newChats.pop()
|
||||
}
|
||||
return newChats;
|
||||
});
|
||||
}
|
||||
|
||||
// 发送ws
|
||||
async function sendAll(data: sendAllProps) {
|
||||
try {
|
||||
if (ws.current) {
|
||||
if (JSON.stringify(data.inputs) !== '{}') {
|
||||
newChatStart.current = false
|
||||
}
|
||||
ws.current.send(JSON.stringify(data));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error sending the message",
|
||||
list: [error.message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理主动断开
|
||||
function handleOnClose({ event, setIsStop, setInputState }) {
|
||||
console.error('链接手动断开 event :>> ', event);
|
||||
setIsStop(true)
|
||||
setBegin(false)
|
||||
|
||||
if ([1005, 1008].includes(event.code)) {
|
||||
console.warn('即将废弃 :>> ');
|
||||
setInputState({ lock: true, errorMsg: event.reason });
|
||||
} else {
|
||||
if (event.reason) {
|
||||
setErrorData({ title: event.reason });
|
||||
// setChatHistory((old) => {
|
||||
// let newChat = cloneDeep(old);
|
||||
// if (newChat.length) {
|
||||
// newChat[newChat.length - 1].end = true;
|
||||
// }
|
||||
// newChat.push({ end: true, message: `${t('chat.connectionbreakTip')}${event.reason}`, isSend: false, chatKey: '', files: [] });
|
||||
// return newChat
|
||||
// })
|
||||
}
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// destory
|
||||
return () => {
|
||||
// close prev connection
|
||||
if (ws.current) {
|
||||
switch (ws.current.readyState) {
|
||||
case WebSocket.OPEN:
|
||||
console.warn('前端主动关闭1')
|
||||
ws.current.close()
|
||||
; break;
|
||||
case WebSocket.CONNECTING:
|
||||
ws.current.onopen = () => {
|
||||
console.warn('前端主动关闭2')
|
||||
ws.current.close()
|
||||
};
|
||||
}
|
||||
ws.current = null
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 检测并重连
|
||||
const checkReLinkWs = async (reConnect) => {
|
||||
if (ws.current) return true
|
||||
// 重连
|
||||
// 上一条加loading
|
||||
setChatHistory((old) => {
|
||||
let newChat = [...old];
|
||||
newChat[newChat.length - 1].category = 'loading';
|
||||
return newChat;
|
||||
});
|
||||
await reConnect()
|
||||
// 链接成功
|
||||
// 上一条去loading
|
||||
setChatHistory((old) => {
|
||||
let newChat = [...old];
|
||||
newChat[newChat.length - 1].category = '';
|
||||
return newChat;
|
||||
});
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
try {
|
||||
if (ws) {
|
||||
ws.current.send(JSON.stringify({
|
||||
"action": "stop"
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error stop the message",
|
||||
list: [error.message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { begin, stop: handleStop, checkReLinkWs, sendAll, connectWS }
|
||||
}
|
||||
|
||||
/**
|
||||
* build flow
|
||||
* 校验每个节点,展示进度及结果;返回input_keys;end_of_stream断开链接
|
||||
* 主要校验节点并设置更新setTabsState的 formKeysData
|
||||
* @returns
|
||||
*/
|
||||
const useBuild = (flow: FlowType, chatId: string) => {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { setTabsState } = useContext(TabsContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
// SSE 服务端推送
|
||||
async function streamNodeData(flow: FlowType, chatId: string) {
|
||||
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
||||
const { flowId } = await postBuildInit(flow, chatId);
|
||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||
let validationResults = [];
|
||||
let finished = false;
|
||||
let buildEnd = false
|
||||
const apiUrl = `/api/v1/build/stream/${flowId}?chat_id=${chatId}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// If the event is parseable, return
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
const parsedData = JSON.parse(event.data);
|
||||
// if the event is the end of the stream, close the connection
|
||||
if (parsedData.end_of_stream) {
|
||||
eventSource.close(); // 结束关闭链接
|
||||
buildEnd = true
|
||||
return;
|
||||
} else if (parsedData.log) {
|
||||
// If the event is a log, log it
|
||||
// setSuccessData({ title: parsedData.log });
|
||||
} else if (parsedData.input_keys) {
|
||||
setTabsState((old) => {
|
||||
return {
|
||||
...old,
|
||||
[flowId]: {
|
||||
...old[flowId],
|
||||
formKeysData: parsedData,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// setProgress(parsedData.progress);
|
||||
validationResults.push(parsedData.valid);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error("EventSource failed:", error);
|
||||
eventSource.close();
|
||||
if (error.data) {
|
||||
const parsedData = JSON.parse(error.data);
|
||||
setErrorData({ title: parsedData.error });
|
||||
}
|
||||
};
|
||||
// Step 3: Wait for the stream to finish
|
||||
while (!finished) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
finished = buildEnd // validationResults.length === flow.data.nodes.length;
|
||||
}
|
||||
// Step 4: Return true if all nodes are valid, false otherwise
|
||||
return validationResults.every((result) => result);
|
||||
}
|
||||
|
||||
// 延时器
|
||||
async function enforceMinimumLoadingTime(
|
||||
startTime: number,
|
||||
minimumLoadingTime: number
|
||||
) {
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const remainingTime = minimumLoadingTime - elapsedTime;
|
||||
|
||||
if (remainingTime > 0) {
|
||||
return new Promise((resolve) => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBuild() {
|
||||
try {
|
||||
const errors = flow.data.nodes.flatMap((n: NodeType) => validateNode(n, flow.data.edges))
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: t('chat.buildError'),
|
||||
list: errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const minimumLoadingTime = 200; // in milliseconds
|
||||
const startTime = Date.now();
|
||||
|
||||
await streamNodeData(flow, chatId);
|
||||
await enforceMinimumLoadingTime(startTime, minimumLoadingTime); // 至少等200ms, 再继续(强制最小load时间)
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
return handleBuild
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/bs-ui/select";
|
||||
import { forwardRef, useContext, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Dropdown from "../../../components/dropdownComponent";
|
||||
import InputComponent from "../../../components/inputComponent";
|
||||
import InputFileComponent from "../../../components/inputFileComponent";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
@@ -12,14 +12,23 @@ import { Variable, VariableType, getVariablesApi } from "../../../controllers/AP
|
||||
* @description
|
||||
* 表单项数据由组件的参数信息和单独接口获取的必填信息及排序信息而来。
|
||||
*/
|
||||
export default function ChatReportForm({ flow, onStart }) {
|
||||
const ChatReportForm = forwardRef(({ type = 'chat', vid = 0, flow, onStart }, ref) => {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
submit: () => {
|
||||
handleStart()
|
||||
}
|
||||
}));
|
||||
|
||||
// 从 api中获取
|
||||
const [items, setItems] = useState<Variable[]>([])
|
||||
useEffect(() => {
|
||||
getVariablesApi({ flow_id: flow.id }).then(
|
||||
// chat -》L1; diff -> 对比测试
|
||||
type === 'chat' ? getVariablesApi({ flow_id: flow.flow_id || flow.id }).then(
|
||||
res => setItems(res)
|
||||
) : getVariablesApi({ version_id: vid, flow_id: flow.flow_id || flow.id }).then(
|
||||
res => setItems(res)
|
||||
)
|
||||
}, [])
|
||||
@@ -56,9 +65,9 @@ export default function ChatReportForm({ flow, onStart }) {
|
||||
onStart(obj, str)
|
||||
}
|
||||
|
||||
return <div className="absolute right-20 bottom-32 w-[90%] max-w-[680px] flex flex-col gap-6 rounded-xl p-4 md:p-6 bg-[#1A1A1A]">
|
||||
return <div className="flex flex-col gap-6 rounded-xl p-4 ">
|
||||
<div className="max-h-[520px] overflow-y-auto">
|
||||
{items.map((item, i) => <div key={item.id} className="w-full text-sm" style={{color:"#fff"}}>
|
||||
{items.map((item, i) => <div key={item.id} className="w-full text-sm text-[#fff]">
|
||||
{item.name}
|
||||
<span className="text-status-red">{item.required ? " *" : ""}</span>
|
||||
<div className="mt-2">
|
||||
@@ -68,11 +77,18 @@ export default function ChatReportForm({ flow, onStart }) {
|
||||
onChange={(val) => handleChange(i, val)}
|
||||
/> :
|
||||
item.type === VariableType.Select ?
|
||||
<Dropdown
|
||||
options={item.options.map(e => e.value)}
|
||||
onSelect={(val) => handleChange(i, val)}
|
||||
value={item.value}
|
||||
></Dropdown> :
|
||||
<Select onValueChange={(val) => handleChange(i, val)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{
|
||||
item.options.map(el => <SelectItem key={el.value} value={el.value}>{el.value}</SelectItem>)
|
||||
}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select> :
|
||||
item.type === VariableType.File ?
|
||||
<InputFileComponent
|
||||
isSSO
|
||||
@@ -90,6 +106,8 @@ export default function ChatReportForm({ flow, onStart }) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button size="sm" className="shengcheng-btn" onClick={handleStart}>{t('report.start')}</Button>
|
||||
{type === 'chat' && <Button size="sm" className="shengcheng-btn" onClick={handleStart}>{t('report.start')}</Button>}
|
||||
</div>
|
||||
};
|
||||
});
|
||||
|
||||
export default ChatReportForm
|
||||
@@ -10,7 +10,7 @@ import { generateUUID } from "../../utils";
|
||||
import SkillTemps from "../SkillPage/components/SkillTemps";
|
||||
import ChatPanne from "./components/ChatPanne";
|
||||
import { captureAndAlertRequestErrorHoc } from "../../controllers/request";
|
||||
import { useDebounce } from "../../util/hook";
|
||||
import { useDebounce, useTable } from "../../util/hook";
|
||||
import duihuaDel from "../../assets/chat/duihua-del.png";
|
||||
import robot from "../../assets/robot.png";
|
||||
import robot2 from "../../assets/robot2.png";
|
||||
@@ -18,6 +18,10 @@ import robot3 from "../../assets/robot3.png";
|
||||
import duihuaItemTop from "../../assets/chat/duihua-item-top.png";
|
||||
import duihuaItemJia from "../../assets/chat/duihua-item-+.png";
|
||||
import duihuaItemGuan from "../../assets/chat/duihua-item-x.png";
|
||||
import SkillChatSheet from "@/components/bs-comp/sheets/SkillChatSheet";
|
||||
import { TitleIconBg } from "@/components/bs-comp/cardComponent";
|
||||
import npcIcon from "../../assets/npc/npcIcon.png";
|
||||
import nengliIcon from "../../assets/npc/nengliIcon.png";
|
||||
|
||||
|
||||
export default function SkillChatPage() {
|
||||
@@ -25,54 +29,52 @@ export default function SkillChatPage() {
|
||||
const [face, setFace] = useState(true);
|
||||
|
||||
const { t } = useTranslation()
|
||||
const [selectChat, setSelelctChat] = useState<any>({
|
||||
id: '', chatId: '', type: ''
|
||||
})
|
||||
|
||||
const { flow: initFlow } = useContext(TabsContext);
|
||||
const [flow, setFlow] = useState<FlowType>(null)
|
||||
const [onlineFlows, setOnlineFlows] = useState([])
|
||||
useEffect(() => {
|
||||
readOnlineFlows().then(setOnlineFlows)
|
||||
}, [])
|
||||
const {
|
||||
data: onlineFlows,
|
||||
loading,
|
||||
search,
|
||||
} = useTable<FlowType>({}, (param) =>
|
||||
readOnlineFlows(param.page, param.keyword).then((res) => {
|
||||
return res;
|
||||
})
|
||||
);
|
||||
// 对话列表
|
||||
const { chatList, chatId, chatsRef, setChatId, addChat, deleteChat } = useChatList()
|
||||
const chatIdRef = useRef('')
|
||||
|
||||
// select flow
|
||||
const handlerSelectFlow = async (node: FlowType) => {
|
||||
const handlerSelectFlow = async (card) => {
|
||||
// 会话ID
|
||||
chatIdRef.current = generateUUID(32)
|
||||
setOpen(false)
|
||||
const _chatId = generateUUID(32)
|
||||
// setOpen(false)
|
||||
// add list
|
||||
addChat({
|
||||
"flow_name": node.name,
|
||||
"flow_description": node.description,
|
||||
"flow_id": node.id,
|
||||
"chat_id": chatIdRef.current,
|
||||
"flow_name": card.name,
|
||||
"flow_description": card.desc,
|
||||
"flow_id": card.id,
|
||||
"chat_id": _chatId,
|
||||
"create_time": "-",
|
||||
"update_time": "-"
|
||||
"update_time": "-",
|
||||
"flow_type": card.flow_type,
|
||||
"avatar_img": card.avatar_img,
|
||||
"avatar_color": card.avatar_color,
|
||||
})
|
||||
|
||||
const flow = await getFlowApi(node.id)
|
||||
setFlow(flow)
|
||||
setChatId(chatIdRef.current)
|
||||
setFace(false)
|
||||
setSelelctChat({ id: card.id, chatId: _chatId, type: card.flow_type })
|
||||
setChatId(_chatId)
|
||||
}
|
||||
|
||||
// select chat
|
||||
const handleSelectChat = useDebounce(async (chat) => {
|
||||
console.log('chat.id :>> ', chat);
|
||||
if (chat.chat_id === chatId) return
|
||||
|
||||
chatIdRef.current = chat.chat_id
|
||||
const flow = initFlow?.id === chat.flow_id ? initFlow : await getFlowApi(chat.flow_id)
|
||||
|
||||
// if (!flow) {
|
||||
// setInputState({ lock: true, errorCode: '1004' })
|
||||
// clearHistory()
|
||||
// return setFace(false)
|
||||
// }
|
||||
|
||||
setFlow(flow)
|
||||
setSelelctChat({ id: chat.flow_id, chatId: chat.chat_id, type: chat.flow_type })
|
||||
setChatId(chat.chat_id)
|
||||
setFace(false)
|
||||
}, 100, false)
|
||||
|
||||
|
||||
@@ -83,7 +85,7 @@ export default function SkillChatPage() {
|
||||
desc: t('chat.confirmDeleteChat'),
|
||||
onOk(next) {
|
||||
deleteChat(id);
|
||||
setFace(true)
|
||||
setSelelctChat({ id: '', chatId: '', type: '' })
|
||||
next()
|
||||
}
|
||||
})
|
||||
@@ -91,9 +93,16 @@ export default function SkillChatPage() {
|
||||
|
||||
|
||||
return <div className="flex">
|
||||
<div className="h-screen w-[288px] border-r xinDuiHua-box relative">
|
||||
<div className="xinDuiHua absolute">
|
||||
<div className="xinDuiHua-btn cursor-pointer" onClick={() => setOpen(true)}>{t('chat.newChat')}</div>
|
||||
<div className="h-screen w-[288px] relative">
|
||||
<div className="xinDuiHua absolute bg-[#000000] xinDuiHua-box">
|
||||
<SkillChatSheet onSelect={handlerSelectFlow}>
|
||||
<div id="newchat" className="xinDuiHua-btn cursor-pointer">
|
||||
{/* <PlusBoxIcon className="dark:hidden"></PlusBoxIcon> */}
|
||||
{/* <PlusBoxIconDark className="hidden dark:block"></PlusBoxIconDark> */}
|
||||
{t('chat.newChat')}
|
||||
</div>
|
||||
</SkillChatSheet>
|
||||
{/* <div className="xinDuiHua-btn cursor-pointer" onClick={() => setOpen(true)}>{t('chat.newChat')}</div> */}
|
||||
{/* <div className="xinDuiHua-del cursor-pointer">
|
||||
<img src={duihuaDel} alt=""/>
|
||||
</div> */}
|
||||
@@ -105,9 +114,10 @@ export default function SkillChatPage() {
|
||||
className={` group item xinDuiHua-list-item relative hover:xinDuiHua-list-active cursor-pointer dark:hover:xinDuiHua-list-active ${chatId === chat.chat_id && 'xinDuiHua-list-active dark:xinDuiHua-list-active'}`}
|
||||
onClick={() => handleSelectChat(chat)}>
|
||||
<div>
|
||||
{(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} alt=""/>}
|
||||
{/* {(chat.flow_id == "06b1d374-ba97-46e6-8782-c56dec8dcc17" || chat.flow_id == "ed8e21f6-9757-43d0-b076-8c6e81bb0580") && <img src={robot2} alt=""/>}
|
||||
{chat.flow_id == "ca214b41-2b73-4585-b172-bf1e546cf6ec" && <img src={robot3} alt=""/>}
|
||||
{(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} alt=""/>}
|
||||
{(chat.flow_id != "06b1d374-ba97-46e6-8782-c56dec8dcc17" && chat.flow_id != "ed8e21f6-9757-43d0-b076-8c6e81bb0580" && chat.flow_id != "ca214b41-2b73-4585-b172-bf1e546cf6ec") && <img src={robot} alt=""/>} */}
|
||||
<TitleIconBg className="w-[40px] h-[40px]" img={chat.avatar_img} id={chat.avatar_color ? chat.avatar_color : chat.flow_id} ><img src={chat.avatar_img ? chat.avatar_img : (chat.flow_type == "assistant" ? npcIcon : nengliIcon)} alt="" /></TitleIconBg>
|
||||
{/* <img src={robot} alt=""/> */}
|
||||
<div>
|
||||
<p>{chat.flow_name}</p>
|
||||
@@ -127,13 +137,7 @@ export default function SkillChatPage() {
|
||||
</div>
|
||||
</div>
|
||||
{/* chat */}
|
||||
{face
|
||||
? <div className="flex-1 chat-box h-screen overflow-hidden relative">
|
||||
<p className="text-center mt-[100px] text-sm text-gray-600">{t('chat.selectChat')}</p>
|
||||
</div>
|
||||
: <div className="flex-1 chat-box h-screen relative">
|
||||
{flow && <ChatPanne chatId={chatId} flow={flow} />}
|
||||
</div>}
|
||||
<ChatPanne data={selectChat}></ChatPanne>
|
||||
{/* 选择对话技能 */}
|
||||
<SkillTemps
|
||||
flows={onlineFlows}
|
||||
|
||||
@@ -232,23 +232,23 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
// if (chat.isSend) return chat.files.length ? <>
|
||||
// 发送消息
|
||||
if (chat.isSend) return <div className="flex flex-col items-end">
|
||||
<div className="flex items-center avatarZk">
|
||||
<p className="mr-[11px] text-[13px]">{userName}</p>
|
||||
<div className="flex items-start avatarZk">
|
||||
<div className="mr-[10px]">
|
||||
<div className="chat-end-zk">
|
||||
{chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>}
|
||||
{chat.message[chat.chatKey]}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!disabledReSend && <img src={btnEdit} onClick={() => !disabledReSend && onEdit(chat.message[chat.chatKey])} className="w-[20px] ml-[10px] cursor-pointer" alt=""/>}
|
||||
{!disabledReSend && <img src={btnReSend} onClick={() => !disabledReSend && onReSend(chat.message[chat.chatKey])} className="w-[20px] ml-[10px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[20px] ml-[10px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
{/* <p className="mr-[11px] text-[13px]">{userName}</p> */}
|
||||
<img src={robot} className="w-[30px]" alt=""/>
|
||||
</div>
|
||||
<div className="mt-[10px]">
|
||||
<div className="chat-end-zk">
|
||||
{chat.category === 'loading' && <span className="loading loading-spinner loading-xs mr-4 align-middle"></span>}
|
||||
{chat.message[chat.chatKey]}
|
||||
</div>
|
||||
|
||||
<div className='chat-end-btn'>
|
||||
{!disabledReSend && <img src={btnEdit} onClick={() => !disabledReSend && onEdit(chat.message[chat.chatKey])} className="w-[20px] ml-[10px] cursor-pointer" alt=""/>}
|
||||
{!disabledReSend && <img src={btnReSend} onClick={() => !disabledReSend && onReSend(chat.message[chat.chatKey])} className="w-[20px] ml-[10px] cursor-pointer" alt=""/>}
|
||||
{/* <img src={btnDel} className="w-[20px] ml-[10px] cursor-pointer" alt=""/> */}
|
||||
{/* {!showSearch && <Search size={18} className="cursor-pointer hover:text-blue-600 text-blue-400" onClick={() => onSearch(chat.message[chat.chatKey])}></Search>} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 文件 */ }
|
||||
// <div className="chat chat-end">
|
||||
@@ -286,31 +286,31 @@ export const ChatMessage = ({ chat, userName, disabledReSend, showSearch, onSour
|
||||
{/* <div className="chat-image avatar">
|
||||
<div className="w-[40px] h-[40px] rounded-full flex items-center justify-center" style={{ background: avatarColor }}><Bot color="#fff" size={28} /></div>
|
||||
</div> */}
|
||||
<div className="flex items-center avatarZk">
|
||||
<div className="flex items-start avatarZk">
|
||||
<img src={robot} className="w-[30px]" alt=""/>
|
||||
<p className="ml-[10px] text-[13px]">{userName}</p>
|
||||
{/* <p className="ml-[10px] text-[13px]">{userName}</p> */}
|
||||
<div ref={textRef} className={`min-h-8 min-w-[110px] max-w-[50vw] mt-[10px] ${chat.id && chat.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{chat.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{chat.receiver && <p className="text-blue-500 text-sm">@ {chat.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{chat.message.toString() && !chat.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!chat.id && chat.end && <Thumbs
|
||||
id={chat.id}
|
||||
data={chat.liked}
|
||||
onCopy={handleCopy}
|
||||
onDislike={onDislike}
|
||||
className="chat-start-btnM"
|
||||
></Thumbs>
|
||||
// className={`absolute w-full left-0 bottom-[8px] justify-end pr-5`}></Thumbs>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{/* {chat.sender && <div className="chat-header text-gray-400 text-sm">{chat.sender}</div>} */}
|
||||
<div ref={textRef} className={`min-h-8 min-w-[110px] max-w-[50vw] mt-[10px] ${chat.id && chat.end && 'pb-8'}`}>
|
||||
<div className="chat-start-zk relative">
|
||||
{chat.message.toString() ? mkdown : <span className="loading loading-ring loading-md"></span>}
|
||||
{/* @user */}
|
||||
{chat.receiver && <p className="text-blue-500 text-sm">@ {chat.receiver.user_name}</p>}
|
||||
{/* 光标 */}
|
||||
{chat.message.toString() && !chat.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>}
|
||||
</div>
|
||||
|
||||
{/* 赞 踩 */}
|
||||
{!!chat.id && chat.end && <Thumbs
|
||||
id={chat.id}
|
||||
data={chat.liked}
|
||||
onCopy={handleCopy}
|
||||
onDislike={onDislike}
|
||||
className="chat-start-btnM"
|
||||
></Thumbs>
|
||||
// className={`absolute w-full left-0 bottom-[8px] justify-end pr-5`}></Thumbs>
|
||||
}
|
||||
</div>
|
||||
{chat.source !== SourceType.NONE && chat.end && sourceContent(chat.source)}
|
||||
</div>
|
||||
};
|
||||
|
||||
942
src/pages/ChatAppPage/mobile/ChatPanneM copy.tsx
Normal file
@@ -0,0 +1,942 @@
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { ClipboardList, FileInput, FileText, Send, StopCircle } from "lucide-react";
|
||||
import { forwardRef, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ShadTooltip from "../../../components/ShadTooltipComponent";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { TabsContext } from "../../../contexts/tabsContext";
|
||||
import { getChatHistory, postBuildInit, postValidatePrompt } from "../../../controllers/API";
|
||||
import { Variable } from "../../../controllers/API/flow";
|
||||
import { sendAllProps } from "../../../types/api";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { FlowType, NodeType } from "../../../types/flow";
|
||||
import { validateNode } from "../../../utils";
|
||||
import { ChatMessage } from "./ChatMessageM";
|
||||
import ChatReportForm from "../components/ChatReportForm";
|
||||
import ResouceModal from "../components/ResouceModal";
|
||||
import ThumbsMessage from "../components/ThumbsMessage";
|
||||
import NpcInfo from "./NpcInfoM";
|
||||
import DuiHua from "./DuiHuaM";
|
||||
import PopUp from "./popUp";
|
||||
import Quote from "./quote";
|
||||
import { locationContext } from "../../../contexts/locationContext";
|
||||
import titIconL from "../../../assets/chatM/tit-icon-l.png";
|
||||
import titIconR from "../../../assets/chatM/tit-icon-r.png";
|
||||
import duihuaSend from "../../../assets/chat/duihua-send.png";
|
||||
|
||||
interface Iprops {
|
||||
chatId: string
|
||||
flow: FlowType
|
||||
queryString?: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
export default forwardRef(function ChatPanne({ chatId, flow, queryString, version = 'v1' }: Iprops,ref) {
|
||||
const { t } = useTranslation()
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
|
||||
const { isRoom, isForm, isReport, checkPrompt } = useFlowState(flow)
|
||||
|
||||
// build
|
||||
const build = useBuild(flow, chatId)
|
||||
// 消息列表
|
||||
const { messages, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll } = useMessages(chatId, flow)
|
||||
// ws通信
|
||||
const { stop, connectWS, begin: chating, checkReLinkWs, sendAll } = useWebsocket(chatId, flow, setChatHistory, queryString, version)
|
||||
// 停止状态
|
||||
const [isStop, setIsStop] = useState(true)
|
||||
// 输入框状态
|
||||
const { inputState, inputEmpty, inputDisabled, inputRef,
|
||||
formShow, setFormShow,
|
||||
setInputState, setInputEmpty, handleTextAreaHeight } = useInputState({ flow, chatId, chating, messages, isForm, isReport })
|
||||
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
// npc信息
|
||||
const [isNpcInfo, setIsNpcInfo] = useState(false)
|
||||
// 对话信息
|
||||
const [isDuiHua, setIsDuiHua] = useState(false)
|
||||
// 弹窗信息
|
||||
const [isPopUp, setIsPopUp] = useState(false)
|
||||
// 引用弹窗信息
|
||||
const [isQuote, setIsQuote] = useState(false)
|
||||
// 开始构建&切换初始化会话
|
||||
const initChat = async () => {
|
||||
await checkPrompt(flow)
|
||||
await build()
|
||||
const historyData = version === 'v1' ? await loadHistory() : (initGuide(), [])
|
||||
await connectWS({ setInputState, setIsStop, changeHistoryByScroll })
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
// 第一条消息,用来初始化会话
|
||||
sendAll({
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
inputs: {},
|
||||
flow_id: flow.id,
|
||||
chat_id: chatId
|
||||
})
|
||||
|
||||
changeHistoryByScroll.current = false
|
||||
// 自动聚焦
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 500);
|
||||
|
||||
const isNewChat = historyData.length === 0 || historyData[0].id === 9999
|
||||
setFormShow(isNewChat && isForm)
|
||||
}
|
||||
useEffect(() => {
|
||||
initChat()
|
||||
}, [flow])
|
||||
// document.documentElement.addEventListener(
|
||||
// 'touchstart',
|
||||
// function (event) {
|
||||
// if (event.touches.length > 1) {
|
||||
// event.preventDefault();
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// passive: false,
|
||||
// },
|
||||
// );
|
||||
// sendmsg user name
|
||||
const sendUserName = useMemo(() => {
|
||||
const node = flow.data.nodes.find(el => el.data.type === 'AutoGenUser')
|
||||
return node?.data.node.template['name'].value || ''
|
||||
}, [flow])
|
||||
|
||||
const handleSend = async () => {
|
||||
const msg = inputRef.current?.value
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ''
|
||||
inputRef.current.style.height = 'auto'
|
||||
}
|
||||
setInputEmpty(true)
|
||||
}, 100);
|
||||
|
||||
if (msg.trim() === '') return
|
||||
|
||||
setInputState({ lock: true, errorMsg: '' });
|
||||
let inputs = tabsState[flow.id].formKeysData.input_keys;
|
||||
const input = inputs.find((el: any) => !el.type)
|
||||
const inputKey = input ? Object.keys(input)[0] : '';
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: true,
|
||||
message: { ...input, [inputKey]: msg },
|
||||
chatKey: inputKey,
|
||||
thought: '',
|
||||
category: '',
|
||||
files: [],
|
||||
end: false,
|
||||
user_name: ""
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
|
||||
await checkReLinkWs(async () => {
|
||||
// await build()
|
||||
await connectWS({ setInputState, setIsStop, changeHistoryByScroll })
|
||||
})
|
||||
|
||||
const chatInfo = {
|
||||
chat_id: chatId,
|
||||
flow_id: flow.id,
|
||||
inputs: { ...input, [inputKey]: msg }
|
||||
}
|
||||
// @ts-ignore
|
||||
isRoom && chating ? sendAll({ action: "continue", ...chatInfo })
|
||||
: sendAll({
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
...chatInfo
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 报表请求
|
||||
const sendReport = (items: Variable[], str) => {
|
||||
let inputs = tabsState[flow.id].formKeysData.input_keys;
|
||||
const input = inputs.find((el: any) => !el.type)
|
||||
const inputKey = input ? Object.keys(input)[0] : '';
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: true,
|
||||
message: { ...input, [inputKey]: str },
|
||||
chatKey: inputKey,
|
||||
thought: '',
|
||||
category: '',
|
||||
files: [],
|
||||
end: false,
|
||||
user_name: ""
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
|
||||
const data = items.map(item => ({
|
||||
id: item.nodeId,
|
||||
name: item.name,
|
||||
file_path: item.type === 'file' ? item.value : '',
|
||||
value: item.type === 'file' ? '' : item.value
|
||||
}))
|
||||
|
||||
setIsStop(false)
|
||||
setFormShow(false)
|
||||
|
||||
sendAll({
|
||||
inputs: {
|
||||
...input,
|
||||
[inputKey]: str,
|
||||
data
|
||||
},
|
||||
chatHistory: messages,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
chat_id: chatId,
|
||||
flow_id: flow.id,
|
||||
});
|
||||
}
|
||||
|
||||
// 溯源
|
||||
const [souce, setSouce] = useState<ChatMessageType>(null)
|
||||
|
||||
const thumbRef = useRef(null)
|
||||
|
||||
return <div className="overflow-hidden relative duihua-chat">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<div className="absolute chatShareM-tit">
|
||||
<div></div>
|
||||
{/* <div className="cursor-pointer" onClick={() => setIsDuiHua(!isDuiHua)}>
|
||||
<img src={titIconL} alt=""/>
|
||||
</div> */}
|
||||
<p>{flow.name}</p>
|
||||
{/* <div className="cursor-pointer" onClick={() => setIsNpcInfo(!isNpcInfo)}>
|
||||
<img src={titIconR} alt=""/>
|
||||
</div> */}
|
||||
<div></div>
|
||||
</div>
|
||||
<div className="chata mt-[50px]" style={{ height: 'calc(100% - 50px)' }}>
|
||||
{/* 会话记录 */}
|
||||
<div ref={messagesRef} className={`chat-panne h-full overflow-y-scroll no-scrollbar px-[15px] ${isRoom || isReport ? 'pb-40' : 'pb-[60px]'}`}>
|
||||
{
|
||||
messages.map((c, i) => <ChatMessage
|
||||
key={c.id || i}
|
||||
userName={sendUserName}
|
||||
chat={c}
|
||||
disabledReSend={inputDisabled}
|
||||
showSearch={!!appConfig.dialogQuickSearch}
|
||||
onSource={() => setSouce(c)}
|
||||
onDislike={(chatId) => { thumbRef.current?.openModal(chatId) }}
|
||||
onReSend={(msg) => {
|
||||
inputRef.current.value = msg
|
||||
handleSend()
|
||||
}}
|
||||
onEdit={(msg) => { inputRef.current.value = msg; setInputEmpty(!msg) }}
|
||||
onSearch={(msg) => window.open(appConfig.dialogQuickSearch + encodeURIComponent(msg))}
|
||||
></ChatMessage>)
|
||||
}
|
||||
</div>
|
||||
{/* 输入框 */}
|
||||
<div className="absolute w-full bottom-0 duihua-input-box pb-[10px]">
|
||||
{/* <div className={`relative duihua-input
|
||||
${inputDisabled && 'bg-gray-200 dark:bg-gray-600'}`}> */}
|
||||
<div className={`relative duihua-input`}>
|
||||
<textarea id='input'
|
||||
ref={inputRef}
|
||||
disabled={inputDisabled} rows={1}
|
||||
className={`w-full resize-none border-none bg-transparent outline-none max-h-[160px]`}
|
||||
placeholder={(inputDisabled ? "当前处于回复中或不支持输入状态" : t('chat.inputPlaceholder'))}
|
||||
onInput={handleTextAreaHeight}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) handleSend()
|
||||
}}></textarea>
|
||||
<div className="absolute right-0 bottom-0 w-[42px] duihua-input-btn cursor-pointer">
|
||||
{/* <ShadTooltip content={t('chat.sendTooltip')}>
|
||||
<button disabled={inputEmpty || inputDisabled} className=" disabled:text-gray-400" onClick={handleSend}><Send /></button>
|
||||
</ShadTooltip> */}
|
||||
<div className="duihua-input-btn-send">
|
||||
<img src={duihuaSend} onClick={handleSend} alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
{/* {inputState.errorMsg && <div className="bg-gray-200 absolute top-0 left-0 w-full h-full text-center text-gray-400 align-middle pt-4">{inputState.errorMsg}</div>} */}
|
||||
{inputState.errorMsg && <div className="absolute top-0 left-0 w-full h-full text-center align-middle pt-4" style={{background:"#2E1212",color:"#FF6060"}}>{inputState.errorMsg}</div>}
|
||||
</div>
|
||||
<p className="mb-2 text-center text-gray-400 text-sm">{appConfig.dialogTips}</p>
|
||||
</div>
|
||||
</div>
|
||||
{(isRoom || isReport) && <div className=" absolute w-full flex justify-center bottom-32 pointer-events-none">
|
||||
<Button className="rounded-full pointer-events-auto" variant="outline" disabled={isStop} onClick={() => { setIsStop(true); stop(); }}><StopCircle className="mr-2" />Stop</Button>
|
||||
</div>}
|
||||
{/* 源文件类型 */}
|
||||
<ResouceModal chatId={chatId} open={!!souce} data={souce} setOpen={() => setSouce(null)}></ResouceModal>
|
||||
{/* 表单 */}
|
||||
{isForm && formShow && <ChatReportForm flow={flow} onStart={sendReport} />}
|
||||
{/* 踩 反馈 */}
|
||||
<ThumbsMessage ref={thumbRef}></ThumbsMessage>
|
||||
|
||||
<NpcInfo isNpcInfo={isNpcInfo} setIsNpcInfo={setIsNpcInfo}></NpcInfo>
|
||||
|
||||
<DuiHua isDuiHua={isDuiHua} setIsDuiHua={setIsDuiHua}></DuiHua>
|
||||
|
||||
<PopUp isPopUp={isPopUp} setIsPopUp={setIsPopUp}></PopUp>
|
||||
|
||||
<Quote isQuote={isQuote} setIsQuote={setIsQuote}></Quote>
|
||||
|
||||
</div>
|
||||
});
|
||||
/**
|
||||
* 输入框状态
|
||||
* 分析 flow状态
|
||||
* return 该技能含有表单、有报表、群聊
|
||||
* @returns
|
||||
*/
|
||||
const useInputState = ({ flow, chatId, chating, messages, isForm, isReport }) => {
|
||||
const { tabsState } = useContext(TabsContext);
|
||||
|
||||
const [inputState, setInputState] = useState({
|
||||
lock: false,
|
||||
errorMsg: ''
|
||||
})
|
||||
// 输入问答
|
||||
const inputRef = useRef(null)
|
||||
useEffect(() => {
|
||||
!chating && setTimeout(() => {
|
||||
// 对话结束自动聚焦
|
||||
inputRef.current?.focus()
|
||||
}, 1000);
|
||||
}, [chating])
|
||||
// input 滚动
|
||||
const [inputEmpty, setInputEmpty] = useState(true)
|
||||
useEffect(() => {
|
||||
setInputEmpty(true)
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
}, [chatId])
|
||||
|
||||
// 获取上传file input
|
||||
const fileInputs = useMemo(() => {
|
||||
return tabsState[flow.id]?.formKeysData?.input_keys?.filter((input: any) => input.type === 'file')
|
||||
}, [tabsState, flow])
|
||||
|
||||
const handleTextAreaHeight = (e) => {
|
||||
const textarea = e.target
|
||||
textarea.style.height = 'auto'
|
||||
textarea.style.height = textarea.scrollHeight + 'px'
|
||||
setInputEmpty(textarea.value.trim() === '')
|
||||
}
|
||||
// input disabled
|
||||
const inputDisabled = useMemo(() => {
|
||||
return inputState.lock
|
||||
// 表单 && 没回话或只有一个引导词
|
||||
|| (isForm && (messages.length === 0 || (messages.length === 1 && messages[0].id === 9999)))
|
||||
|| isReport
|
||||
}, [inputState, fileInputs, isReport])
|
||||
|
||||
// 表单收起
|
||||
const [formShow, setFormShow] = useState(true)
|
||||
return {
|
||||
inputState, inputEmpty, inputDisabled, inputRef,
|
||||
formShow, setFormShow,
|
||||
setInputState, setInputEmpty, handleTextAreaHeight
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* flow state
|
||||
* 分析 flow状态
|
||||
* return 该技能含有表单、有报表、群聊
|
||||
* @returns
|
||||
*/
|
||||
const useFlowState = (flow: FlowType) => {
|
||||
const flowSate = useMemo(() => {
|
||||
// 是否群聊
|
||||
const isRoom = !!flow.data?.nodes.find(node => node.data.type === "AutoGenChain")
|
||||
// 是否展示表单
|
||||
const isForm = !!flow.data?.nodes.find(node => ["VariableNode", "InputFileNode"].includes(node.data.type))
|
||||
// 是否报表
|
||||
const isReport = !!flow.data?.nodes.find(node => "Report" === node.data.type)
|
||||
return { isRoom, isForm, isReport }
|
||||
}, [flow])
|
||||
|
||||
// propmt类型补充自定义字段
|
||||
const checkPrompt = async (_flow) => {
|
||||
const params = _flow.data.nodes.map(node => {
|
||||
const temps = []
|
||||
const temp = node.data.node.template
|
||||
Object.keys(temp).map(key => {
|
||||
const { type, value } = temp[key]
|
||||
if (type === 'prompt' && !!value) !temps.length && temps.push({ name: key, template: value, data: node.data })
|
||||
})
|
||||
return temps
|
||||
}).flat()
|
||||
|
||||
const promises = params.map(param => {
|
||||
return postValidatePrompt(param.name, param.template, param.data.node).then(res => {
|
||||
if (res) param.data.node = res.frontend_node
|
||||
})
|
||||
})
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
return { ...flowSate, checkPrompt }
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息列表模块
|
||||
* 翻页、追加、历史
|
||||
* @returns
|
||||
*/
|
||||
const useMessages = (chatId, flow) => {
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const lastIdRef = useRef(0)
|
||||
// 控制开启自动随消息滚动(临时方案)
|
||||
const changeHistoryByScroll = useRef(false)
|
||||
|
||||
const loadIdRef = useRef('') // 记录最后一个加载的 chatId
|
||||
// 获取聊天记录
|
||||
const loadHistory = async (lastId?: number) => {
|
||||
loadIdRef.current = chatId
|
||||
|
||||
const res = await getChatHistory(flow.id, chatId, lastId ? 10 : 30, lastId)
|
||||
const hisData = res.map(item => {
|
||||
// let count = 0
|
||||
let { message, files, is_bot, intermediate_steps, ...other } = item
|
||||
try {
|
||||
message = message && message[0] === '{' ? JSON.parse(message.replace(/([\t\n"])/g, '\\$1').replace(/'/g, '"')) : message || ''
|
||||
} catch (e) {
|
||||
// 未考虑的情况暂不处理
|
||||
}
|
||||
return {
|
||||
...other,
|
||||
chatKey: typeof message === 'string' ? undefined : Object.keys(message)[0],
|
||||
end: true,
|
||||
files: files ? JSON.parse(files) : [],
|
||||
isSend: !is_bot,
|
||||
message,
|
||||
thought: intermediate_steps,
|
||||
noAccess: true
|
||||
}
|
||||
})
|
||||
lastIdRef.current = hisData[hisData.length - 1]?.id || lastIdRef.current || 0 // 记录最后一个id
|
||||
|
||||
let historyData = []
|
||||
if (lastId) {
|
||||
historyData = [...hisData.reverse(), ...chatHistory]
|
||||
} else if (loadIdRef.current === chatId) { // 保证同一会话
|
||||
historyData = hisData.reverse()
|
||||
}
|
||||
setChatHistory(historyData)
|
||||
const pageSize = historyData.length < 30 ? 30 : 10 // 先偷懒
|
||||
if (hisData.length < pageSize) initGuide()
|
||||
return historyData
|
||||
}
|
||||
|
||||
const loadLock = useRef(false)
|
||||
const currentIdRef = useRef(0)
|
||||
const loadNextPage = async () => {
|
||||
if (loadLock.current) return
|
||||
if (currentIdRef.current === lastIdRef.current) return // 最后一个相同表示聊天记录已到顶
|
||||
loadLock.current = true
|
||||
currentIdRef.current = lastIdRef.current
|
||||
changeHistoryByScroll.current = true
|
||||
await loadHistory(currentIdRef.current)
|
||||
loadLock.current = false
|
||||
// 滚动 hack TODO 滚动翻页设计
|
||||
setTimeout(() => {
|
||||
changeHistoryByScroll.current = false
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const initGuide = () => {
|
||||
const guideMsg = {
|
||||
"category": "system",
|
||||
"chat_id": chatId,
|
||||
"end": true,
|
||||
"create_time": "",
|
||||
"extra": "{}",
|
||||
"files": [],
|
||||
"flow_id": flow.id,
|
||||
"id": 9999,
|
||||
"thought": flow.guide_word,
|
||||
"is_bot": true,
|
||||
"liked": 0,
|
||||
"message": '',
|
||||
"receiver": null,
|
||||
"remark": null,
|
||||
"sender": "",
|
||||
"solved": 0,
|
||||
isSend: false,
|
||||
"source": 0,
|
||||
"type": "end",
|
||||
"update_time": "",
|
||||
noAccess: true,
|
||||
"user_id": 0
|
||||
}
|
||||
flow.guide_word && setChatHistory((chatHistory) =>
|
||||
chatHistory[0]?.id === 9999 ? chatHistory : [guideMsg, ...chatHistory]
|
||||
)
|
||||
}
|
||||
|
||||
// 消息滚动
|
||||
const messagesRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (messagesRef.current && !changeHistoryByScroll.current) { // 滚动加载不触发
|
||||
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
|
||||
}
|
||||
}, [chatHistory, changeHistoryByScroll]);
|
||||
|
||||
// 消息滚动加载
|
||||
useEffect(() => {
|
||||
function handleScroll() {
|
||||
if (messagesRef.current.scrollTop <= 30) {
|
||||
loadNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
messagesRef.current?.addEventListener('scroll', handleScroll);
|
||||
return () => messagesRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}, [messagesRef.current, chatHistory, chatId]);
|
||||
|
||||
return {
|
||||
messages: chatHistory, messagesRef, loadHistory, setChatHistory, initGuide, changeHistoryByScroll
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket 通信
|
||||
* 建立连接、重连、断开、接收、发送
|
||||
* @returns
|
||||
*/
|
||||
const useWebsocket = (chatId, flow, setChatHistory, queryString, version) => {
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
// 接收ws状态
|
||||
const [begin, setBegin] = useState(false)
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
const chatIdRef = useRef(chatId);
|
||||
useEffect(() => {
|
||||
chatIdRef.current = chatId;
|
||||
}, [chatId])
|
||||
|
||||
function heartbeat() {
|
||||
if (!ws.current) return;
|
||||
if (ws.current.readyState !== 1) return;
|
||||
ws.current.send("heartbeat");
|
||||
setTimeout(heartbeat, 30000);
|
||||
}
|
||||
|
||||
function getWebSocketUrl(flowId, isDevelopment = false) {
|
||||
const token = localStorage.getItem("ws_token") || '';
|
||||
|
||||
const isSecureProtocol = window.location.protocol === "https:";
|
||||
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
|
||||
const host = appConfig.websocketHost || window.location.host // isDevelopment ? "localhost:7860" : window.location.host;
|
||||
const chatEndpoint = version === 'v1' ? `/api/v1/chat/${flowId}?type=L1&chat_id=${chatId}&t=${token}`
|
||||
: `/api/v2/chat/ws/${flowId}?type=L1&chat_id=${chatId}${queryString}&t=${token}`
|
||||
|
||||
return `${webSocketProtocol}://${host}${chatEndpoint}`;
|
||||
}
|
||||
|
||||
const newChatStart = useRef(false) // 处理当前会话上下文丢失,阻止上一次打字机效果
|
||||
// 自动重连次数
|
||||
const tryReLinkCount = useRef(0)
|
||||
const reConnect = (params) => {
|
||||
if (tryReLinkCount.current <= 3) {
|
||||
connectWS(params)
|
||||
tryReLinkCount.current++
|
||||
} else {
|
||||
console.warn('超过最大重试次数 :>> ');
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
tryReLinkCount.current = 0
|
||||
newChatStart.current = true
|
||||
}, [chatId])
|
||||
|
||||
function connectWS(params) {
|
||||
const { setInputState, setIsStop, changeHistoryByScroll } = params
|
||||
if (ws.current) return Promise.resolve('ok');
|
||||
|
||||
// 连接断开重链接
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
const urlWs = getWebSocketUrl(
|
||||
flow.id,
|
||||
process.env.NODE_ENV === "development"
|
||||
);
|
||||
const newWs = new WebSocket(urlWs);
|
||||
newWs.onopen = () => {
|
||||
console.log("WebSocket connection established!");
|
||||
res('ok')
|
||||
// heartbeat()
|
||||
};
|
||||
newWs.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.chat_id !== chatIdRef.current) return
|
||||
console.log('newChatStart.current :>> ', newChatStart.current);
|
||||
|
||||
const errorMsg = data.category === 'error' ? data.intermediate_steps : ''
|
||||
|
||||
if (newChatStart.current) {
|
||||
if (data.type === 'close') {
|
||||
newChatStart.current = false
|
||||
return setInputState({ lock: false, errorMsg })
|
||||
} else {
|
||||
return setInputState({ lock: true, errorMsg })
|
||||
}
|
||||
}
|
||||
// 异常类型处理,提示
|
||||
if (errorMsg) return setInputState({ lock: true, errorMsg })
|
||||
|
||||
handleWsMessage({ data, setIsStop, setInputState, changeHistoryByScroll });
|
||||
// get chat history
|
||||
// 群聊@自己时,开启input
|
||||
if (data.type === 'end' && data.receiver?.is_self) {
|
||||
setInputState({ lock: false, errorMsg: '' })
|
||||
}
|
||||
};
|
||||
newWs.onclose = (event) => {
|
||||
ws.current = null
|
||||
|
||||
handleOnClose({ event, setIsStop, setInputState });
|
||||
// reConnect(params)
|
||||
};
|
||||
newWs.onerror = (ev) => {
|
||||
ws.current = null
|
||||
|
||||
console.error('链接异常error', ev);
|
||||
setIsStop(true)
|
||||
|
||||
setErrorData({
|
||||
title: `${t('chat.networkError')}:`,
|
||||
list: [
|
||||
t('chat.networkErrorList1'),
|
||||
t('chat.networkErrorList2'),
|
||||
t('chat.networkErrorList3')
|
||||
],
|
||||
});
|
||||
reConnect(params)
|
||||
};
|
||||
ws.current = newWs;
|
||||
console.log('newWs :>> ', newWs);
|
||||
} catch (error) {
|
||||
console.error('创建链接异常', error);
|
||||
rej(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var isStream = false;
|
||||
function handleWsMessage({ data, setIsStop, setInputState, changeHistoryByScroll }) {
|
||||
if (Array.isArray(data) && data.length) return
|
||||
if (data.type === "begin") {
|
||||
setBegin(true)
|
||||
setIsStop(false)
|
||||
changeHistoryByScroll.current = false
|
||||
}
|
||||
if (data.type === "close") {
|
||||
setBegin(false)
|
||||
setIsStop(true)
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
changeHistoryByScroll.current = true
|
||||
}
|
||||
if (data.type === "start") {
|
||||
setChatHistory((old) => {
|
||||
let newChat = cloneDeep(old);
|
||||
newChat.push({
|
||||
isSend: false,
|
||||
message: '',
|
||||
chatKey: '',
|
||||
thought: data.intermediate_steps || '',
|
||||
category: data.category || '',
|
||||
files: [],
|
||||
end: false
|
||||
})
|
||||
return newChat
|
||||
});
|
||||
isStream = true;
|
||||
}
|
||||
if (data.type === "stream" && isStream) {
|
||||
updateLastMessage({ str: data.message, thought: data.intermediate_steps });
|
||||
}
|
||||
if (data.type === "end") {
|
||||
updateLastMessage({
|
||||
...data,
|
||||
str: data.message,
|
||||
files: data.files || null,
|
||||
end: true,
|
||||
thought: data.intermediate_steps || '',
|
||||
cate: data.category || '',
|
||||
messageId: data.message_id,
|
||||
noAccess: false,
|
||||
liked: 0
|
||||
});
|
||||
|
||||
isStream = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateLastMessage({ str, thought = '', end = false, files = [], cate = '', messageId = 0, source = false, noAccess = false, ...data }: {
|
||||
str: string;
|
||||
messageId?: number
|
||||
thought?: string;
|
||||
cate?: string;
|
||||
end?: boolean;
|
||||
files?: Array<any>;
|
||||
source?: boolean
|
||||
noAccess?: boolean
|
||||
}) {
|
||||
setChatHistory((old) => {
|
||||
const newChats = [...old]
|
||||
// console.log('newchats :>> ', newChats);
|
||||
let chatsLen = newChats.length
|
||||
const prevChat = newChats[chatsLen - 2]
|
||||
// hack 过滤重复最后消息
|
||||
if (end
|
||||
&& str
|
||||
&& chatsLen > 1
|
||||
&& str === prevChat.message
|
||||
// && data.sender === prevChat.sender
|
||||
&& !prevChat.thought) {
|
||||
newChats.splice(chatsLen - 2, 1) // 删上一条
|
||||
chatsLen = newChats.length
|
||||
}
|
||||
// 更新
|
||||
const lastChat = newChats[chatsLen - 1]
|
||||
const newLastChat = {
|
||||
...newChats[chatsLen - 1],
|
||||
...data,
|
||||
id: messageId,
|
||||
message: lastChat.message + str,
|
||||
thought: lastChat.thought + (thought ? `${thought}\n` : ''),
|
||||
files,
|
||||
category: cate,
|
||||
source,
|
||||
noAccess,
|
||||
end
|
||||
}
|
||||
newChats[chatsLen - 1] = newLastChat
|
||||
// start - end 之间没有内容删除load
|
||||
if (end && !(newLastChat.files.length || newLastChat.thought || newLastChat.message)) {
|
||||
newChats.pop()
|
||||
}
|
||||
return newChats;
|
||||
});
|
||||
}
|
||||
|
||||
// 发送ws
|
||||
async function sendAll(data: sendAllProps) {
|
||||
try {
|
||||
if (ws.current) {
|
||||
if (JSON.stringify(data.inputs) !== '{}') {
|
||||
newChatStart.current = false
|
||||
}
|
||||
ws.current.send(JSON.stringify(data));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error sending the message",
|
||||
list: [error.message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 处理主动断开
|
||||
function handleOnClose({ event, setIsStop, setInputState }) {
|
||||
console.error('链接手动断开 event :>> ', event);
|
||||
setIsStop(true)
|
||||
setBegin(false)
|
||||
|
||||
if ([1005, 1008].includes(event.code)) {
|
||||
console.warn('即将废弃 :>> ');
|
||||
setInputState({ lock: true, errorMsg: event.reason });
|
||||
} else {
|
||||
if (event.reason) {
|
||||
setErrorData({ title: event.reason });
|
||||
// setChatHistory((old) => {
|
||||
// let newChat = cloneDeep(old);
|
||||
// if (newChat.length) {
|
||||
// newChat[newChat.length - 1].end = true;
|
||||
// }
|
||||
// newChat.push({ end: true, message: `${t('chat.connectionbreakTip')}${event.reason}`, isSend: false, chatKey: '', files: [] });
|
||||
// return newChat
|
||||
// })
|
||||
}
|
||||
setInputState({ lock: false, errorMsg: '' });
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// destory
|
||||
return () => {
|
||||
// close prev connection
|
||||
if (ws.current) {
|
||||
switch (ws.current.readyState) {
|
||||
case WebSocket.OPEN:
|
||||
console.warn('前端主动关闭1')
|
||||
ws.current.close()
|
||||
; break;
|
||||
case WebSocket.CONNECTING:
|
||||
ws.current.onopen = () => {
|
||||
console.warn('前端主动关闭2')
|
||||
ws.current.close()
|
||||
};
|
||||
}
|
||||
ws.current = null
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 检测并重连
|
||||
const checkReLinkWs = async (reConnect) => {
|
||||
if (ws.current) return true
|
||||
// 重连
|
||||
// 上一条加loading
|
||||
setChatHistory((old) => {
|
||||
let newChat = [...old];
|
||||
newChat[newChat.length - 1].category = 'loading';
|
||||
return newChat;
|
||||
});
|
||||
await reConnect()
|
||||
// 链接成功
|
||||
// 上一条去loading
|
||||
setChatHistory((old) => {
|
||||
let newChat = [...old];
|
||||
newChat[newChat.length - 1].category = '';
|
||||
return newChat;
|
||||
});
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
try {
|
||||
if (ws) {
|
||||
ws.current.send(JSON.stringify({
|
||||
"action": "stop"
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error stop the message",
|
||||
list: [error.message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { begin, stop: handleStop, checkReLinkWs, sendAll, connectWS }
|
||||
}
|
||||
|
||||
/**
|
||||
* build flow
|
||||
* 校验每个节点,展示进度及结果;返回input_keys;end_of_stream断开链接
|
||||
* 主要校验节点并设置更新setTabsState的 formKeysData
|
||||
* @returns
|
||||
*/
|
||||
const useBuild = (flow: FlowType, chatId: string) => {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { setTabsState } = useContext(TabsContext);
|
||||
const { t } = useTranslation()
|
||||
|
||||
// SSE 服务端推送
|
||||
async function streamNodeData(flow: FlowType, chatId: string) {
|
||||
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
||||
const { flowId } = await postBuildInit(flow, chatId);
|
||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||
let validationResults = [];
|
||||
let finished = false;
|
||||
let buildEnd = false
|
||||
const apiUrl = `/api/v1/build/stream/${flowId}?chat_id=${chatId}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// If the event is parseable, return
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
const parsedData = JSON.parse(event.data);
|
||||
// if the event is the end of the stream, close the connection
|
||||
if (parsedData.end_of_stream) {
|
||||
eventSource.close(); // 结束关闭链接
|
||||
buildEnd = true
|
||||
return;
|
||||
} else if (parsedData.log) {
|
||||
// If the event is a log, log it
|
||||
// setSuccessData({ title: parsedData.log });
|
||||
} else if (parsedData.input_keys) {
|
||||
setTabsState((old) => {
|
||||
return {
|
||||
...old,
|
||||
[flowId]: {
|
||||
...old[flowId],
|
||||
formKeysData: parsedData,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// setProgress(parsedData.progress);
|
||||
validationResults.push(parsedData.valid);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error: any) => {
|
||||
console.error("EventSource failed:", error);
|
||||
eventSource.close();
|
||||
if (error.data) {
|
||||
const parsedData = JSON.parse(error.data);
|
||||
setErrorData({ title: parsedData.error });
|
||||
}
|
||||
};
|
||||
// Step 3: Wait for the stream to finish
|
||||
while (!finished) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
finished = buildEnd // validationResults.length === flow.data.nodes.length;
|
||||
}
|
||||
// Step 4: Return true if all nodes are valid, false otherwise
|
||||
return validationResults.every((result) => result);
|
||||
}
|
||||
|
||||
// 延时器
|
||||
async function enforceMinimumLoadingTime(
|
||||
startTime: number,
|
||||
minimumLoadingTime: number
|
||||
) {
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const remainingTime = minimumLoadingTime - elapsedTime;
|
||||
|
||||
if (remainingTime > 0) {
|
||||
return new Promise((resolve) => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBuild() {
|
||||
try {
|
||||
const errors = flow.data.nodes.flatMap((n: NodeType) => validateNode(n, flow.data.edges))
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: t('chat.buildError'),
|
||||
list: errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const minimumLoadingTime = 200; // in milliseconds
|
||||
const startTime = Date.now();
|
||||
|
||||
await streamNodeData(flow, chatId);
|
||||
await enforceMinimumLoadingTime(startTime, minimumLoadingTime); // 至少等200ms, 再继续(强制最小load时间)
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
return handleBuild
|
||||
}
|
||||