feat: resource authorization
parent
3f565602a4
commit
af8172a7b5
|
|
@ -4,6 +4,15 @@ import type { pageRequest } from '@/api/type/common'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
const prefix = '/workspace'
|
const prefix = '/workspace'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取成员列表
|
||||||
|
* @query 参数
|
||||||
|
*/
|
||||||
|
const getUserList: (workspace_id: String) => Promise<Result<any>> = (workspace_id) => {
|
||||||
|
return get(`${prefix}/${workspace_id}/user_list`)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取资源权限
|
* 获取资源权限
|
||||||
* @query 参数
|
* @query 参数
|
||||||
|
|
@ -40,4 +49,5 @@ const putResourceAuthorization: (workspace_id: String, body: any) => Promise<Res
|
||||||
export default {
|
export default {
|
||||||
getResourceAuthorization,
|
getResourceAuthorization,
|
||||||
putResourceAuthorization,
|
putResourceAuthorization,
|
||||||
|
getUserList
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export enum AuthorizationEnum {
|
export enum AuthorizationEnum {
|
||||||
MANAGE = 'MANAGE',
|
MANAGE = 'MANAGE',
|
||||||
USE = 'USE',
|
USE = 'USE',
|
||||||
DATASET = 'DATASET',
|
KNOWLEDGE = 'KNOWLEDGE',
|
||||||
APPLICATION = 'APPLICATION'
|
APPLICATION = 'APPLICATION'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
export default {
|
||||||
|
noHistory: '暂无历史记录',
|
||||||
|
createChat: '新建对话',
|
||||||
|
history: '历史记录',
|
||||||
|
only20history: '仅显示最近 20 条对话',
|
||||||
|
question_count: '条提问',
|
||||||
|
exportRecords: '导出聊天记录',
|
||||||
|
chatId: '对话 ID',
|
||||||
|
userInput: '用户输入',
|
||||||
|
quote: '引用',
|
||||||
|
download: '点击下载文件',
|
||||||
|
transcribing: '转文字中',
|
||||||
|
passwordValidator: {
|
||||||
|
title: '请输入密码打开链接',
|
||||||
|
errorMessage1: '密码不能为空',
|
||||||
|
errorMessage2: '密码错误'
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
play: '点击播放',
|
||||||
|
pause: '停止',
|
||||||
|
regeneration: '换个答案',
|
||||||
|
like: '赞同',
|
||||||
|
cancelLike: '取消赞同',
|
||||||
|
oppose: '反对',
|
||||||
|
cancelOppose: '取消反对',
|
||||||
|
continue: '继续',
|
||||||
|
stopChat: '停止回答',
|
||||||
|
startChat: '开始对话'
|
||||||
|
},
|
||||||
|
tip: {
|
||||||
|
error500Message: '抱歉,当前正在维护,无法提供服务,请稍后再试!',
|
||||||
|
errorIdentifyMessage: '无法识别用户身份',
|
||||||
|
errorLimitMessage: '抱歉,您的提问已达到最大限制,请明天再来吧!',
|
||||||
|
answerMessage: '抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。',
|
||||||
|
stopAnswer: '已停止回答',
|
||||||
|
answerLoading: '回答中',
|
||||||
|
recorderTip: `<p>该功能需要使用麦克风,浏览器禁止不安全页面录音,解决方案如下:<br/>
|
||||||
|
1、可开启 https 解决;<br/>
|
||||||
|
2、若无 https 配置则需要修改浏览器安全配置,Chrome 设置如下:<br/>
|
||||||
|
(1) 地址栏输入chrome://flags/#unsafely-treat-insecure-origin-as-secure;<br/>
|
||||||
|
(2) 将 http 站点配置在文本框中,例如: http://127.0.0.1:8080。</p>`,
|
||||||
|
recorderError: '录音失败',
|
||||||
|
confirm: '我知道了',
|
||||||
|
requiredMessage: '请填写所有必填字段',
|
||||||
|
inputParamMessage1: '请在URL中填写参数',
|
||||||
|
inputParamMessage2: '的值',
|
||||||
|
prologueMessage: '抱歉,当前正在维护,无法提供服务,请稍后再试!'
|
||||||
|
},
|
||||||
|
inputPlaceholder: {
|
||||||
|
speaking: '说话中',
|
||||||
|
recorderLoading: '转文字中',
|
||||||
|
default: '请输入问题'
|
||||||
|
},
|
||||||
|
uploadFile: {
|
||||||
|
label: '上传文件',
|
||||||
|
most: '最多',
|
||||||
|
limit: '个,每个文件限制',
|
||||||
|
fileType: '文件类型',
|
||||||
|
tipMessage: '请在文件上传配置中选择文件类型',
|
||||||
|
limitMessage1: '最多上传',
|
||||||
|
limitMessage2: '个文件',
|
||||||
|
sizeLimit: '单个文件大小不能超过',
|
||||||
|
imageMessage: '请解析图片内容',
|
||||||
|
errorMessage: '上传失败'
|
||||||
|
},
|
||||||
|
executionDetails: {
|
||||||
|
title: '执行详情',
|
||||||
|
paramOutputTooltip: '每个文档仅支持预览500字',
|
||||||
|
audioFile: '语音文件',
|
||||||
|
searchContent: '检索内容',
|
||||||
|
searchResult: '检索结果',
|
||||||
|
conditionResult: '判断结果',
|
||||||
|
currentChat: '本次对话',
|
||||||
|
answer: 'AI 回答',
|
||||||
|
replyContent: '回复内容',
|
||||||
|
textContent: '文本内容',
|
||||||
|
input: '输入',
|
||||||
|
output: '输出',
|
||||||
|
rerankerContent: '重排内容',
|
||||||
|
rerankerResult: '重排结果',
|
||||||
|
paragraph: '分段',
|
||||||
|
noSubmit: '用户未提交',
|
||||||
|
errMessage: '错误日志'
|
||||||
|
},
|
||||||
|
KnowledgeSource: {
|
||||||
|
title: '知识来源',
|
||||||
|
referenceParagraph: '引用分段',
|
||||||
|
consume: '消耗tokens',
|
||||||
|
consumeTime: '耗时'
|
||||||
|
},
|
||||||
|
paragraphSource: {
|
||||||
|
title: '知识库引用',
|
||||||
|
question: '用户问题',
|
||||||
|
optimizationQuestion: '优化后问题'
|
||||||
|
},
|
||||||
|
editTitle: '编辑标题'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default {
|
||||||
|
quickCreatePlaceholder: '快速创建空白文档',
|
||||||
|
quickCreateName: '文档名称',
|
||||||
|
noData: '无匹配数据',
|
||||||
|
loading: '加载中',
|
||||||
|
noMore: '到底啦!',
|
||||||
|
selectParagraph: {
|
||||||
|
title: '选择分段',
|
||||||
|
error: '仅执行未成功分段',
|
||||||
|
all: '全部分段'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
// import components from './components'
|
import components from './components'
|
||||||
import views from './views'
|
import views from './views'
|
||||||
import theme from './theme'
|
import theme from './theme'
|
||||||
import layout from './layout'
|
import layout from './layout'
|
||||||
import dynamicsForm from './dynamics-form'
|
import dynamicsForm from './dynamics-form'
|
||||||
import common from './common'
|
import common from './common'
|
||||||
// import chat from './ai-chat'
|
import chat from './ai-chat'
|
||||||
export default {
|
export default {
|
||||||
lang: '简体中文',
|
lang: '简体中文',
|
||||||
zhCn,
|
zhCn,
|
||||||
|
|
@ -14,4 +14,6 @@ export default {
|
||||||
layout,
|
layout,
|
||||||
dynamicsForm,
|
dynamicsForm,
|
||||||
common,
|
common,
|
||||||
|
chat,
|
||||||
|
components,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
export default {
|
||||||
|
title: '概览',
|
||||||
|
appInfo: {
|
||||||
|
header: '应用信息',
|
||||||
|
publicAccessLink: '公开访问链接',
|
||||||
|
openText: '开',
|
||||||
|
closeText: '关',
|
||||||
|
copyLinkText: '复制链接',
|
||||||
|
refreshLinkText: '刷新链接',
|
||||||
|
demo: '演示',
|
||||||
|
embedInWebsite: '嵌入第三方',
|
||||||
|
accessControl: '访问限制',
|
||||||
|
displaySetting: '显示设置',
|
||||||
|
apiAccessCredentials: 'API 访问凭据',
|
||||||
|
apiKey: 'API Key',
|
||||||
|
refreshToken: {
|
||||||
|
msgConfirm1: '是否重新生成公开访问链接?',
|
||||||
|
msgConfirm2:
|
||||||
|
'重新生成公开访问链接会影响嵌入第三方脚本变更,需要将新脚本重新嵌入第三方,请谨慎操作!',
|
||||||
|
refreshSuccess: '刷新成功'
|
||||||
|
},
|
||||||
|
|
||||||
|
APIKeyDialog: {
|
||||||
|
saveSettings: '保存设置',
|
||||||
|
msgConfirm1: '是否删除API Key',
|
||||||
|
msgConfirm2: '删除API Key后将无法恢复,请确认是否删除?',
|
||||||
|
enabledSuccess: '已启用',
|
||||||
|
disabledSuccess: '已禁用'
|
||||||
|
},
|
||||||
|
EditAvatarDialog: {
|
||||||
|
title: '应用头像',
|
||||||
|
customizeUpload: '自定义上传',
|
||||||
|
upload: '上传',
|
||||||
|
default: '默认logo',
|
||||||
|
custom: '自定义',
|
||||||
|
sizeTip: '建议尺寸 32*32,支持 JPG、PNG、GIF,大小不超过 10 MB',
|
||||||
|
fileSizeExceeded: '文件大小超过 10 MB',
|
||||||
|
uploadImagePrompt: '请上传一张图片'
|
||||||
|
},
|
||||||
|
EmbedDialog: {
|
||||||
|
fullscreenModeTitle: '全屏模式',
|
||||||
|
copyInstructions: '复制以下代码进行嵌入',
|
||||||
|
floatingModeTitle: '浮窗模式',
|
||||||
|
mobileModeTitle: '移动端模式'
|
||||||
|
},
|
||||||
|
LimitDialog: {
|
||||||
|
showSourceLabel: '显示知识来源',
|
||||||
|
clientQueryLimitLabel: '每个客户端提问限制',
|
||||||
|
timesDays: '次/天',
|
||||||
|
authentication: '身份验证',
|
||||||
|
authenticationValue: '验证密码',
|
||||||
|
whitelistLabel: '白名单',
|
||||||
|
whitelistPlaceholder:
|
||||||
|
'请输入允许嵌入第三方的源地址,一行一个,如:\nhttp://127.0.0.1:5678\nhttps://dataease.io'
|
||||||
|
},
|
||||||
|
SettingAPIKeyDialog: {
|
||||||
|
dialogTitle: '设置',
|
||||||
|
allowCrossDomainLabel: '允许跨域地址',
|
||||||
|
crossDomainPlaceholder:
|
||||||
|
'请输入允许的跨域地址,开启后不输入跨域地址则不限制。\n跨域地址一行一个,如:\nhttp://127.0.0.1:5678 \nhttps://dataease.io'
|
||||||
|
},
|
||||||
|
SettingDisplayDialog: {
|
||||||
|
dialogTitle: '显示设置',
|
||||||
|
languageLabel: '语言',
|
||||||
|
showSourceLabel: '显示知识来源',
|
||||||
|
showExecutionDetail: '显示执行详情',
|
||||||
|
restoreDefault: '恢复默认',
|
||||||
|
customThemeColor: '自定义主题色',
|
||||||
|
headerTitleFontColor: '头部标题字体颜色',
|
||||||
|
default: '默认',
|
||||||
|
askUserAvatar: '提问用户头像',
|
||||||
|
replace: '替换',
|
||||||
|
imageMessage: '建议尺寸 32*32,支持 JPG、PNG、GIF,大小不超过 10 MB',
|
||||||
|
AIAvatar: 'AI 回复头像',
|
||||||
|
display: '显示',
|
||||||
|
floatIcon: '浮窗入口图标',
|
||||||
|
iconDefaultPosition: '图标默认位置',
|
||||||
|
iconPosition: {
|
||||||
|
left: '左',
|
||||||
|
right: '右',
|
||||||
|
bottom: '下',
|
||||||
|
top: '上'
|
||||||
|
},
|
||||||
|
draggablePosition: '可拖拽位置',
|
||||||
|
showHistory: '显示历史记录',
|
||||||
|
displayGuide: '显示引导图(浮窗模式)',
|
||||||
|
disclaimer: '免责声明',
|
||||||
|
disclaimerValue: '「以上内容均由 AI 生成,仅供参考和借鉴」'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
monitor: {
|
||||||
|
monitoringStatistics: '监控统计',
|
||||||
|
customRange: '自定义范围',
|
||||||
|
startDatePlaceholder: '开始时间',
|
||||||
|
endDatePlaceholder: '结束时间',
|
||||||
|
pastDayOptions: {
|
||||||
|
past7Days: '过去7天',
|
||||||
|
past30Days: '过去30天',
|
||||||
|
past90Days: '过去90天',
|
||||||
|
past183Days: '过去半年',
|
||||||
|
other: '自定义'
|
||||||
|
},
|
||||||
|
charts: {
|
||||||
|
customerTotal: '用户总数',
|
||||||
|
customerNew: '用户新增数',
|
||||||
|
queryCount: '提问次数',
|
||||||
|
tokensTotal: 'Tokens 总数',
|
||||||
|
userSatisfaction: '用户满意度',
|
||||||
|
approval: '赞同',
|
||||||
|
disapproval: '反对'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
export default {
|
||||||
|
node: '节点',
|
||||||
|
nodeName: '节点名称',
|
||||||
|
baseComponent: '基础组件',
|
||||||
|
nodeSetting: '节点设置',
|
||||||
|
workflow: '工作流',
|
||||||
|
searchBar: {
|
||||||
|
placeholder: '按名称搜索'
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
previewVersion: '预览版本:',
|
||||||
|
saveTime: '保存时间:'
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
restoreVersion: '恢复版本',
|
||||||
|
restoreCurrentVersion: '恢复此版本',
|
||||||
|
addComponent: '添加组件',
|
||||||
|
public: '发布',
|
||||||
|
releaseHistory: '发布历史',
|
||||||
|
autoSave: '自动保存',
|
||||||
|
latestRelease: '最近发布',
|
||||||
|
copyParam: '复制参数',
|
||||||
|
debug: '调试',
|
||||||
|
exit: '直接退出',
|
||||||
|
exitSave: '保存并退出'
|
||||||
|
},
|
||||||
|
tip: {
|
||||||
|
publicSuccess: '发布成功',
|
||||||
|
noData: '没有找到相关结果',
|
||||||
|
nameMessage: '名字不能为空!',
|
||||||
|
onlyRight: '只允许从右边的锚点连出',
|
||||||
|
notRecyclable: '不可循环连线',
|
||||||
|
onlyLeft: '只允许连接左边的锚点',
|
||||||
|
applicationNodeError: '该应用不可用',
|
||||||
|
functionNodeError: '该函数不可用',
|
||||||
|
repeatedNodeError: '节点名称已存在!',
|
||||||
|
cannotCopy: '不能被复制',
|
||||||
|
copyError: '已复制节点',
|
||||||
|
paramErrorMessage: '参数已存在: ',
|
||||||
|
saveMessage: '当前的更改尚未保存,是否保存后退出?'
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
confirmTitle: '确定删除该节点?',
|
||||||
|
deleteMessage: '节点不允许删除'
|
||||||
|
},
|
||||||
|
control: {
|
||||||
|
zoomOut: '缩小',
|
||||||
|
zoomIn: '放大',
|
||||||
|
fitView: '适应',
|
||||||
|
retract: '收起全部节点',
|
||||||
|
extend: '展开全部节点',
|
||||||
|
beautify: '一键美化'
|
||||||
|
},
|
||||||
|
variable: {
|
||||||
|
label: '变量',
|
||||||
|
global: '全局变量',
|
||||||
|
Referencing: '引用变量',
|
||||||
|
ReferencingRequired: '引用变量必填',
|
||||||
|
ReferencingError: '引用变量错误',
|
||||||
|
NoReferencing: '不存在的引用变量',
|
||||||
|
placeholder: '请选择变量'
|
||||||
|
},
|
||||||
|
condition: {
|
||||||
|
title: '执行条件',
|
||||||
|
front: '前置',
|
||||||
|
AND: '所有',
|
||||||
|
OR: '任一',
|
||||||
|
text: '连线节点执行完,执行当前节点'
|
||||||
|
},
|
||||||
|
validate: {
|
||||||
|
startNodeRequired: '开始节点必填',
|
||||||
|
startNodeOnly: '开始节点只能有一个',
|
||||||
|
baseNodeRequired: '基本信息节点必填',
|
||||||
|
baseNodeOnly: '基本信息节点只能有一个',
|
||||||
|
notInWorkFlowNode: '未在流程中的节点',
|
||||||
|
noNextNode: '不存在的下一个节点',
|
||||||
|
nodeUnavailable: '节点不可用',
|
||||||
|
needConnect1: '节点的',
|
||||||
|
needConnect2: '分支需要连接',
|
||||||
|
cannotEndNode: '节点不能当做结束节点'
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
startNode: {
|
||||||
|
label: '开始',
|
||||||
|
question: '用户问题',
|
||||||
|
currentTime: '当前时间'
|
||||||
|
},
|
||||||
|
baseNode: {
|
||||||
|
label: '基本信息',
|
||||||
|
appName: {
|
||||||
|
label: '应用名称'
|
||||||
|
},
|
||||||
|
appDescription: {
|
||||||
|
label: '应用描述'
|
||||||
|
},
|
||||||
|
fileUpload: {
|
||||||
|
label: '文件上传',
|
||||||
|
tooltip: '开启后,问答页面会显示上传文件的按钮。'
|
||||||
|
},
|
||||||
|
FileUploadSetting: {
|
||||||
|
title: '文件上传设置',
|
||||||
|
maxFiles: '单次上传最多文件数',
|
||||||
|
fileLimit: '每个文件最大(MB)',
|
||||||
|
fileUploadType: {
|
||||||
|
label: '上传的文件类型',
|
||||||
|
documentText: '需要使用“文档内容提取”节点解析文档内容',
|
||||||
|
imageText: '需要使用“视觉模型”节点解析图片内容',
|
||||||
|
audioText: '需要使用“语音转文本”节点解析音频内容',
|
||||||
|
otherText: '需要自行解析该类型文件'
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
aiChatNode: {
|
||||||
|
label: 'AI 对话',
|
||||||
|
text: '与 AI 大模型进行对话',
|
||||||
|
answer: 'AI 回答内容',
|
||||||
|
returnContent: {
|
||||||
|
label: '返回内容',
|
||||||
|
tooltip: `关闭后该节点的内容则不输出给用户。
|
||||||
|
如果你想让用户看到该节点的输出内容,请打开开关。`
|
||||||
|
},
|
||||||
|
defaultPrompt: '已知信息',
|
||||||
|
think: '思考过程'
|
||||||
|
},
|
||||||
|
searchDatasetNode: {
|
||||||
|
label: '知识库检索',
|
||||||
|
text: '关联知识库,查找与问题相关的分段',
|
||||||
|
paragraph_list: '检索结果的分段列表',
|
||||||
|
is_hit_handling_method_list: '满足直接回答的分段列表',
|
||||||
|
result: '检索结果',
|
||||||
|
directly_return: '满足直接回答的分段内容',
|
||||||
|
searchParam: '检索参数',
|
||||||
|
searchQuestion: {
|
||||||
|
label: '检索问题',
|
||||||
|
placeholder: '请选择检索问题',
|
||||||
|
requiredMessage: '请选择检索问题'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
questionNode: {
|
||||||
|
label: '问题优化',
|
||||||
|
text: '根据历史聊天记录优化完善当前问题,更利于匹配知识库分段',
|
||||||
|
result: '问题优化结果',
|
||||||
|
defaultPrompt1: `根据上下文优化和完善用户问题`,
|
||||||
|
defaultPrompt2: `请输出一个优化后的问题。`,
|
||||||
|
systemDefault: '你是一个问题优化大师'
|
||||||
|
},
|
||||||
|
conditionNode: {
|
||||||
|
label: '判断器',
|
||||||
|
text: '根据不同条件执行不同的节点',
|
||||||
|
branch_name: '分支名称',
|
||||||
|
conditions: {
|
||||||
|
label: '条件',
|
||||||
|
info: '符合以下',
|
||||||
|
requiredMessage: '请选择条件'
|
||||||
|
},
|
||||||
|
valueMessage: '请输入值',
|
||||||
|
addCondition: '添加条件',
|
||||||
|
addBranch: '添加分支'
|
||||||
|
},
|
||||||
|
replyNode: {
|
||||||
|
label: '指定回复',
|
||||||
|
text: '指定回复内容,引用变量会转换为字符串进行输出',
|
||||||
|
content: '内容',
|
||||||
|
replyContent: {
|
||||||
|
label: '回复内容',
|
||||||
|
custom: '自定义',
|
||||||
|
reference: '引用变量'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rerankerNode: {
|
||||||
|
label: '多路召回',
|
||||||
|
text: '使用重排模型对多个知识库的检索结果进行二次召回',
|
||||||
|
result_list: '重排结果列表',
|
||||||
|
result: '重排结果',
|
||||||
|
rerankerContent: {
|
||||||
|
label: '重排内容',
|
||||||
|
requiredMessage: '请选择重排内容'
|
||||||
|
},
|
||||||
|
higher: '高于',
|
||||||
|
ScoreTooltip: 'Score越高相关性越强。',
|
||||||
|
max_paragraph_char_number: '最大引用字符数',
|
||||||
|
reranker_model: {
|
||||||
|
label: '重排模型',
|
||||||
|
placeholder: '请选择重排模型'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formNode: {
|
||||||
|
label: '表单收集',
|
||||||
|
text: '在问答过程中用于收集用户信息,可以根据收集到表单数据执行后续流程',
|
||||||
|
form_content_format1: '你好,请先填写下面表单内容:',
|
||||||
|
form_content_format2: '填写后请点击【提交】按钮进行提交。',
|
||||||
|
form_data: '表单全部内容',
|
||||||
|
formContent: {
|
||||||
|
label: '表单输出内容',
|
||||||
|
requiredMessage: '请表单输出内容',
|
||||||
|
tooltip: '设置执行该节点输出的内容,{ form } 为表单的占位符。'
|
||||||
|
},
|
||||||
|
formAllContent: '表单全部内容',
|
||||||
|
formSetting: '表单配置'
|
||||||
|
},
|
||||||
|
documentExtractNode: {
|
||||||
|
label: '文档内容提取',
|
||||||
|
text: '提取文档中的内容',
|
||||||
|
content: '文档内容'
|
||||||
|
},
|
||||||
|
imageUnderstandNode: {
|
||||||
|
label: '图片理解',
|
||||||
|
text: '识别出图片中的对象、场景等信息回答用户问题',
|
||||||
|
answer: 'AI 回答内容',
|
||||||
|
model: {
|
||||||
|
label: '视觉模型',
|
||||||
|
requiredMessage: '请选择视觉模型'
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
label: '选择图片',
|
||||||
|
requiredMessage: '请选择图片'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
variableAssignNode: {
|
||||||
|
label: '变量赋值',
|
||||||
|
text: '更新全局变量的值',
|
||||||
|
assign: '赋值'
|
||||||
|
},
|
||||||
|
mcpNode: {
|
||||||
|
label: 'MCP 调用',
|
||||||
|
text: '通过SSE/Streamable HTTP方式执行MCP服务中的工具',
|
||||||
|
getToolsSuccess: '获取工具成功',
|
||||||
|
getTool: '获取工具',
|
||||||
|
tool: '工具',
|
||||||
|
toolParam: '工具参数',
|
||||||
|
mcpServerTip: '请输入JSON格式的MCP服务器配置',
|
||||||
|
mcpToolTip: '请选择工具',
|
||||||
|
configLabel: 'MCP Server Config (仅支持SSE/Streamable HTTP调用方式)'
|
||||||
|
},
|
||||||
|
imageGenerateNode: {
|
||||||
|
label: '图片生成',
|
||||||
|
text: '根据提供的文本内容生成图片',
|
||||||
|
answer: 'AI 回答内容',
|
||||||
|
model: {
|
||||||
|
label: '图片生成模型',
|
||||||
|
requiredMessage: '请选择图片生成模型'
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
label: '提示词(正向)',
|
||||||
|
tooltip: '正向提示词,用来描述生成图像中期望包含的元素和视觉特点'
|
||||||
|
},
|
||||||
|
negative_prompt: {
|
||||||
|
label: '提示词(负向)',
|
||||||
|
tooltip: '反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制。',
|
||||||
|
placeholder: '请描述不想生成的图片内容,比如:颜色、血腥内容'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
speechToTextNode: {
|
||||||
|
label: '语音转文本',
|
||||||
|
text: '将音频通过语音识别模型转换为文本',
|
||||||
|
stt_model: {
|
||||||
|
label: '语音识别模型'
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
label: '选择语音文件',
|
||||||
|
placeholder: '请选择语音文件'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textToSpeechNode: {
|
||||||
|
label: '文本转语音',
|
||||||
|
text: '将文本通过语音合成模型转换为音频',
|
||||||
|
tts_model: {
|
||||||
|
label: '语音识别模型'
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
label: '选择文本内容'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
functionNode: {
|
||||||
|
label: '自定义函数',
|
||||||
|
text: '通过执行自定义脚本,实现数据处理'
|
||||||
|
},
|
||||||
|
applicationNode: {
|
||||||
|
label: '应用节点'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compare: {
|
||||||
|
is_null: '为空',
|
||||||
|
is_not_null: '不为空',
|
||||||
|
contain: '包含',
|
||||||
|
not_contain: '不包含',
|
||||||
|
eq: '等于',
|
||||||
|
ge: '大于等于',
|
||||||
|
gt: '大于',
|
||||||
|
le: '小于等于',
|
||||||
|
lt: '小于',
|
||||||
|
len_eq: '长度等于',
|
||||||
|
len_ge: '长度大于等于',
|
||||||
|
len_gt: '长度大于',
|
||||||
|
len_le: '长度小于等于',
|
||||||
|
len_lt: '长度小于',
|
||||||
|
is_true: '为真',
|
||||||
|
is_not_true: '不为真'
|
||||||
|
},
|
||||||
|
FileUploadSetting: {}
|
||||||
|
}
|
||||||
|
|
@ -8,17 +8,13 @@ import userManage from './user-manage'
|
||||||
import resourceAuthorization from './resource-authorization'
|
import resourceAuthorization from './resource-authorization'
|
||||||
import application from './application'
|
import application from './application'
|
||||||
import problem from './problem'
|
import problem from './problem'
|
||||||
|
import applicationOverview from './application-overview'
|
||||||
|
import applicationWorkflow from './application-workflow'
|
||||||
// import notFound from './404'
|
// import notFound from './404'
|
||||||
|
|
||||||
// import applicationOverview from './application-overview'
|
|
||||||
|
|
||||||
// import user from './user'
|
|
||||||
// import team from './team'
|
|
||||||
|
|
||||||
// import paragraph from './paragraph'
|
// import paragraph from './paragraph'
|
||||||
|
|
||||||
// import log from './log'
|
// import log from './log'
|
||||||
// import applicationWorkflow from './application-workflow'
|
|
||||||
|
|
||||||
// import operateLog from './operate-log'
|
// import operateLog from './operate-log'
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -32,16 +28,12 @@ export default {
|
||||||
resourceAuthorization,
|
resourceAuthorization,
|
||||||
application,
|
application,
|
||||||
problem,
|
problem,
|
||||||
|
applicationOverview,
|
||||||
|
applicationWorkflow,
|
||||||
// notFound,
|
// notFound,
|
||||||
|
|
||||||
// applicationOverview,
|
|
||||||
|
|
||||||
// user,
|
|
||||||
// team,
|
|
||||||
// paragraph,
|
// paragraph,
|
||||||
|
|
||||||
// log,
|
// log,
|
||||||
// applicationWorkflow,
|
|
||||||
|
|
||||||
// operateLog
|
// operateLog
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
// import Layout from '@/layout/layout-template/DetailLayout.vue'
|
||||||
|
import { ComplexPermission } from '@/utils/permission/type'
|
||||||
|
|
||||||
|
const applicationRouter = {
|
||||||
|
path: '/application',
|
||||||
|
name: 'application',
|
||||||
|
meta: { title: 'views.application.title', permission: 'APPLICATION:READ' },
|
||||||
|
redirect: '/application',
|
||||||
|
component: () => import('@/layout/layout-template/SimpleLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/application',
|
||||||
|
name: 'application-index',
|
||||||
|
meta: { title: '应用主页', activeMenu: '/application' },
|
||||||
|
component: () => import('@/views/application/index.vue')
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// path: '/application/:id/:type',
|
||||||
|
// name: 'ApplicationDetail',
|
||||||
|
// meta: { title: '应用详情', activeMenu: '/application' },
|
||||||
|
// component: Layout,
|
||||||
|
// hidden: true,
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: 'overview',
|
||||||
|
// name: 'AppOverview',
|
||||||
|
// meta: {
|
||||||
|
// icon: 'app-all-menu',
|
||||||
|
// iconActive: 'app-all-menu-active',
|
||||||
|
// title: 'views.applicationOverview.title',
|
||||||
|
// active: 'overview',
|
||||||
|
// parentPath: '/application/:id/:type',
|
||||||
|
// parentName: 'ApplicationDetail'
|
||||||
|
// },
|
||||||
|
// component: () => import('@/views/application-overview/index.vue')
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: 'setting',
|
||||||
|
// name: 'AppSetting',
|
||||||
|
// meta: {
|
||||||
|
// icon: 'app-setting',
|
||||||
|
// iconActive: 'app-setting-active',
|
||||||
|
// title: 'common.setting',
|
||||||
|
// active: 'setting',
|
||||||
|
// parentPath: '/application/:id/:type',
|
||||||
|
// parentName: 'ApplicationDetail'
|
||||||
|
// },
|
||||||
|
// component: () => import('@/views/application/ApplicationSetting.vue')
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: 'access',
|
||||||
|
// name: 'AppAccess',
|
||||||
|
// meta: {
|
||||||
|
// icon: 'app-access',
|
||||||
|
// iconActive: 'app-access-active',
|
||||||
|
// title: 'views.application.applicationAccess.title',
|
||||||
|
// active: 'access',
|
||||||
|
// parentPath: '/application/:id/:type',
|
||||||
|
// parentName: 'ApplicationDetail',
|
||||||
|
// permission: new ComplexPermission([], ['x-pack'], 'OR')
|
||||||
|
// },
|
||||||
|
// component: () => import('@/views/application/ApplicationAccess.vue')
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: 'hit-test',
|
||||||
|
// name: 'AppHitTest',
|
||||||
|
// meta: {
|
||||||
|
// icon: 'app-hit-test',
|
||||||
|
// title: 'views.application.hitTest.title',
|
||||||
|
// active: 'hit-test',
|
||||||
|
// parentPath: '/application/:id/:type',
|
||||||
|
// parentName: 'ApplicationDetail'
|
||||||
|
// },
|
||||||
|
// component: () => import('@/views/hit-test/index.vue')
|
||||||
|
// },
|
||||||
|
// // {
|
||||||
|
// // path: 'log',
|
||||||
|
// // name: 'Log',
|
||||||
|
// // meta: {
|
||||||
|
// // icon: 'app-document',
|
||||||
|
// // iconActive: 'app-document-active',
|
||||||
|
// // title: 'views.log.title',
|
||||||
|
// // active: 'log',
|
||||||
|
// // parentPath: '/application/:id/:type',
|
||||||
|
// // parentName: 'ApplicationDetail'
|
||||||
|
// // },
|
||||||
|
// // component: () => import('@/views/log/index.vue')
|
||||||
|
// // }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default applicationRouter
|
||||||
|
|
@ -10,6 +10,20 @@ export const routes: Array<RouteRecordRaw> = [
|
||||||
children: [...rolesRoutes],
|
children: [...rolesRoutes],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 高级编排
|
||||||
|
{
|
||||||
|
path: '/application/:id/workflow',
|
||||||
|
name: 'ApplicationWorkflow',
|
||||||
|
meta: { activeMenu: '/application' },
|
||||||
|
component: () => import('@/views/application-workflow/index.vue'),
|
||||||
|
},
|
||||||
|
// 对话
|
||||||
|
{
|
||||||
|
path: '/chat/:accessToken',
|
||||||
|
name: 'Chat',
|
||||||
|
component: () => import('@/views/chat/index.vue'),
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
|
|
|
||||||
|
|
@ -409,3 +409,11 @@ h5 {
|
||||||
background: var(--el-color-primary);
|
background: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
内容部分 自适应高度
|
||||||
|
*/
|
||||||
|
.main-calc-height {
|
||||||
|
height: var(--app-main-height);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
title="API Key"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="800"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
align-center
|
||||||
|
>
|
||||||
|
<el-button type="primary" class="mb-16" @click="createApiKey">
|
||||||
|
{{ $t('common.create') }}
|
||||||
|
</el-button>
|
||||||
|
<el-table :data="apiKey" class="mb-16" :loading="loading" height="420">
|
||||||
|
<el-table-column prop="secret_key" label="API Key">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="vertical-middle lighter break-all">
|
||||||
|
{{ row.secret_key }}
|
||||||
|
</span>
|
||||||
|
<el-button type="primary" text @click="copyClick(row.secret_key)">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.status.label')" width="70">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div @click.stop>
|
||||||
|
<el-switch
|
||||||
|
size="small"
|
||||||
|
v-model="row.is_active"
|
||||||
|
:before-change="() => changeState(row)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="name" :label="$t('common.createDate')" width="170">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ datetimeFormat(row.create_time) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.operation')" align="left" width="90">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="mr-4">
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.setting')" placement="top">
|
||||||
|
<el-button type="primary" text @click.stop="settingApiKey(row)">
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
|
||||||
|
<el-button type="primary" text @click="deleteApiKey(row)">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<SettingAPIKeyDialog ref="SettingAPIKeyDialogRef" @refresh="refresh" />
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { copyClick } from '@/utils/clipboard'
|
||||||
|
import overviewApi from '@/api/application-overview'
|
||||||
|
import SettingAPIKeyDialog from './SettingAPIKeyDialog.vue'
|
||||||
|
import { datetimeFormat } from '@/utils/time'
|
||||||
|
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route
|
||||||
|
|
||||||
|
const emit = defineEmits(['addData'])
|
||||||
|
|
||||||
|
const SettingAPIKeyDialogRef = ref()
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const apiKey = ref<any>(null)
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
apiKey.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function settingApiKey(row: any) {
|
||||||
|
SettingAPIKeyDialogRef.value.open(row, 'APPLICATION')
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteApiKey(row: any) {
|
||||||
|
MsgConfirm(
|
||||||
|
// @ts-ignore
|
||||||
|
`${t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm1')}: ${row.secret_key}?`,
|
||||||
|
t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm2'),
|
||||||
|
{
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
cancelButtonText: t('common.cancel'),
|
||||||
|
confirmButtonClass: 'danger'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
overviewApi.delAPIKey(id as string, row.id, loading).then(() => {
|
||||||
|
MsgSuccess(t('common.deleteSuccess'))
|
||||||
|
getApiKeyList()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeState(row: any) {
|
||||||
|
const obj = {
|
||||||
|
is_active: !row.is_active
|
||||||
|
}
|
||||||
|
const str = obj.is_active
|
||||||
|
? t('views.applicationOverview.appInfo.APIKeyDialog.enabledSuccess')
|
||||||
|
: t('views.applicationOverview.appInfo.APIKeyDialog.disabledSuccess')
|
||||||
|
overviewApi
|
||||||
|
.putAPIKey(id as string, row.id, obj, loading)
|
||||||
|
.then((res) => {
|
||||||
|
MsgSuccess(str)
|
||||||
|
getApiKeyList()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createApiKey() {
|
||||||
|
overviewApi.postAPIKey(id as string, loading).then((res) => {
|
||||||
|
getApiKeyList()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
getApiKeyList()
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiKeyList() {
|
||||||
|
overviewApi.getAPIKey(id as string, loading).then((res) => {
|
||||||
|
apiKey.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
getApiKeyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.applicationOverview.appInfo.SettingDisplayDialog.dialogTitle')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
width="550"
|
||||||
|
>
|
||||||
|
<el-form label-position="top" ref="displayFormRef" :model="form">
|
||||||
|
<el-form-item>
|
||||||
|
<span>{{
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.languageLabel')
|
||||||
|
}}</span>
|
||||||
|
<el-select v-model="form.language" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in langList"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-space direction="vertical" alignment="start">
|
||||||
|
<el-checkbox
|
||||||
|
v-model="form.show_source"
|
||||||
|
:label="
|
||||||
|
isWorkFlow(detail.type)
|
||||||
|
? $t('views.applicationOverview.appInfo.SettingDisplayDialog.showExecutionDetail')
|
||||||
|
: $t('views.applicationOverview.appInfo.SettingDisplayDialog.showSourceLabel')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-space>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit(displayFormRef)" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import type { FormInstance, FormRules, UploadFiles } from 'element-plus'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
|
import { MsgSuccess, MsgError } from '@/utils/message'
|
||||||
|
import { getBrowserLang, langList, t } from '@/locales'
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const displayFormRef = ref()
|
||||||
|
const form = ref<any>({
|
||||||
|
show_source: false,
|
||||||
|
language: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const detail = ref<any>(null)
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
form.value = {
|
||||||
|
show_source: false,
|
||||||
|
language: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const open = (data: any, content: any) => {
|
||||||
|
detail.value = content
|
||||||
|
form.value.show_source = data.show_source
|
||||||
|
form.value.language = data.language
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
applicationApi.putAccessToken(id as string, form.value, loading).then((res) => {
|
||||||
|
emit('refresh')
|
||||||
|
// @ts-ignore
|
||||||
|
MsgSuccess(t('common.settingSuccess'))
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.applicationOverview.appInfo.EditAvatarDialog.title')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
width="550"
|
||||||
|
>
|
||||||
|
<el-radio-group v-model="radioType" class="radio-block mb-16">
|
||||||
|
<el-radio value="default">
|
||||||
|
<p>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.default') }}</p>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="detail?.name"
|
||||||
|
:name="detail?.name"
|
||||||
|
pinyinColor
|
||||||
|
class="mt-8 mb-8"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</el-radio>
|
||||||
|
<el-radio value="custom">
|
||||||
|
<p>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.customizeUpload') }}</p>
|
||||||
|
<div class="flex mt-8">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="fileURL"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
class="mr-16"
|
||||||
|
>
|
||||||
|
<img :src="fileURL" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
action="#"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/jpeg, image/png, image/gif"
|
||||||
|
:on-change="onChange"
|
||||||
|
>
|
||||||
|
<el-button icon="Upload" :disabled="radioType !== 'custom'">{{
|
||||||
|
$t('views.applicationOverview.appInfo.EditAvatarDialog.upload')
|
||||||
|
}}</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
<div class="el-upload__tip info mt-8">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.sizeTip') }}
|
||||||
|
</div>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="submit" :loading="loading">
|
||||||
|
{{ $t('common.save') }}</el-button
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import overviewApi from '@/api/application-overview'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { MsgSuccess, MsgError } from '@/utils/message'
|
||||||
|
import { defaultIcon, isAppIcon } from '@/utils/common'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
const { application } = useStore()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id } //应用id
|
||||||
|
} = route
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const iconFile = ref<any>(null)
|
||||||
|
const fileURL = ref<any>(null)
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const detail = ref<any>(null)
|
||||||
|
const radioType = ref('default')
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
iconFile.value = null
|
||||||
|
fileURL.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (data: any) => {
|
||||||
|
radioType.value = isAppIcon(data.icon) ? 'custom' : 'default'
|
||||||
|
fileURL.value = isAppIcon(data.icon) ? data.icon : null
|
||||||
|
detail.value = cloneDeep(data)
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (file: any) => {
|
||||||
|
//1、判断文件大小是否合法,文件限制不能大于10MB
|
||||||
|
const isLimit = file?.size / 1024 / 1024 < 10
|
||||||
|
if (!isLimit) {
|
||||||
|
// @ts-ignore
|
||||||
|
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.fileSizeExceeded'))
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
iconFile.value = file
|
||||||
|
fileURL.value = URL.createObjectURL(file.raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
if (radioType.value === 'default') {
|
||||||
|
application.asyncPutApplication(id as string, { icon: defaultIcon }, loading).then((res) => {
|
||||||
|
emit('refresh')
|
||||||
|
MsgSuccess(t('common.saveSuccess'))
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
} else if (radioType.value === 'custom' && iconFile.value) {
|
||||||
|
let fd = new FormData()
|
||||||
|
fd.append('file', iconFile.value.raw)
|
||||||
|
overviewApi.putAppIcon(id as string, fd, loading).then((res: any) => {
|
||||||
|
emit('refresh')
|
||||||
|
MsgSuccess(t('common.saveSuccess'))
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.uploadImagePrompt'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.applicationOverview.appInfo.embedInWebsite')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="900"
|
||||||
|
class="embed-dialog"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="8">
|
||||||
|
<div class="border">
|
||||||
|
<p class="title p-16 bold">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.EmbedDialog.fullscreenModeTitle') }}
|
||||||
|
</p>
|
||||||
|
<img src="@/assets/window1.png" alt="" class="ml-8" height="150" />
|
||||||
|
<div class="code layout-bg border-t p-8">
|
||||||
|
<div class="flex-between p-8">
|
||||||
|
<span class="bold">{{
|
||||||
|
$t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')
|
||||||
|
}}</span>
|
||||||
|
<el-button text @click="copyClick(source1)">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-scrollbar height="150" always>
|
||||||
|
<div class="pre-wrap p-8 pt-0">
|
||||||
|
{{ source1 }}
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<div class="border">
|
||||||
|
<p class="title p-16 bold">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.EmbedDialog.mobileModeTitle') }}
|
||||||
|
</p>
|
||||||
|
<img src="@/assets/window3.png" alt="" class="ml-8" height="150" />
|
||||||
|
<div class="code layout-bg border-t p-8">
|
||||||
|
<div class="flex-between p-8">
|
||||||
|
<span class="bold">{{
|
||||||
|
$t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')
|
||||||
|
}}</span>
|
||||||
|
<el-button text @click="copyClick(source3)">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-scrollbar height="150" always>
|
||||||
|
<div class="pre-wrap p-8 pt-0">
|
||||||
|
{{ source3 }}
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<div class="border">
|
||||||
|
<p class="title p-16 bold">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.EmbedDialog.floatingModeTitle') }}
|
||||||
|
</p>
|
||||||
|
<img src="@/assets/window2.png" alt="" class="ml-8" height="150" />
|
||||||
|
<div class="code layout-bg border-t p-8">
|
||||||
|
<div class="flex-between p-8">
|
||||||
|
<span class="bold">{{
|
||||||
|
$t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')
|
||||||
|
}}</span>
|
||||||
|
<el-button text @click="copyClick(source2)">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-scrollbar height="150" always>
|
||||||
|
<div class="pre-wrap p-8 pt-0">
|
||||||
|
{{ source2 }}
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { copyClick } from '@/utils/clipboard'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
|
||||||
|
const { application } = useStore()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: Object,
|
||||||
|
apiInputParams: String
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['addData'])
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
|
||||||
|
const source1 = ref('')
|
||||||
|
|
||||||
|
const source2 = ref('')
|
||||||
|
const source3 = ref('')
|
||||||
|
|
||||||
|
const urlParams1 = computed(() => (props.apiInputParams ? '?' + props.apiInputParams : ''))
|
||||||
|
const urlParams2 = computed(() => (props.apiInputParams ? '&' + props.apiInputParams : ''))
|
||||||
|
const urlParams3 = computed(() =>
|
||||||
|
props.apiInputParams ? '?mode=mobile&' + props.apiInputParams : '?mode=mobile'
|
||||||
|
)
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
source1.value = ''
|
||||||
|
source2.value = ''
|
||||||
|
source3.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (val: string) => {
|
||||||
|
source1.value = `<iframe
|
||||||
|
src="${application.location + val + urlParams1.value}"
|
||||||
|
style="width: 100%; height: 100%;"
|
||||||
|
frameborder="0"
|
||||||
|
allow="microphone">
|
||||||
|
</iframe>
|
||||||
|
`
|
||||||
|
|
||||||
|
source2.value = `<script
|
||||||
|
async
|
||||||
|
defer
|
||||||
|
src="${window.location.origin}/api/application/embed?protocol=${window.location.protocol.replace(
|
||||||
|
':',
|
||||||
|
''
|
||||||
|
)}&host=${window.location.host}&token=${val}${urlParams2.value}">
|
||||||
|
<\/script>
|
||||||
|
`
|
||||||
|
source3.value = `<iframe
|
||||||
|
src="${application.location + val + urlParams3.value}"
|
||||||
|
style="width: 100%; height: 100%;"
|
||||||
|
frameborder="0"
|
||||||
|
allow="microphone">
|
||||||
|
</iframe>
|
||||||
|
`
|
||||||
|
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.embed-dialog {
|
||||||
|
.title {
|
||||||
|
color: var(--app-text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
color: var(--app-text-color) !important;
|
||||||
|
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: pre;
|
||||||
|
height: 188px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.applicationOverview.appInfo.accessControl')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
width="650"
|
||||||
|
>
|
||||||
|
<el-form label-position="top" ref="limitFormRef" :model="form">
|
||||||
|
<!-- <el-form-item
|
||||||
|
:label="$t('views.applicationOverview.appInfo.LimitDialog.showSourceLabel')"
|
||||||
|
@click.prevent
|
||||||
|
>
|
||||||
|
<el-switch size="small" v-model="form.show_source"></el-switch>
|
||||||
|
</el-form-item> -->
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationOverview.appInfo.LimitDialog.clientQueryLimitLabel')"
|
||||||
|
>
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.access_num"
|
||||||
|
:min="0"
|
||||||
|
:step="1"
|
||||||
|
:max="10000"
|
||||||
|
:value-on-clear="0"
|
||||||
|
controls-position="right"
|
||||||
|
style="width: 268px"
|
||||||
|
step-strictly
|
||||||
|
/>
|
||||||
|
<span class="ml-4">{{
|
||||||
|
$t('views.applicationOverview.appInfo.LimitDialog.timesDays')
|
||||||
|
}}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 身份验证 -->
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationOverview.appInfo.LimitDialog.authentication')"
|
||||||
|
v-hasPermission="new ComplexPermission([], ['x-pack'], 'OR')"
|
||||||
|
>
|
||||||
|
<el-switch size="small" v-model="form.authentication" @change="firstGeneration"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
prop="authentication_value"
|
||||||
|
v-if="form.authentication"
|
||||||
|
:label="$t('views.applicationOverview.appInfo.LimitDialog.authenticationValue')"
|
||||||
|
v-hasPermission="new ComplexPermission([], ['x-pack'], 'OR')"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
class="authentication-append-input"
|
||||||
|
v-model="form.authentication_value"
|
||||||
|
readonly
|
||||||
|
style="width: 268px"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-tooltip :content="$t('common.copy')" placement="top">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
@click="copyClick(form.authentication_value)"
|
||||||
|
style="margin: 0 4px !important"
|
||||||
|
>
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip :content="$t('common.refresh')" placement="top">
|
||||||
|
<el-button
|
||||||
|
@click="refreshAuthentication"
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
style="margin: 0 4px 0 0 !important"
|
||||||
|
>
|
||||||
|
<el-icon><RefreshRight /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')"
|
||||||
|
@click.prevent
|
||||||
|
>
|
||||||
|
<el-switch size="small" v-model="form.white_active"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-model="form.white_list"
|
||||||
|
:placeholder="$t('views.applicationOverview.appInfo.LimitDialog.whitelistPlaceholder')"
|
||||||
|
:rows="10"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit(limitFormRef)" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { MsgSuccess } from '@/utils/message'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import { copyClick } from '@/utils/clipboard'
|
||||||
|
import { ComplexPermission } from '@/utils/permission/type'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const limitFormRef = ref()
|
||||||
|
const form = ref<any>({
|
||||||
|
access_num: 0,
|
||||||
|
white_active: true,
|
||||||
|
white_list: '',
|
||||||
|
authentication_value: '',
|
||||||
|
authentication: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
form.value = {
|
||||||
|
access_num: 0,
|
||||||
|
white_active: true,
|
||||||
|
white_list: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (data: any) => {
|
||||||
|
form.value.access_num = data.access_num
|
||||||
|
form.value.white_active = data.white_active
|
||||||
|
form.value.white_list = data.white_list?.length ? data.white_list?.join('\n') : ''
|
||||||
|
form.value.authentication_value = data.authentication_value
|
||||||
|
form.value.authentication = data.authentication
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
const obj = {
|
||||||
|
white_list: form.value.white_list ? form.value.white_list.split('\n') : [],
|
||||||
|
white_active: form.value.white_active,
|
||||||
|
access_num: form.value.access_num,
|
||||||
|
authentication: form.value.authentication,
|
||||||
|
authentication_value: form.value.authentication_value
|
||||||
|
}
|
||||||
|
applicationApi.putAccessToken(id as string, obj, loading).then((res) => {
|
||||||
|
emit('refresh')
|
||||||
|
// @ts-ignore
|
||||||
|
MsgSuccess(t('common.settingSuccess'))
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function generateAuthenticationValue(length: number = 10) {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||||
|
const randomValues = new Uint8Array(length)
|
||||||
|
window.crypto.getRandomValues(randomValues)
|
||||||
|
return Array.from(randomValues)
|
||||||
|
.map((value) => chars[value % chars.length])
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
function refreshAuthentication() {
|
||||||
|
form.value.authentication_value = generateAuthenticationValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstGeneration() {
|
||||||
|
if (form.value.authentication && !form.value.authentication_value) {
|
||||||
|
form.value.authentication_value = generateAuthenticationValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.authentication-append-input {
|
||||||
|
.el-input-group__append {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('common.setting')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-form label-position="top" ref="settingFormRef" :model="form">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.allowCrossDomainLabel')"
|
||||||
|
@click.prevent
|
||||||
|
>
|
||||||
|
<el-switch size="small" v-model="form.allow_cross_domain"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-model="form.cross_domain_list"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.crossDomainPlaceholder')
|
||||||
|
"
|
||||||
|
:rows="10"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="submit(settingFormRef)" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import overviewApi from '@/api/application-overview'
|
||||||
|
import overviewSystemApi from '@/api/system-api-key'
|
||||||
|
import { MsgSuccess } from '@/utils/message'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const settingFormRef = ref()
|
||||||
|
const form = ref<any>({
|
||||||
|
allow_cross_domain: false,
|
||||||
|
cross_domain_list: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const APIKeyId = ref('')
|
||||||
|
const APIType = ref('APPLICATION')
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
form.value = {
|
||||||
|
allow_cross_domain: false,
|
||||||
|
cross_domain_list: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (data: any, type: string) => {
|
||||||
|
APIKeyId.value = data.id
|
||||||
|
APIType.value = type
|
||||||
|
form.value.allow_cross_domain = data.allow_cross_domain
|
||||||
|
form.value.cross_domain_list = data.cross_domain_list?.length
|
||||||
|
? data.cross_domain_list?.join('\n')
|
||||||
|
: ''
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
const obj = {
|
||||||
|
allow_cross_domain: form.value.allow_cross_domain,
|
||||||
|
cross_domain_list: form.value.cross_domain_list
|
||||||
|
? form.value.cross_domain_list.split('\n').filter(function (item: string) {
|
||||||
|
return item !== ''
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiCall =
|
||||||
|
APIType.value === 'APPLICATION'
|
||||||
|
? overviewApi.putAPIKey(id as string, APIKeyId.value, obj, loading)
|
||||||
|
: overviewSystemApi.putAPIKey(APIKeyId.value, obj, loading)
|
||||||
|
|
||||||
|
apiCall.then((res) => {
|
||||||
|
emit('refresh')
|
||||||
|
//@ts-ignore
|
||||||
|
MsgSuccess(t('common.settingSuccess'))
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
<template>
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col
|
||||||
|
:xs="12"
|
||||||
|
:sm="12"
|
||||||
|
:md="12"
|
||||||
|
:lg="6"
|
||||||
|
:xl="6"
|
||||||
|
v-for="(item, index) in statisticsType"
|
||||||
|
:key="index"
|
||||||
|
class="mb-16"
|
||||||
|
>
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="flex align-center ml-8 mr-8">
|
||||||
|
<el-avatar :size="40" shape="square" :style="{ background: item.background }">
|
||||||
|
<appIcon :iconName="item.icon" :style="{ fontSize: '24px', color: item.color }" />
|
||||||
|
</el-avatar>
|
||||||
|
<div class="ml-12">
|
||||||
|
<p class="color-secondary lighter mb-4">{{ item.name }}</p>
|
||||||
|
<div v-if="item.id !== 'starCharts'" class="flex align-baseline">
|
||||||
|
<h2>{{ numberFormat(item.sum?.[0]) }}</h2>
|
||||||
|
<span v-if="item.sum.length > 1" class="ml-12" style="color: #f54a45"
|
||||||
|
>+{{ numberFormat(item.sum?.[1]) }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex align-center mr-8">
|
||||||
|
<AppIcon iconName="app-like-color"></AppIcon>
|
||||||
|
<h2 class="ml-4">{{ item.sum?.[0] }}</h2>
|
||||||
|
<AppIcon class="ml-12" iconName="app-oppose-color"></AppIcon>
|
||||||
|
<h2 class="ml-4">{{ item.sum?.[1] }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="24"
|
||||||
|
:lg="12"
|
||||||
|
:xl="12"
|
||||||
|
v-for="(item, index) in statisticsType"
|
||||||
|
:key="index"
|
||||||
|
class="mb-16"
|
||||||
|
>
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="p-8">
|
||||||
|
<AppCharts height="316px" :id="item.id" type="line" :option="item.option" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import AppCharts from '@/components/app-charts/index.vue'
|
||||||
|
import { getAttrsArray, getSum, numberFormat } from '@/utils/utils'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const statisticsType = computed(() => [
|
||||||
|
{
|
||||||
|
id: 'customerCharts',
|
||||||
|
// @ts-ignore
|
||||||
|
name: t('views.applicationOverview.monitor.charts.customerTotal'),
|
||||||
|
icon: 'app-user',
|
||||||
|
background: '#EBF1FF',
|
||||||
|
color: '#3370FF',
|
||||||
|
sum: [
|
||||||
|
getSum(getAttrsArray(props.data, 'customer_num') || 0),
|
||||||
|
getSum(getAttrsArray(props.data, 'customer_added_count') || 0)
|
||||||
|
],
|
||||||
|
option: {
|
||||||
|
title: t('views.applicationOverview.monitor.charts.customerTotal'),
|
||||||
|
xData: getAttrsArray(props.data, 'day'),
|
||||||
|
yData: [
|
||||||
|
{
|
||||||
|
name: t('views.applicationOverview.monitor.charts.customerTotal'),
|
||||||
|
type: 'line',
|
||||||
|
area: true,
|
||||||
|
data: getAttrsArray(props.data, 'customer_num')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('views.applicationOverview.monitor.charts.customerNew'),
|
||||||
|
type: 'line',
|
||||||
|
area: true,
|
||||||
|
data: getAttrsArray(props.data, 'customer_added_count')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chatRecordCharts',
|
||||||
|
name: t('views.applicationOverview.monitor.charts.queryCount'),
|
||||||
|
icon: 'app-question',
|
||||||
|
background: '#FFF3E5',
|
||||||
|
color: '#FF8800',
|
||||||
|
sum: [getSum(getAttrsArray(props.data, 'chat_record_count') || 0)],
|
||||||
|
option: {
|
||||||
|
title: t('views.applicationOverview.monitor.charts.queryCount'),
|
||||||
|
xData: getAttrsArray(props.data, 'day'),
|
||||||
|
yData: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
data: getAttrsArray(props.data, 'chat_record_count')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tokensCharts',
|
||||||
|
name: t('views.applicationOverview.monitor.charts.tokensTotal'),
|
||||||
|
icon: 'app-tokens',
|
||||||
|
background: '#E5FBF8',
|
||||||
|
color: '#00D6B9',
|
||||||
|
sum: [getSum(getAttrsArray(props.data, 'tokens_num') || 0)],
|
||||||
|
option: {
|
||||||
|
title: t('views.applicationOverview.monitor.charts.tokensTotal'),
|
||||||
|
xData: getAttrsArray(props.data, 'day'),
|
||||||
|
yData: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
data: getAttrsArray(props.data, 'tokens_num')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'starCharts',
|
||||||
|
name: t('views.applicationOverview.monitor.charts.userSatisfaction'),
|
||||||
|
icon: 'app-user-stars',
|
||||||
|
background: '#FEEDEC',
|
||||||
|
color: '#F54A45',
|
||||||
|
sum: [
|
||||||
|
getSum(getAttrsArray(props.data, 'star_num') || 0),
|
||||||
|
getSum(getAttrsArray(props.data, 'trample_num') || 0)
|
||||||
|
],
|
||||||
|
option: {
|
||||||
|
title: t('views.applicationOverview.monitor.charts.userSatisfaction'),
|
||||||
|
xData: getAttrsArray(props.data, 'day'),
|
||||||
|
yData: [
|
||||||
|
{
|
||||||
|
name: t('views.applicationOverview.monitor.charts.approval'),
|
||||||
|
type: 'line',
|
||||||
|
data: getAttrsArray(props.data, 'star_num')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('views.applicationOverview.monitor.charts.disapproval'),
|
||||||
|
type: 'line',
|
||||||
|
data: getAttrsArray(props.data, 'trample_num')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,639 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.applicationOverview.appInfo.SettingDisplayDialog.dialogTitle')"
|
||||||
|
width="900"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
align-center
|
||||||
|
class="display-setting-dialog"
|
||||||
|
>
|
||||||
|
<template #header="{ titleId, titleClass }">
|
||||||
|
<div class="flex-between mb-8">
|
||||||
|
<h4 :id="titleId" :class="titleClass">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.dialogTitle') }}
|
||||||
|
</h4>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<el-button type="primary" @click.prevent="resetForm" link>
|
||||||
|
<el-icon class="mr-4">
|
||||||
|
<Refresh />
|
||||||
|
</el-icon>
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.restoreDefault') }}
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<div class="setting-preview border border-r-4 mr-16" style="min-width: 400px">
|
||||||
|
<div class="setting-preview-container">
|
||||||
|
<div class="setting-preview-header" :style="customStyle">
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-12 ml-24 flex">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(detail?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="detail?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="detail?.name"
|
||||||
|
:name="detail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="ellipsis">
|
||||||
|
{{ detail?.name || $t('views.application.applicationForm.form.appName.label') }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="mr-16">
|
||||||
|
<el-button link>
|
||||||
|
<AppIcon
|
||||||
|
:iconName="'app-magnify'"
|
||||||
|
:style="{
|
||||||
|
color: xpackForm.custom_theme?.header_font_color
|
||||||
|
}"
|
||||||
|
style="font-size: 20px"
|
||||||
|
></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
<el-button link>
|
||||||
|
<el-icon
|
||||||
|
:size="20"
|
||||||
|
class="color-secondary"
|
||||||
|
:style="{
|
||||||
|
color: xpackForm.custom_theme?.header_font_color
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<Close />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="p-16" style="position: relative">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="avatar" v-if="xpackForm.show_avatar">
|
||||||
|
<el-image
|
||||||
|
v-if="imgUrl.avatar"
|
||||||
|
:src="imgUrl.avatar"
|
||||||
|
alt=""
|
||||||
|
fit="cover"
|
||||||
|
style="width: 28px; height: 28px; display: block"
|
||||||
|
/>
|
||||||
|
<LogoIcon
|
||||||
|
v-else
|
||||||
|
height="28px"
|
||||||
|
style="width: 28px; height: 28px; display: block"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img
|
||||||
|
src="@/assets/display-bg2.png"
|
||||||
|
alt=""
|
||||||
|
:width="
|
||||||
|
xpackForm.show_avatar
|
||||||
|
? xpackForm.show_user_avatar
|
||||||
|
? '232px'
|
||||||
|
: '270px'
|
||||||
|
: xpackForm.show_user_avatar
|
||||||
|
? '260px'
|
||||||
|
: '300px'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex mt-4" style="justify-content: flex-end">
|
||||||
|
<img
|
||||||
|
src="@/assets/display-bg3.png"
|
||||||
|
alt=""
|
||||||
|
:width="
|
||||||
|
xpackForm.show_user_avatar
|
||||||
|
? xpackForm.show_avatar
|
||||||
|
? '227px'
|
||||||
|
: '255px'
|
||||||
|
: xpackForm.show_avatar
|
||||||
|
? '265px'
|
||||||
|
: '292px'
|
||||||
|
"
|
||||||
|
style="object-fit: contain"
|
||||||
|
/>
|
||||||
|
<div class="avatar ml-8" v-if="xpackForm.show_user_avatar">
|
||||||
|
<el-image
|
||||||
|
v-if="imgUrl.user_avatar"
|
||||||
|
:src="imgUrl.user_avatar"
|
||||||
|
alt=""
|
||||||
|
fit="cover"
|
||||||
|
style="width: 28px; height: 28px; display: block"
|
||||||
|
/>
|
||||||
|
<AppAvatar v-else>
|
||||||
|
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="position: absolute; bottom: 0; padding-bottom: 8px; box-sizing: border-box"
|
||||||
|
class="p-16 text-center w-full"
|
||||||
|
>
|
||||||
|
<img src="@/assets/display-bg1.png" alt="" class="w-full" />
|
||||||
|
<el-text type="info" v-if="xpackForm.disclaimer" class="mt-8" style="font-size: 12px">
|
||||||
|
<auto-tooltip :content="xpackForm.disclaimer_value">
|
||||||
|
{{ xpackForm.disclaimer_value }}
|
||||||
|
</auto-tooltip>
|
||||||
|
</el-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="float_icon">
|
||||||
|
<el-image
|
||||||
|
v-if="imgUrl.float_icon"
|
||||||
|
:src="imgUrl.float_icon"
|
||||||
|
alt=""
|
||||||
|
fit="cover"
|
||||||
|
style="width: 40px; height: 40px; display: block"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
src="/MaxKB.gif"
|
||||||
|
height="50px"
|
||||||
|
style="width: 40px; height: 40px; display: block"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="displayFormRef" :model="xpackForm">
|
||||||
|
<el-row class="w-full mb-8">
|
||||||
|
<el-col :span="12">
|
||||||
|
<h5 class="mb-8">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.customThemeColor') }}
|
||||||
|
</h5>
|
||||||
|
<div>
|
||||||
|
<el-color-picker v-model="xpackForm.custom_theme.theme_color" />
|
||||||
|
{{
|
||||||
|
!xpackForm.custom_theme.theme_color
|
||||||
|
? $t('views.applicationOverview.appInfo.SettingDisplayDialog.default')
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<h5 class="mb-8">
|
||||||
|
{{
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.headerTitleFontColor')
|
||||||
|
}}
|
||||||
|
</h5>
|
||||||
|
<el-color-picker v-model="xpackForm.custom_theme.header_font_color" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row class="w-full mb-8">
|
||||||
|
<h5 class="mb-8">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.languageLabel') }}
|
||||||
|
</h5>
|
||||||
|
<el-select v-model="xpackForm.language" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in langList"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-row>
|
||||||
|
<el-card shadow="never" class="mb-8">
|
||||||
|
<div class="flex-between mb-8">
|
||||||
|
<span class="lighter">{{
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.AIAvatar')
|
||||||
|
}}</span>
|
||||||
|
<span class="flex align-center">
|
||||||
|
<el-checkbox v-model="xpackForm.show_avatar">{{
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.display')
|
||||||
|
}}</el-checkbox>
|
||||||
|
<el-upload
|
||||||
|
class="ml-8"
|
||||||
|
ref="uploadRef"
|
||||||
|
action="#"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/jpeg, image/png, image/gif"
|
||||||
|
:on-change="(file: any, fileList: any) => onChange(file, fileList, 'avatar')"
|
||||||
|
>
|
||||||
|
<el-button size="small">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.replace') }}
|
||||||
|
</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-text type="info" size="small">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.imageMessage') }}
|
||||||
|
</el-text>
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never" class="mb-8">
|
||||||
|
<div class="flex-between mb-8">
|
||||||
|
<span class="lighter">{{
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.askUserAvatar')
|
||||||
|
}}</span>
|
||||||
|
<span class="flex align-center">
|
||||||
|
<el-checkbox v-model="xpackForm.show_user_avatar">
|
||||||
|
{{
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.display')
|
||||||
|
}}</el-checkbox
|
||||||
|
>
|
||||||
|
<el-upload
|
||||||
|
class="ml-8"
|
||||||
|
ref="uploadRef"
|
||||||
|
action="#"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/jpeg, image/png, image/gif"
|
||||||
|
:on-change="(file: any, fileList: any) => onChange(file, fileList, 'user_avatar')"
|
||||||
|
>
|
||||||
|
<el-button size="small">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.replace') }}
|
||||||
|
</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-text type="info" size="small"
|
||||||
|
>{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.imageMessage') }}
|
||||||
|
</el-text>
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never" class="mb-8">
|
||||||
|
<div class="flex-between mb-8">
|
||||||
|
<span class="lighter">{{
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.floatIcon')
|
||||||
|
}}</span>
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
action="#"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/jpeg, image/png, image/gif"
|
||||||
|
:on-change="(file: any, fileList: any) => onChange(file, fileList, 'float_icon')"
|
||||||
|
>
|
||||||
|
<el-button size="small">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.replace') }}
|
||||||
|
</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
<el-text type="info" size="small">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.SettingDisplayDialog.imageMessage') }}
|
||||||
|
</el-text>
|
||||||
|
<div class="border-t mt-8">
|
||||||
|
<div class="flex-between mb-8">
|
||||||
|
<span class="lighter">{{
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.iconDefaultPosition')
|
||||||
|
}}</span>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="xpackForm.draggable"
|
||||||
|
:label="
|
||||||
|
$t('views.applicationOverview.appInfo.SettingDisplayDialog.draggablePosition')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<el-row :gutter="8" class="w-full mb-8">
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<el-select v-model="xpackForm.float_location.x.type" style="width: 80px">
|
||||||
|
<el-option
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'views.applicationOverview.appInfo.SettingDisplayDialog.iconPosition.left'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
value="left"
|
||||||
|
/>
|
||||||
|
<el-option
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'views.applicationOverview.appInfo.SettingDisplayDialog.iconPosition.right'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
value="right"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input-number
|
||||||
|
v-model="xpackForm.float_location.x.value"
|
||||||
|
:min="0"
|
||||||
|
:step="1"
|
||||||
|
:precision="0"
|
||||||
|
:value-on-clear="0"
|
||||||
|
step-strictly
|
||||||
|
controls-position="right"
|
||||||
|
/>
|
||||||
|
<span class="ml-4">px</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<el-select v-model="xpackForm.float_location.y.type" style="width: 80px">
|
||||||
|
<el-option
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'views.applicationOverview.appInfo.SettingDisplayDialog.iconPosition.top'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
value="top"
|
||||||
|
/>
|
||||||
|
<el-option
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'views.applicationOverview.appInfo.SettingDisplayDialog.iconPosition.bottom'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
value="bottom"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input-number
|
||||||
|
v-model="xpackForm.float_location.y.value"
|
||||||
|
:min="0"
|
||||||
|
:step="1"
|
||||||
|
:precision="0"
|
||||||
|
:value-on-clear="0"
|
||||||
|
step-strictly
|
||||||
|
controls-position="right"
|
||||||
|
/>
|
||||||
|
<span class="ml-4">px</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-space direction="vertical" alignment="start" :size="2">
|
||||||
|
<el-checkbox
|
||||||
|
v-model="xpackForm.show_source"
|
||||||
|
:label="
|
||||||
|
isWorkFlow(detail.type)
|
||||||
|
? $t('views.applicationOverview.appInfo.SettingDisplayDialog.showExecutionDetail')
|
||||||
|
: $t('views.applicationOverview.appInfo.SettingDisplayDialog.showSourceLabel')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="xpackForm.show_history"
|
||||||
|
:label="$t('views.applicationOverview.appInfo.SettingDisplayDialog.showHistory')"
|
||||||
|
/>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="xpackForm.show_guide"
|
||||||
|
:label="$t('views.applicationOverview.appInfo.SettingDisplayDialog.displayGuide')"
|
||||||
|
/>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="xpackForm.disclaimer"
|
||||||
|
:label="$t('views.applicationOverview.appInfo.SettingDisplayDialog.disclaimer')"
|
||||||
|
@change="changeDisclaimer"
|
||||||
|
/>
|
||||||
|
<span v-if="xpackForm.disclaimer"
|
||||||
|
><el-tooltip :content="xpackForm.disclaimer_value" placement="top">
|
||||||
|
<el-input
|
||||||
|
v-model="xpackForm.disclaimer_value"
|
||||||
|
style="width: 422px; margin-bottom: 10px"
|
||||||
|
@change="changeValue"
|
||||||
|
:maxlength="128"
|
||||||
|
/> </el-tooltip
|
||||||
|
></span>
|
||||||
|
</el-space>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit(displayFormRef)" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import type { FormInstance, FormRules, UploadFiles } from 'element-plus'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import applicationXpackApi from '@/api/application-xpack'
|
||||||
|
import { MsgSuccess, MsgError } from '@/utils/message'
|
||||||
|
import { langList, t } from '@/locales'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
|
||||||
|
const { user } = useStore()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const defaultSetting = {
|
||||||
|
show_source: false,
|
||||||
|
language: '',
|
||||||
|
show_history: true,
|
||||||
|
draggable: true,
|
||||||
|
show_guide: true,
|
||||||
|
avatar: '',
|
||||||
|
avatar_url: '',
|
||||||
|
float_icon: '',
|
||||||
|
float_icon_url: '',
|
||||||
|
user_avatar: '',
|
||||||
|
user_avatar_url: '',
|
||||||
|
disclaimer: false,
|
||||||
|
disclaimer_value: t('views.applicationOverview.appInfo.SettingDisplayDialog.disclaimerValue'),
|
||||||
|
custom_theme: {
|
||||||
|
theme_color: '',
|
||||||
|
header_font_color: '#1f2329'
|
||||||
|
},
|
||||||
|
float_location: {
|
||||||
|
y: { type: 'bottom', value: 30 },
|
||||||
|
x: { type: 'right', value: 0 }
|
||||||
|
},
|
||||||
|
show_avatar: true,
|
||||||
|
show_user_avatar: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayFormRef = ref()
|
||||||
|
|
||||||
|
const xpackForm = ref<any>({
|
||||||
|
show_source: false,
|
||||||
|
language: '',
|
||||||
|
show_history: false,
|
||||||
|
draggable: false,
|
||||||
|
show_guide: false,
|
||||||
|
avatar: '',
|
||||||
|
avatar_url: '',
|
||||||
|
float_icon: '',
|
||||||
|
float_icon_url: '',
|
||||||
|
user_avatar: '',
|
||||||
|
user_avatar_url: '',
|
||||||
|
disclaimer: false,
|
||||||
|
disclaimer_value: t('views.applicationOverview.appInfo.SettingDisplayDialog.disclaimerValue'),
|
||||||
|
custom_theme: {
|
||||||
|
theme_color: '',
|
||||||
|
header_font_color: '#1f2329'
|
||||||
|
},
|
||||||
|
float_location: {
|
||||||
|
y: { type: 'bottom', value: 30 },
|
||||||
|
x: { type: 'right', value: 0 }
|
||||||
|
},
|
||||||
|
show_avatar: true,
|
||||||
|
show_user_avatar: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const imgUrl = ref<any>({
|
||||||
|
avatar: '',
|
||||||
|
float_icon: '',
|
||||||
|
user_avatar: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const detail = ref<any>(null)
|
||||||
|
|
||||||
|
const customStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
background: xpackForm.value.custom_theme?.theme_color,
|
||||||
|
color: xpackForm.value.custom_theme?.header_font_color
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
xpackForm.value = cloneDeep(defaultSetting)
|
||||||
|
imgUrl.value = {
|
||||||
|
avatar: '',
|
||||||
|
float_icon: '',
|
||||||
|
user_avatar: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (file: any, fileList: UploadFiles, attr: string) => {
|
||||||
|
//1、判断文件大小是否合法,文件限制不能大于 10 MB
|
||||||
|
const isLimit = file?.size / 1024 / 1024 < 10
|
||||||
|
if (!isLimit) {
|
||||||
|
// @ts-ignore
|
||||||
|
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.fileSizeExceeded'))
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
xpackForm.value[attr] = file.raw
|
||||||
|
imgUrl.value[attr] = URL.createObjectURL(file.raw)
|
||||||
|
xpackForm.value[`${attr}_url`] = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = (data: any, content: any) => {
|
||||||
|
detail.value = content
|
||||||
|
xpackForm.value.show_source = data.show_source
|
||||||
|
xpackForm.value.show_history = data.show_history
|
||||||
|
xpackForm.value.language = data.language
|
||||||
|
xpackForm.value.draggable = data.draggable
|
||||||
|
xpackForm.value.show_guide = data.show_guide
|
||||||
|
imgUrl.value.avatar = data.avatar
|
||||||
|
imgUrl.value.float_icon = data.float_icon
|
||||||
|
imgUrl.value.user_avatar = data.user_avatar
|
||||||
|
xpackForm.value.disclaimer = data.disclaimer
|
||||||
|
xpackForm.value.disclaimer_value = data.disclaimer_value
|
||||||
|
if (
|
||||||
|
xpackForm.value.disclaimer_value ===
|
||||||
|
t('views.applicationOverview.appInfo.SettingDisplayDialog.disclaimerValue')
|
||||||
|
) {
|
||||||
|
xpackForm.value.disclaimer_value = t(
|
||||||
|
'views.applicationOverview.appInfo.SettingDisplayDialog.disclaimerValue'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
xpackForm.value.avatar_url = data.avatar
|
||||||
|
xpackForm.value.user_avatar_url = data.user_avatar
|
||||||
|
xpackForm.value.float_icon_url = data.float_icon
|
||||||
|
xpackForm.value.show_avatar = data.show_avatar
|
||||||
|
xpackForm.value.show_user_avatar = data.show_user_avatar
|
||||||
|
xpackForm.value.custom_theme = {
|
||||||
|
theme_color: data.custom_theme?.theme_color || '',
|
||||||
|
header_font_color: data.custom_theme?.header_font_color || '#1f2329'
|
||||||
|
}
|
||||||
|
xpackForm.value.float_location = data.float_location
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeValue = (value: string) => {
|
||||||
|
xpackForm.value.disclaimer_value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeDisclaimer = (value: boolean) => {
|
||||||
|
xpackForm.value.disclaimer = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
let fd = new FormData()
|
||||||
|
Object.keys(xpackForm.value).map((item) => {
|
||||||
|
if (['custom_theme', 'float_location'].includes(item)) {
|
||||||
|
fd.append(item, JSON.stringify(xpackForm.value[item]))
|
||||||
|
} else {
|
||||||
|
fd.append(item, xpackForm.value[item])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
applicationXpackApi.putAccessToken(id as string, fd, loading).then((res) => {
|
||||||
|
emit('refresh')
|
||||||
|
// @ts-ignore
|
||||||
|
MsgSuccess(t('common.settingSuccess'))
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.setting-preview {
|
||||||
|
background: #f5f6f7;
|
||||||
|
height: 570px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.float_icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-preview-container {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 25px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
background: var(--dialog-bg-gradient-color);
|
||||||
|
box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
width: 330px;
|
||||||
|
height: 520px;
|
||||||
|
|
||||||
|
.setting-preview-header {
|
||||||
|
background: var(--app-header-bg-color);
|
||||||
|
height: var(--app-header-height);
|
||||||
|
line-height: var(--app-header-height);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-setting-dialog {
|
||||||
|
.el-dialog__header {
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__headerbtn {
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,434 @@
|
||||||
|
<template>
|
||||||
|
<LayoutContainer :header="$t('views.applicationOverview.title')">
|
||||||
|
<el-scrollbar>
|
||||||
|
<div class="main-calc-height p-24">
|
||||||
|
<h4 class="title-decoration-1 mb-16">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.header') }}
|
||||||
|
</h4>
|
||||||
|
<el-card shadow="never" class="overview-card" v-loading="loading">
|
||||||
|
<div class="title flex align-center">
|
||||||
|
<div
|
||||||
|
class="edit-avatar mr-12"
|
||||||
|
@mouseenter="showEditIcon = true"
|
||||||
|
@mouseleave="showEditIcon = false"
|
||||||
|
>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(detail?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="detail?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="detail?.name"
|
||||||
|
:name="detail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="showEditIcon"
|
||||||
|
shape="square"
|
||||||
|
class="edit-mask"
|
||||||
|
:size="32"
|
||||||
|
@click="openEditAvatar"
|
||||||
|
>
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</AppAvatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{ detail?.name }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="12" class="mt-16">
|
||||||
|
<div class="flex">
|
||||||
|
<el-text type="info">{{
|
||||||
|
$t('views.applicationOverview.appInfo.publicAccessLink')
|
||||||
|
}}</el-text>
|
||||||
|
<el-switch
|
||||||
|
v-model="accessToken.is_active"
|
||||||
|
class="ml-8"
|
||||||
|
size="small"
|
||||||
|
inline-prompt
|
||||||
|
:active-text="$t('views.applicationOverview.appInfo.openText')"
|
||||||
|
:inactive-text="$t('views.applicationOverview.appInfo.closeText')"
|
||||||
|
:before-change="() => changeState(accessToken.is_active)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 mb-16 url-height flex align-center" style="margin-bottom: 37px">
|
||||||
|
<span class="vertical-middle lighter break-all ellipsis-1">
|
||||||
|
{{ shareUrl }}
|
||||||
|
</span>
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.copy')" placement="top">
|
||||||
|
<el-button type="primary" text @click="copyClick(shareUrl)">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.refresh')" placement="top">
|
||||||
|
<el-button
|
||||||
|
@click="refreshAccessToken"
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
style="margin-left: 1px"
|
||||||
|
>
|
||||||
|
<el-icon><RefreshRight /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button
|
||||||
|
v-if="accessToken?.is_active"
|
||||||
|
:disabled="!accessToken?.is_active"
|
||||||
|
type="primary"
|
||||||
|
tag="a"
|
||||||
|
:href="shareUrl"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ $t('views.applicationOverview.appInfo.demo') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button v-else :disabled="!accessToken?.is_active" type="primary">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.demo') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="!accessToken?.is_active" @click="openDialog">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.embedInWebsite') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="openLimitDialog">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.accessControl') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="openDisplaySettingDialog">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.displaySetting') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" class="mt-16">
|
||||||
|
<div class="flex">
|
||||||
|
<el-text type="info"
|
||||||
|
>{{ $t('views.applicationOverview.appInfo.apiAccessCredentials') }}
|
||||||
|
</el-text>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 mb-16 url-height">
|
||||||
|
<div>
|
||||||
|
<el-text>API {{ $t('common.fileUpload.document') }}:</el-text
|
||||||
|
><el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="toUrl(apiUrl)"
|
||||||
|
class="vertical-middle lighter break-all"
|
||||||
|
>
|
||||||
|
{{ apiUrl }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<span class="flex">
|
||||||
|
<el-text style="width: 80px">Base URL:</el-text>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="vertical-middle lighter break-all ellipsis-1">{{
|
||||||
|
baseUrl + id
|
||||||
|
}}</span>
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.copy')" placement="top">
|
||||||
|
<el-button type="primary" text @click="copyClick(baseUrl + id)">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button @click="openAPIKeyDialog">{{
|
||||||
|
$t('views.applicationOverview.appInfo.apiKey')
|
||||||
|
}}</el-button>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
<h4 class="title-decoration-1 mt-16 mb-16">
|
||||||
|
{{ $t('views.applicationOverview.monitor.monitoringStatistics') }}
|
||||||
|
</h4>
|
||||||
|
<div class="mb-16">
|
||||||
|
<el-select
|
||||||
|
v-model="history_day"
|
||||||
|
class="mr-12"
|
||||||
|
@change="changeDayHandle"
|
||||||
|
style="width: 180px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in dayOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-date-picker
|
||||||
|
v-if="history_day === 'other'"
|
||||||
|
v-model="daterangeValue"
|
||||||
|
type="daterange"
|
||||||
|
:start-placeholder="$t('views.applicationOverview.monitor.startDatePlaceholder')"
|
||||||
|
:end-placeholder="$t('views.applicationOverview.monitor.endDatePlaceholder')"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
@change="changeDayRangeHandle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-loading="statisticsLoading">
|
||||||
|
<StatisticsCharts :data="statisticsData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
<EmbedDialog
|
||||||
|
ref="EmbedDialogRef"
|
||||||
|
:data="detail"
|
||||||
|
:api-input-params="mapToUrlParams(apiInputParams)"
|
||||||
|
/>
|
||||||
|
<APIKeyDialog ref="APIKeyDialogRef" />
|
||||||
|
<LimitDialog ref="LimitDialogRef" @refresh="refresh" />
|
||||||
|
<EditAvatarDialog ref="EditAvatarDialogRef" @refresh="refreshIcon" />
|
||||||
|
<XPackDisplaySettingDialog
|
||||||
|
ref="XPackDisplaySettingDialogRef"
|
||||||
|
@refresh="refresh"
|
||||||
|
v-if="user.isEnterprise()"
|
||||||
|
/>
|
||||||
|
<DisplaySettingDialog ref="DisplaySettingDialogRef" @refresh="refresh" v-else />
|
||||||
|
</LayoutContainer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import EmbedDialog from './component/EmbedDialog.vue'
|
||||||
|
import APIKeyDialog from './component/APIKeyDialog.vue'
|
||||||
|
import LimitDialog from './component/LimitDialog.vue'
|
||||||
|
import DisplaySettingDialog from './component/DisplaySettingDialog.vue'
|
||||||
|
import XPackDisplaySettingDialog from './component/XPackDisplaySettingDialog.vue'
|
||||||
|
import EditAvatarDialog from './component/EditAvatarDialog.vue'
|
||||||
|
import StatisticsCharts from './component/StatisticsCharts.vue'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import overviewApi from '@/api/application-overview'
|
||||||
|
import { nowDate, beforeDay } from '@/utils/time'
|
||||||
|
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
|
import { copyClick } from '@/utils/clipboard'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const { user, application } = useStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route as any
|
||||||
|
|
||||||
|
const apiUrl = window.location.origin + '/doc/chat/'
|
||||||
|
|
||||||
|
const baseUrl = window.location.origin + '/api/application/'
|
||||||
|
|
||||||
|
const DisplaySettingDialogRef = ref()
|
||||||
|
const XPackDisplaySettingDialogRef = ref()
|
||||||
|
const EditAvatarDialogRef = ref()
|
||||||
|
const LimitDialogRef = ref()
|
||||||
|
const APIKeyDialogRef = ref()
|
||||||
|
const EmbedDialogRef = ref()
|
||||||
|
|
||||||
|
const accessToken = ref<any>({})
|
||||||
|
const detail = ref<any>(null)
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const urlParams = computed(() =>
|
||||||
|
mapToUrlParams(apiInputParams.value) ? '?' + mapToUrlParams(apiInputParams.value) : ''
|
||||||
|
)
|
||||||
|
const shareUrl = computed(
|
||||||
|
() => application.location + accessToken.value.access_token + urlParams.value
|
||||||
|
)
|
||||||
|
|
||||||
|
const dayOptions = [
|
||||||
|
{
|
||||||
|
value: 7,
|
||||||
|
// @ts-ignore
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.past7Days')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 30,
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.past30Days')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 90,
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.past90Days')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 183,
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.past183Days')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'other',
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.other')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const history_day = ref<number | string>(7)
|
||||||
|
|
||||||
|
// 日期组件时间
|
||||||
|
const daterangeValue = ref('')
|
||||||
|
|
||||||
|
// 提交日期时间
|
||||||
|
const daterange = ref({
|
||||||
|
start_time: '',
|
||||||
|
end_time: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const statisticsLoading = ref(false)
|
||||||
|
const statisticsData = ref([])
|
||||||
|
|
||||||
|
const showEditIcon = ref(false)
|
||||||
|
const apiInputParams = ref([])
|
||||||
|
|
||||||
|
function toUrl(url: string) {
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
function openDisplaySettingDialog() {
|
||||||
|
if (user.isEnterprise()) {
|
||||||
|
XPackDisplaySettingDialogRef.value?.open(accessToken.value, detail.value)
|
||||||
|
} else {
|
||||||
|
DisplaySettingDialogRef.value?.open(accessToken.value, detail.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function openEditAvatar() {
|
||||||
|
EditAvatarDialogRef.value.open(detail.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeDayHandle(val: number | string) {
|
||||||
|
if (val !== 'other') {
|
||||||
|
daterange.value.start_time = beforeDay(val)
|
||||||
|
daterange.value.end_time = nowDate
|
||||||
|
getAppStatistics()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeDayRangeHandle(val: string) {
|
||||||
|
daterange.value.start_time = val[0]
|
||||||
|
daterange.value.end_time = val[1]
|
||||||
|
getAppStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppStatistics() {
|
||||||
|
overviewApi.getStatistics(id, daterange.value, statisticsLoading).then((res: any) => {
|
||||||
|
statisticsData.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAccessToken() {
|
||||||
|
MsgConfirm(
|
||||||
|
t('views.applicationOverview.appInfo.refreshToken.msgConfirm1'),
|
||||||
|
t('views.applicationOverview.appInfo.refreshToken.msgConfirm2'),
|
||||||
|
{
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
cancelButtonText: t('common.cancel')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
const obj = {
|
||||||
|
access_token_reset: true
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const str = t('views.applicationOverview.appInfo.refreshToken.refreshSuccess')
|
||||||
|
updateAccessToken(obj, str)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
function changeState(bool: Boolean) {
|
||||||
|
const obj = {
|
||||||
|
is_active: !bool
|
||||||
|
}
|
||||||
|
const str = obj.is_active ? t('common.status.enableSuccess') : t('common.status.disableSuccess')
|
||||||
|
updateAccessToken(obj, str)
|
||||||
|
.then(() => {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAccessToken(obj: any, str: string) {
|
||||||
|
applicationApi.putAccessToken(id as string, obj, loading).then((res) => {
|
||||||
|
accessToken.value = res?.data
|
||||||
|
MsgSuccess(str)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function openLimitDialog() {
|
||||||
|
LimitDialogRef.value.open(accessToken.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAPIKeyDialog() {
|
||||||
|
APIKeyDialogRef.value.open()
|
||||||
|
}
|
||||||
|
function openDialog() {
|
||||||
|
EmbedDialogRef.value.open(accessToken.value?.access_token)
|
||||||
|
}
|
||||||
|
function getAccessToken() {
|
||||||
|
application.asyncGetAccessToken(id, loading).then((res: any) => {
|
||||||
|
accessToken.value = res?.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetail() {
|
||||||
|
application.asyncGetApplicationDetail(id, loading).then((res: any) => {
|
||||||
|
detail.value = res.data
|
||||||
|
detail.value.work_flow?.nodes
|
||||||
|
?.filter((v: any) => v.id === 'base-node')
|
||||||
|
.map((v: any) => {
|
||||||
|
apiInputParams.value = v.properties.api_input_field_list
|
||||||
|
? v.properties.api_input_field_list.map((v: any) => {
|
||||||
|
return {
|
||||||
|
name: v.variable,
|
||||||
|
value: v.default_value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: v.properties.input_field_list
|
||||||
|
? v.properties.input_field_list
|
||||||
|
.filter((v: any) => v.assignment_method === 'api_input')
|
||||||
|
.map((v: any) => {
|
||||||
|
return {
|
||||||
|
name: v.variable,
|
||||||
|
value: v.default_value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
getAccessToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshIcon() {
|
||||||
|
getDetail()
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapToUrlParams(map: any[]) {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
|
map.forEach((item: any) => {
|
||||||
|
params.append(encodeURIComponent(item.name), encodeURIComponent(item.value))
|
||||||
|
})
|
||||||
|
|
||||||
|
return params.toString() // 返回 URL 查询字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getDetail()
|
||||||
|
getAccessToken()
|
||||||
|
changeDayHandle(history_day.value)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.overview-card {
|
||||||
|
position: relative;
|
||||||
|
.active-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 21px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
<template>
|
||||||
|
<div v-show="show" class="workflow-dropdown-menu border border-r-4">
|
||||||
|
<el-tabs v-model="activeName" class="workflow-dropdown-tabs">
|
||||||
|
<div style="display: flex; width: 100%; justify-content: center" class="mb-4">
|
||||||
|
<el-input
|
||||||
|
v-model="search_text"
|
||||||
|
style="width: 240px"
|
||||||
|
:placeholder="$t('views.applicationWorkflow.searchBar.placeholder')"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<el-icon class="el-input__icon"><search /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-tab-pane :label="$t('views.applicationWorkflow.baseComponent')" name="base">
|
||||||
|
<el-scrollbar height="400">
|
||||||
|
<div v-if="filter_menu_nodes.length > 0">
|
||||||
|
<template v-for="(item, index) in filter_menu_nodes" :key="index">
|
||||||
|
<div
|
||||||
|
class="workflow-dropdown-item cursor flex p-8-12"
|
||||||
|
@click.stop="clickNodes(item)"
|
||||||
|
@mousedown.stop="onmousedown(item)"
|
||||||
|
>
|
||||||
|
<component :is="iconComponent(`${item.type}-icon`)" class="mr-8 mt-4" :size="32" />
|
||||||
|
<div class="pre-wrap">
|
||||||
|
<div class="lighter">{{ item.label }}</div>
|
||||||
|
<el-text type="info" size="small">{{ item.text }}</el-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-else class="ml-16 mt-8">
|
||||||
|
<el-text type="info">{{ $t('views.applicationWorkflow.tip.noData') }}</el-text>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('views.functionLib.title')" name="function">
|
||||||
|
<el-scrollbar height="400">
|
||||||
|
<div
|
||||||
|
class="workflow-dropdown-item cursor flex p-8-12"
|
||||||
|
@click.stop="clickNodes(functionNode)"
|
||||||
|
@mousedown.stop="onmousedown(functionNode)"
|
||||||
|
>
|
||||||
|
<component :is="iconComponent(`function-lib-node-icon`)" class="mr-8 mt-4" :size="32" />
|
||||||
|
<div class="pre-wrap">
|
||||||
|
<div class="lighter">{{ functionNode.label }}</div>
|
||||||
|
<el-text type="info" size="small">{{ functionNode.text }}</el-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-for="(item, index) in filter_function_lib_list" :key="index">
|
||||||
|
<div
|
||||||
|
class="workflow-dropdown-item cursor flex p-8-12 align-center"
|
||||||
|
@click.stop="clickNodes(functionLibNode, item, 'function')"
|
||||||
|
@mousedown.stop="onmousedown(functionLibNode, item, 'function')"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="iconComponent(`function-lib-node-icon`)"
|
||||||
|
class="mr-8"
|
||||||
|
:size="32"
|
||||||
|
:item="item"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="pre-wrap">
|
||||||
|
<div class="lighter ellipsis-1" :title="item.name">{{ item.name }}</div>
|
||||||
|
<p>
|
||||||
|
<el-text
|
||||||
|
class="ellipsis-1"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
:title="item.desc"
|
||||||
|
v-if="item.desc"
|
||||||
|
>{{ item.desc }}</el-text
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('views.application.title')" name="application">
|
||||||
|
<el-scrollbar height="400">
|
||||||
|
<div v-if="filter_application_list.length > 0">
|
||||||
|
<template v-for="(item, index) in filter_application_list" :key="index">
|
||||||
|
<div
|
||||||
|
class="workflow-dropdown-item cursor flex align-center p-8-12"
|
||||||
|
@click.stop="clickNodes(applicationNode, item, 'application')"
|
||||||
|
@mousedown.stop="onmousedown(applicationNode, item, 'application')"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="iconComponent(`application-node-icon`)"
|
||||||
|
class="mr-8"
|
||||||
|
:size="32"
|
||||||
|
:item="item"
|
||||||
|
/>
|
||||||
|
<div class="pre-wrap" style="width: 60%">
|
||||||
|
<div class="lighter ellipsis" :title="item.name">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<el-text
|
||||||
|
class="ellipsis"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
:title="item.desc"
|
||||||
|
v-if="item.desc"
|
||||||
|
>{{ item.desc }}</el-text
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="status-tag" style="margin-left: auto">
|
||||||
|
<el-tag type="warning" v-if="isWorkFlow(item.type)" style="height: 22px">
|
||||||
|
{{ $t('views.application.workflow') }}</el-tag
|
||||||
|
>
|
||||||
|
<el-tag class="blue-tag" v-else style="height: 22px">{{
|
||||||
|
$t('views.application.simple')
|
||||||
|
}}</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-else class="ml-16 mt-8">
|
||||||
|
<el-text type="info">{{ $t('views.applicationWorkflow.tip.noData') }}</el-text>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { menuNodes, functionLibNode, functionNode, applicationNode } from '@/workflow/common/data'
|
||||||
|
import { iconComponent } from '@/workflow/icons/utils'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
const search_text = ref<string>('')
|
||||||
|
const props = defineProps({
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
workflowRef: Object
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['clickNodes', 'onmousedown'])
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const activeName = ref('base')
|
||||||
|
|
||||||
|
const functionLibList = ref<any[]>([])
|
||||||
|
const filter_function_lib_list = computed(() => {
|
||||||
|
return functionLibList.value.filter((item: any) =>
|
||||||
|
item.name.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const applicationList = ref<any[]>([])
|
||||||
|
const filter_application_list = computed(() => {
|
||||||
|
return applicationList.value.filter((item: any) =>
|
||||||
|
item.name.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const filter_menu_nodes = computed(() => {
|
||||||
|
return menuNodes.filter((item) =>
|
||||||
|
item.label.toLocaleLowerCase().includes(search_text.value.toLocaleLowerCase())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
function clickNodes(item: any, data?: any, type?: string) {
|
||||||
|
if (data) {
|
||||||
|
item['properties']['stepName'] = data.name
|
||||||
|
if (type == 'function') {
|
||||||
|
item['properties']['node_data'] = {
|
||||||
|
...data,
|
||||||
|
function_lib_id: data.id,
|
||||||
|
input_field_list: data.input_field_list.map((field: any) => ({
|
||||||
|
...field,
|
||||||
|
value: field.source == 'reference' ? [] : ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == 'application') {
|
||||||
|
if (isWorkFlow(data.type)) {
|
||||||
|
const nodeData = data.work_flow.nodes[0].properties.node_data
|
||||||
|
const fileUploadSetting = nodeData.file_upload_setting
|
||||||
|
item['properties']['node_data'] = {
|
||||||
|
name: data.name,
|
||||||
|
icon: data.icon,
|
||||||
|
application_id: data.id,
|
||||||
|
api_input_field_list: data.work_flow.nodes[0].properties.api_input_field_list,
|
||||||
|
user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list,
|
||||||
|
...(!fileUploadSetting
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
...(fileUploadSetting.document ? { document_list: [] } : {}),
|
||||||
|
...(fileUploadSetting.image ? { image_list: [] } : {}),
|
||||||
|
...(fileUploadSetting.audio ? { audio_list: [] } : {})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item['properties']['node_data'] = {
|
||||||
|
name: data.name,
|
||||||
|
icon: data.icon,
|
||||||
|
application_id: data.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.workflowRef?.addNode(item)
|
||||||
|
|
||||||
|
emit('clickNodes', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmousedown(item: any, data?: any, type?: string) {
|
||||||
|
if (data) {
|
||||||
|
item['properties']['stepName'] = data.name
|
||||||
|
if (type == 'function') {
|
||||||
|
item['properties']['node_data'] = {
|
||||||
|
...data,
|
||||||
|
function_lib_id: data.id,
|
||||||
|
input_field_list: data.input_field_list.map((field: any) => ({
|
||||||
|
...field,
|
||||||
|
value: field.source == 'reference' ? [] : ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == 'application') {
|
||||||
|
if (isWorkFlow(data.type)) {
|
||||||
|
const nodeData = data.work_flow.nodes[0].properties.node_data
|
||||||
|
const fileUploadSetting = nodeData.file_upload_setting
|
||||||
|
item['properties']['node_data'] = {
|
||||||
|
name: data.name,
|
||||||
|
icon: data.icon,
|
||||||
|
application_id: data.id,
|
||||||
|
api_input_field_list: data.work_flow.nodes[0].properties.api_input_field_list,
|
||||||
|
user_input_field_list: data.work_flow.nodes[0].properties.user_input_field_list,
|
||||||
|
...(!fileUploadSetting
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
...(fileUploadSetting.document ? { document_list: [] } : {}),
|
||||||
|
...(fileUploadSetting.image ? { image_list: [] } : {}),
|
||||||
|
...(fileUploadSetting.audio ? { audio_list: [] } : {})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item['properties']['node_data'] = {
|
||||||
|
name: data.name,
|
||||||
|
icon: data.icon,
|
||||||
|
application_id: data.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.workflowRef?.onmousedown(item)
|
||||||
|
emit('onmousedown', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getList() {
|
||||||
|
applicationApi.listFunctionLib(props.id, loading).then((res: any) => {
|
||||||
|
functionLibList.value = res.data
|
||||||
|
})
|
||||||
|
applicationApi.getApplicationList(props.id, loading).then((res: any) => {
|
||||||
|
applicationList.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.workflow-dropdown-menu {
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-webkit-user-select: none; /* WebKit内核 */
|
||||||
|
-ms-user-select: none; /* IE10及以后 */
|
||||||
|
-khtml-user-select: none; /* 早期浏览器 */
|
||||||
|
-o-user-select: none; /* Opera */
|
||||||
|
user-select: none; /* CSS3属性 */
|
||||||
|
position: absolute;
|
||||||
|
top: 49px;
|
||||||
|
right: 122px;
|
||||||
|
z-index: 99;
|
||||||
|
width: 268px;
|
||||||
|
box-shadow: 0px 4px 8px 0px var(--app-text-color-light-1);
|
||||||
|
background: #ffffff;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding: 12px 12px 4px;
|
||||||
|
}
|
||||||
|
.workflow-dropdown-item {
|
||||||
|
&:hover {
|
||||||
|
background: var(--app-text-color-light-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
<template>
|
||||||
|
<div class="workflow-publish-history border-l">
|
||||||
|
<h4 class="border-b p-16-24">{{ $t('views.applicationWorkflow.setting.releaseHistory') }}</h4>
|
||||||
|
<div class="list-height pt-0">
|
||||||
|
<el-scrollbar>
|
||||||
|
<div class="p-8 pt-0">
|
||||||
|
<common-list
|
||||||
|
:data="LogData"
|
||||||
|
class="mt-8"
|
||||||
|
v-loading="loading"
|
||||||
|
@click="clickListHandle"
|
||||||
|
@mouseenter="mouseenter"
|
||||||
|
@mouseleave="mouseId = ''"
|
||||||
|
>
|
||||||
|
<template #default="{ row, index }">
|
||||||
|
<div class="flex-between">
|
||||||
|
<div style="max-width: 80%">
|
||||||
|
<h5 :class="index === 0 ? 'primary' : ''" class="flex">
|
||||||
|
<ReadWrite
|
||||||
|
@change="editName($event, row)"
|
||||||
|
:data="row.name || datetimeFormat(row.update_time)"
|
||||||
|
trigger="manual"
|
||||||
|
:write="row.writeStatus"
|
||||||
|
@close="closeWrite(row)"
|
||||||
|
/>
|
||||||
|
<el-tag v-if="index === 0" class="default-tag ml-4">{{
|
||||||
|
$t('views.applicationWorkflow.setting.latestRelease')
|
||||||
|
}}</el-tag>
|
||||||
|
</h5>
|
||||||
|
<el-text type="info" class="color-secondary flex mt-8">
|
||||||
|
<AppAvatar :size="20" class="avatar-grey mr-4">
|
||||||
|
<el-icon><UserFilled /></el-icon>
|
||||||
|
</AppAvatar>
|
||||||
|
{{ row.publish_user_name }}
|
||||||
|
</el-text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div @click.stop v-show="mouseId === row.id">
|
||||||
|
<el-dropdown trigger="click" :teleported="false">
|
||||||
|
<el-button text>
|
||||||
|
<el-icon><MoreFilled /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click.stop="openEditVersion(row)">
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
{{ $t('common.edit') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click="refreshVersion(row)">
|
||||||
|
<el-icon><RefreshLeft /></el-icon>
|
||||||
|
{{ $t('views.applicationWorkflow.setting.restoreCurrentVersion') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #empty>
|
||||||
|
<div class="text-center">
|
||||||
|
<el-text type="info"> {{ $t('chat.noHistory') }}</el-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</common-list>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { datetimeFormat } from '@/utils/time'
|
||||||
|
import { MsgSuccess, MsgError } from '@/utils/message'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route as any
|
||||||
|
|
||||||
|
const emit = defineEmits(['click', 'refreshVersion'])
|
||||||
|
const loading = ref(false)
|
||||||
|
const LogData = ref<any[]>([])
|
||||||
|
|
||||||
|
const mouseId = ref('')
|
||||||
|
|
||||||
|
function mouseenter(row: any) {
|
||||||
|
mouseId.value = row.id
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickListHandle(item: any) {
|
||||||
|
emit('click', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshVersion(item: any) {
|
||||||
|
emit('refreshVersion', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditVersion(item: any) {
|
||||||
|
item['writeStatus'] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeWrite(item: any) {
|
||||||
|
item['writeStatus'] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function editName(val: string, item: any) {
|
||||||
|
if (val) {
|
||||||
|
const obj = {
|
||||||
|
name: val
|
||||||
|
}
|
||||||
|
applicationApi.putWorkFlowVersion(id as string, item.id, obj, loading).then(() => {
|
||||||
|
MsgSuccess(t('common.modifySuccess'))
|
||||||
|
item['writeStatus'] = false
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.nameMessage'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getList() {
|
||||||
|
applicationApi.getWorkFlowVersion(id, loading).then((res: any) => {
|
||||||
|
LogData.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.workflow-publish-history {
|
||||||
|
width: 320px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 57px;
|
||||||
|
background: #ffffff;
|
||||||
|
height: calc(100vh - 57px);
|
||||||
|
z-index: 9;
|
||||||
|
.list-height {
|
||||||
|
height: calc(100vh - 120px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,487 @@
|
||||||
|
<template>
|
||||||
|
<div class="application-workflow" v-loading="loading">
|
||||||
|
<div class="header border-b flex-between p-12-24">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<back-button @click="back"></back-button>
|
||||||
|
<h4>{{ detail?.name }}</h4>
|
||||||
|
<div v-if="showHistory && disablePublic">
|
||||||
|
<el-text type="info" class="ml-16 color-secondary"
|
||||||
|
>{{ $t('views.applicationWorkflow.info.previewVersion') }}
|
||||||
|
{{ currentVersion.name || datetimeFormat(currentVersion.update_time) }}</el-text
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<el-text type="info" class="ml-16 color-secondary" v-else-if="saveTime"
|
||||||
|
>{{ $t('views.applicationWorkflow.info.saveTime')
|
||||||
|
}}{{ datetimeFormat(saveTime) }}</el-text
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div v-if="showHistory && disablePublic">
|
||||||
|
<el-button type="primary" class="mr-8" @click="refreshVersion()">
|
||||||
|
{{ $t('views.applicationWorkflow.setting.restoreVersion') }}
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button text @click="closeHistory">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-button icon="Plus" @click="showPopover = !showPopover">
|
||||||
|
{{ $t('views.applicationWorkflow.setting.addComponent') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="clickShowDebug" :disabled="showDebug">
|
||||||
|
<AppIcon iconName="app-play-outlined" class="mr-4"></AppIcon>
|
||||||
|
{{ $t('views.applicationWorkflow.setting.debug') }}</el-button
|
||||||
|
>
|
||||||
|
<el-button @click="saveApplication(true)">
|
||||||
|
<AppIcon iconName="app-save-outlined" class="mr-4"></AppIcon>
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="publicHandle">
|
||||||
|
{{ $t('views.applicationWorkflow.setting.public') }}
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<el-button text @click.stop class="ml-8 mt-4">
|
||||||
|
<el-icon class="rotate-90"><MoreFilled /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click="openHistory">
|
||||||
|
<AppIcon iconName="app-history-outlined"></AppIcon>
|
||||||
|
{{ $t('views.applicationWorkflow.setting.releaseHistory') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item>
|
||||||
|
<AppIcon iconName="app-save-outlined"></AppIcon>
|
||||||
|
{{ $t('views.applicationWorkflow.setting.autoSave') }}
|
||||||
|
<div class="ml-4">
|
||||||
|
<el-switch size="small" v-model="isSave" @change="changeSave" />
|
||||||
|
</div>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 下拉框 -->
|
||||||
|
<el-collapse-transition>
|
||||||
|
<DropdownMenu
|
||||||
|
:show="showPopover"
|
||||||
|
:id="id"
|
||||||
|
v-click-outside="clickoutside"
|
||||||
|
@clickNodes="clickNodes"
|
||||||
|
@onmousedown="onmousedown"
|
||||||
|
:workflowRef="workflowRef"
|
||||||
|
/>
|
||||||
|
</el-collapse-transition>
|
||||||
|
<!-- 主画布 -->
|
||||||
|
<div class="workflow-main" ref="workflowMainRef">
|
||||||
|
<workflow ref="workflowRef" v-if="detail" :data="detail?.work_flow" />
|
||||||
|
</div>
|
||||||
|
<!-- 调试 -->
|
||||||
|
<el-collapse-transition>
|
||||||
|
<div class="workflow-debug-container" :class="enlarge ? 'enlarge' : ''" v-if="showDebug">
|
||||||
|
<div class="workflow-debug-header" :class="!isDefaultTheme ? 'custom-header' : ''">
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-12 ml-24 flex">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(detail?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="detail?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="detail?.name"
|
||||||
|
:name="detail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
{{ detail?.name || $t('views.application.applicationForm.form.appName.label') }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="mr-16">
|
||||||
|
<el-button link @click="enlarge = !enlarge">
|
||||||
|
<AppIcon
|
||||||
|
:iconName="enlarge ? 'app-minify' : 'app-magnify'"
|
||||||
|
class="color-secondary"
|
||||||
|
style="font-size: 20px"
|
||||||
|
></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
<el-button link @click="showDebug = false">
|
||||||
|
<el-icon :size="20" class="color-secondary"><Close /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scrollbar-height">
|
||||||
|
<AiChat :application-details="detail" :type="'debug-ai-chat'"></AiChat>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-collapse-transition>
|
||||||
|
<!-- 发布历史 -->
|
||||||
|
<PublishHistory
|
||||||
|
v-if="showHistory"
|
||||||
|
@click="checkVersion"
|
||||||
|
v-click-outside="clickoutsideHistory"
|
||||||
|
@refreshVersion="refreshVersion"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onBeforeUnmount, computed, nextTick } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import type { Action } from 'element-plus'
|
||||||
|
import Workflow from '@/workflow/index.vue'
|
||||||
|
import DropdownMenu from '@/views/application-workflow/component/DropdownMenu.vue'
|
||||||
|
import PublishHistory from '@/views/application-workflow/component/PublishHistory.vue'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import { MsgSuccess, MsgError, MsgConfirm } from '@/utils/message'
|
||||||
|
import { datetimeFormat } from '@/utils/time'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { WorkFlowInstance } from '@/workflow/common/validate'
|
||||||
|
import { hasPermission } from '@/utils/permission'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
const { user, application } = useStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const isDefaultTheme = computed(() => {
|
||||||
|
return user.isDefaultTheme()
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route as any
|
||||||
|
|
||||||
|
let interval: any
|
||||||
|
const workflowRef = ref()
|
||||||
|
const workflowMainRef = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
const detail = ref<any>(null)
|
||||||
|
|
||||||
|
const showPopover = ref(false)
|
||||||
|
const showDebug = ref(false)
|
||||||
|
const enlarge = ref(false)
|
||||||
|
const saveTime = ref<any>('')
|
||||||
|
const isSave = ref(false)
|
||||||
|
const showHistory = ref(false)
|
||||||
|
const disablePublic = ref(false)
|
||||||
|
const currentVersion = ref<any>({})
|
||||||
|
const cloneWorkFlow = ref(null)
|
||||||
|
|
||||||
|
function back() {
|
||||||
|
if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) {
|
||||||
|
MsgConfirm(t('common.tip'), t('views.applicationWorkflow.tip.saveMessage'), {
|
||||||
|
confirmButtonText: t('views.applicationWorkflow.setting.exitSave'),
|
||||||
|
cancelButtonText: t('views.applicationWorkflow.setting.exit'),
|
||||||
|
type: 'warning',
|
||||||
|
distinguishCancelAndClose: true
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
saveApplication(true, true)
|
||||||
|
})
|
||||||
|
.catch((action: Action) => {
|
||||||
|
action === 'cancel' && router.push({ path: `/application/${id}/WORK_FLOW/overview` })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ path: `/application/${id}/WORK_FLOW/overview` })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function clickoutsideHistory() {
|
||||||
|
if (!disablePublic.value) {
|
||||||
|
showHistory.value = false
|
||||||
|
disablePublic.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshVersion(item?: any) {
|
||||||
|
if (item) {
|
||||||
|
renderGraphData(item)
|
||||||
|
}
|
||||||
|
if (hasPermission(`APPLICATION:MANAGE:${id}`, 'AND') && isSave.value) {
|
||||||
|
initInterval()
|
||||||
|
}
|
||||||
|
showHistory.value = false
|
||||||
|
disablePublic.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkVersion(item: any) {
|
||||||
|
disablePublic.value = true
|
||||||
|
currentVersion.value = item
|
||||||
|
renderGraphData(item)
|
||||||
|
closeInterval()
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGraphData(item: any) {
|
||||||
|
item.work_flow['nodes'].map((v: any) => {
|
||||||
|
v['properties']['noRender'] = true
|
||||||
|
})
|
||||||
|
detail.value.work_flow = item.work_flow
|
||||||
|
saveTime.value = item?.update_time
|
||||||
|
workflowRef.value?.clearGraphData()
|
||||||
|
nextTick(() => {
|
||||||
|
workflowRef.value?.render(item.work_flow)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeHistory() {
|
||||||
|
getDetail()
|
||||||
|
if (hasPermission(`APPLICATION:MANAGE:${id}`, 'AND') && isSave.value) {
|
||||||
|
initInterval()
|
||||||
|
}
|
||||||
|
showHistory.value = false
|
||||||
|
disablePublic.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function openHistory() {
|
||||||
|
showHistory.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeSave(bool: boolean) {
|
||||||
|
bool ? initInterval() : closeInterval()
|
||||||
|
localStorage.setItem('workflowAutoSave', bool.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickNodes(item: any) {
|
||||||
|
// workflowRef.value?.addNode(item)
|
||||||
|
showPopover.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmousedown(item: any) {
|
||||||
|
// workflowRef.value?.onmousedown(item)
|
||||||
|
showPopover.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickoutside() {
|
||||||
|
showPopover.value = false
|
||||||
|
}
|
||||||
|
async function publicHandle() {
|
||||||
|
// 后执行发布
|
||||||
|
workflowRef.value
|
||||||
|
?.validate()
|
||||||
|
.then(async () => {
|
||||||
|
const obj = {
|
||||||
|
work_flow: getGraphData()
|
||||||
|
}
|
||||||
|
await application.asyncPutApplication(id, obj, loading)
|
||||||
|
const workflow = new WorkFlowInstance(obj.work_flow)
|
||||||
|
try {
|
||||||
|
workflow.is_valid()
|
||||||
|
} catch (e: any) {
|
||||||
|
MsgError(e.toString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
applicationApi.putPublishApplication(id as String, obj, loading).then(() => {
|
||||||
|
|
||||||
|
application.asyncGetApplicationDetail(id, loading).then((res: any) => {
|
||||||
|
detail.value.name = res.data.name
|
||||||
|
MsgSuccess(t('views.applicationWorkflow.tip.publicSuccess'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((res: any) => {
|
||||||
|
const node = res.node
|
||||||
|
const err_message = res.errMessage
|
||||||
|
if (typeof err_message == 'string') {
|
||||||
|
MsgError(
|
||||||
|
res.node.properties?.stepName +
|
||||||
|
` ${t('views.applicationWorkflow.node').toLowerCase()} ` +
|
||||||
|
err_message.toLowerCase()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const keys = Object.keys(err_message)
|
||||||
|
MsgError(
|
||||||
|
node.properties?.stepName +
|
||||||
|
` ${t('views.applicationWorkflow.node').toLowerCase()} ` +
|
||||||
|
err_message[keys[0]]?.[0]?.message.toLowerCase()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickShowDebug = () => {
|
||||||
|
workflowRef.value
|
||||||
|
?.validate()
|
||||||
|
.then(() => {
|
||||||
|
const graphData = getGraphData()
|
||||||
|
const workflow = new WorkFlowInstance(graphData)
|
||||||
|
try {
|
||||||
|
workflow.is_valid()
|
||||||
|
detail.value = {
|
||||||
|
...detail.value,
|
||||||
|
type: 'WORK_FLOW',
|
||||||
|
...workflow.get_base_node()?.properties.node_data,
|
||||||
|
work_flow: getGraphData()
|
||||||
|
}
|
||||||
|
|
||||||
|
showDebug.value = true
|
||||||
|
} catch (e: any) {
|
||||||
|
MsgError(e.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((res: any) => {
|
||||||
|
const node = res.node
|
||||||
|
const err_message = res.errMessage
|
||||||
|
if (typeof err_message == 'string') {
|
||||||
|
MsgError(
|
||||||
|
res.node.properties?.stepName + ` ${t('views.applicationWorkflow.node')},` + err_message
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const keys = Object.keys(err_message)
|
||||||
|
MsgError(
|
||||||
|
node.properties?.stepName +
|
||||||
|
` ${t('views.applicationWorkflow.node')},` +
|
||||||
|
err_message[keys[0]]?.[0]?.message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// function clickoutsideDebug(e: any) {
|
||||||
|
// if (workflowMainRef.value && e && e.target && workflowMainRef.value.contains(e?.target)) {
|
||||||
|
// showDebug.value = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
function getGraphData() {
|
||||||
|
return workflowRef.value?.getGraphData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetail() {
|
||||||
|
application.asyncGetApplicationDetail(id).then((res: any) => {
|
||||||
|
res.data?.work_flow['nodes'].map((v: any) => {
|
||||||
|
v['properties']['noRender'] = true
|
||||||
|
})
|
||||||
|
detail.value = res.data
|
||||||
|
detail.value.stt_model_id = res.data.stt_model
|
||||||
|
detail.value.tts_model_id = res.data.tts_model
|
||||||
|
detail.value.tts_type = res.data.tts_type
|
||||||
|
saveTime.value = res.data?.update_time
|
||||||
|
application.asyncGetAccessToken(id, loading).then((res: any) => {
|
||||||
|
detail.value = { ...detail.value, ...res.data }
|
||||||
|
})
|
||||||
|
workflowRef.value?.clearGraphData()
|
||||||
|
nextTick(() => {
|
||||||
|
workflowRef.value?.render(detail.value.work_flow)
|
||||||
|
cloneWorkFlow.value = getGraphData()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveApplication(bool?: boolean, back?: boolean) {
|
||||||
|
const obj = {
|
||||||
|
work_flow: getGraphData()
|
||||||
|
}
|
||||||
|
loading.value = back || false
|
||||||
|
application
|
||||||
|
.asyncPutApplication(id, obj)
|
||||||
|
.then((res) => {
|
||||||
|
saveTime.value = new Date()
|
||||||
|
if (bool) {
|
||||||
|
cloneWorkFlow.value = getGraphData()
|
||||||
|
MsgSuccess(t('common.saveSuccess'))
|
||||||
|
if (back) {
|
||||||
|
router.push({ path: `/application/${id}/WORK_FLOW/overview` })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时保存
|
||||||
|
*/
|
||||||
|
const initInterval = () => {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
saveApplication()
|
||||||
|
}, 60000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭定时
|
||||||
|
*/
|
||||||
|
const closeInterval = () => {
|
||||||
|
if (interval) {
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getDetail()
|
||||||
|
const workflowAutoSave = localStorage.getItem('workflowAutoSave')
|
||||||
|
isSave.value = workflowAutoSave === 'true' ? true : false
|
||||||
|
// 初始化定时任务
|
||||||
|
if (hasPermission(`APPLICATION:MANAGE:${id}`, 'AND') && isSave.value) {
|
||||||
|
initInterval()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
// 清除定时任务
|
||||||
|
closeInterval()
|
||||||
|
workflowRef.value?.clearGraphData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.application-workflow {
|
||||||
|
background: var(--app-layout-bg-color);
|
||||||
|
height: 100%;
|
||||||
|
.header {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
.workflow-main {
|
||||||
|
height: calc(100vh - 62px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-dropdown-tabs {
|
||||||
|
.el-tabs__nav-wrap {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-debug-container {
|
||||||
|
z-index: 2000;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
background: var(--dialog-bg-gradient-color);
|
||||||
|
box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.1);
|
||||||
|
position: fixed;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 450px;
|
||||||
|
height: 600px;
|
||||||
|
.workflow-debug-header {
|
||||||
|
background: var(--app-header-bg-color);
|
||||||
|
height: var(--app-header-height);
|
||||||
|
line-height: var(--app-header-height);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
.scrollbar-height {
|
||||||
|
height: calc(100% - var(--app-header-height) - 24px);
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
&.enlarge {
|
||||||
|
width: 50% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
right: 0 !important;
|
||||||
|
}
|
||||||
|
.chat-width {
|
||||||
|
max-width: 100% !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-16-24">
|
||||||
|
<h4 class="mb-16">{{ $t('views.application.applicationAccess.title') }}</h4>
|
||||||
|
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="12"
|
||||||
|
:lg="12"
|
||||||
|
:xl="12"
|
||||||
|
class="mb-16"
|
||||||
|
v-for="(item, index) in platforms"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<el-card shadow="hover" class="border-none cursor" style="--el-card-padding: 24px">
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center ml-8 mr-8">
|
||||||
|
<img :src="item.logoSrc" alt="" class="icon" />
|
||||||
|
<div class="ml-12">
|
||||||
|
<h5 class="mb-4">{{ item.name }}</h5>
|
||||||
|
<el-text type="info" style="font-size: 12px">{{ item.description }}</el-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-switch
|
||||||
|
size="small"
|
||||||
|
v-model="item.isActive"
|
||||||
|
@change="changeStatus(item.key, item.isActive)"
|
||||||
|
:disabled="!item.exists"
|
||||||
|
/>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button class="mr-4" @click="openDrawer(item.key)">{{
|
||||||
|
$t('views.application.applicationAccess.setting')
|
||||||
|
}}</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<AccessSettingDrawer ref="AccessSettingDrawerRef" @refresh="refresh" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, onMounted } from 'vue'
|
||||||
|
import AccessSettingDrawer from './component/AccessSettingDrawer.vue'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { MsgSuccess } from '@/utils/message'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
// 平台数据
|
||||||
|
const platforms = reactive([
|
||||||
|
{
|
||||||
|
key: 'wecom',
|
||||||
|
logoSrc: new URL(`../../assets/logo_wechat-work.svg`, import.meta.url).href,
|
||||||
|
name: t('views.application.applicationAccess.wecom'),
|
||||||
|
description: t('views.application.applicationAccess.wecomTip'),
|
||||||
|
isActive: false,
|
||||||
|
exists: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dingtalk',
|
||||||
|
logoSrc: new URL(`../../assets/logo_dingtalk.svg`, import.meta.url).href,
|
||||||
|
name: t('views.application.applicationAccess.dingtalk'),
|
||||||
|
description: t('views.application.applicationAccess.dingtalkTip'),
|
||||||
|
isActive: false,
|
||||||
|
exists: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'wechat',
|
||||||
|
logoSrc: new URL(`../../assets/logo_wechat.svg`, import.meta.url).href,
|
||||||
|
name: t('views.application.applicationAccess.wechat'),
|
||||||
|
description: t('views.application.applicationAccess.wechatTip'),
|
||||||
|
isActive: false,
|
||||||
|
exists: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'feishu',
|
||||||
|
logoSrc: new URL(`../../assets/logo_lark.svg`, import.meta.url).href,
|
||||||
|
name: t('views.application.applicationAccess.lark'),
|
||||||
|
description: t('views.application.applicationAccess.larkTip'),
|
||||||
|
isActive: false,
|
||||||
|
exists: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'slack',
|
||||||
|
logoSrc: new URL(`../../assets/logo_slack.svg`, import.meta.url).href,
|
||||||
|
name: t('views.application.applicationAccess.slack'),
|
||||||
|
description: t('views.application.applicationAccess.slackTip'),
|
||||||
|
isActive: false,
|
||||||
|
exists: false
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const AccessSettingDrawerRef = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route as any
|
||||||
|
|
||||||
|
function openDrawer(key: string) {
|
||||||
|
AccessSettingDrawerRef.value.open(id, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
getPlatformStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlatformStatus() {
|
||||||
|
loading.value = true
|
||||||
|
applicationApi.getPlatformStatus(id).then((res: any) => {
|
||||||
|
platforms.forEach((platform) => {
|
||||||
|
platform.isActive = res.data[platform.key][1]
|
||||||
|
platform.exists = res.data[platform.key][0]
|
||||||
|
})
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeStatus(type: string, value: boolean) {
|
||||||
|
const data = {
|
||||||
|
type: type,
|
||||||
|
status: value
|
||||||
|
}
|
||||||
|
applicationApi.updatePlatformStatus(id, data).then(() => {
|
||||||
|
MsgSuccess(t('common.saveSuccess'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getPlatformStatus()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.p-16-24 {
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-16 {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-between {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-8 {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-8 {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-12 {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-4 {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 32px; // 设置图标宽度
|
||||||
|
height: 32px; // 设置图标高度
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,816 @@
|
||||||
|
<template>
|
||||||
|
<LayoutContainer class="create-application">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex-between w-full">
|
||||||
|
<h3>
|
||||||
|
{{ $t('common.setting') }}
|
||||||
|
</h3>
|
||||||
|
<el-button type="primary" @click="submit(applicationFormRef)" :disabled="loading">
|
||||||
|
{{ $t('views.application.applicationForm.buttons.publish') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-row v-loading="loading">
|
||||||
|
<el-col :span="10">
|
||||||
|
<div class="p-24 mb-16" style="padding-bottom: 0">
|
||||||
|
<h4 class="title-decoration-1">
|
||||||
|
{{ $t('views.applicationOverview.appInfo.header') }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="scrollbar-height-left">
|
||||||
|
<el-scrollbar>
|
||||||
|
<el-form
|
||||||
|
hide-required-asterisk
|
||||||
|
ref="applicationFormRef"
|
||||||
|
:model="applicationForm"
|
||||||
|
:rules="rules"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
class="p-24"
|
||||||
|
style="padding-top: 0"
|
||||||
|
>
|
||||||
|
<el-form-item prop="name">
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span
|
||||||
|
>{{ $t('views.application.applicationForm.form.appName.label') }}
|
||||||
|
<span class="danger">*</span></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-input
|
||||||
|
v-model="applicationForm.name"
|
||||||
|
maxlength="64"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.appName.placeholder')"
|
||||||
|
show-word-limit
|
||||||
|
@blur="applicationForm.name = applicationForm.name?.trim()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.application.applicationForm.form.appDescription.label')"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="applicationForm.desc"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.application.applicationForm.form.appDescription.placeholder')
|
||||||
|
"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="256"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.aiModel.label')">
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span>{{ $t('views.application.applicationForm.form.aiModel.label') }}</span>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="openAIParamSettingDialog"
|
||||||
|
:disabled="!applicationForm.model_id"
|
||||||
|
>
|
||||||
|
{{ $t('common.paramSetting') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<ModelSelect
|
||||||
|
v-model="applicationForm.model_id"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.aiModel.placeholder')"
|
||||||
|
:options="modelOptions"
|
||||||
|
@change="model_change"
|
||||||
|
@submitModel="getModel"
|
||||||
|
showFooter
|
||||||
|
:model-type="'LLM'"
|
||||||
|
></ModelSelect>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.application.applicationForm.form.roleSettings.label')"
|
||||||
|
>
|
||||||
|
<MdEditorMagnify
|
||||||
|
:title="$t('views.application.applicationForm.form.roleSettings.label')"
|
||||||
|
v-model="applicationForm.model_setting.system"
|
||||||
|
style="height: 120px"
|
||||||
|
@submitDialog="submitSystemDialog"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.application.applicationForm.form.roleSettings.placeholder')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
prop="model_setting.no_references_prompt"
|
||||||
|
:rules="{
|
||||||
|
required: applicationForm.model_id,
|
||||||
|
message: $t('views.application.applicationForm.form.prompt.requiredMessage'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<span class="mr-4"
|
||||||
|
>{{
|
||||||
|
$t('views.application.applicationForm.form.prompt.label') +
|
||||||
|
$t('views.application.applicationForm.form.prompt.noReferences')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="
|
||||||
|
$t('views.application.applicationForm.form.prompt.noReferencesTooltip', {
|
||||||
|
question: '{question}'
|
||||||
|
})
|
||||||
|
"
|
||||||
|
placement="right"
|
||||||
|
popper-class="max-w-350"
|
||||||
|
>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
<span class="danger ml-4" v-if="applicationForm.model_id">*</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<MdEditorMagnify
|
||||||
|
:title="
|
||||||
|
$t('views.application.applicationForm.form.prompt.label') +
|
||||||
|
$t('views.application.applicationForm.form.prompt.noReferences')
|
||||||
|
"
|
||||||
|
v-model="applicationForm.model_setting.no_references_prompt"
|
||||||
|
style="height: 120px"
|
||||||
|
@submitDialog="submitNoReferencesPromptDialog"
|
||||||
|
placeholder="{question}"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.application.applicationForm.form.historyRecord.label')"
|
||||||
|
@click.prevent
|
||||||
|
>
|
||||||
|
<el-input-number
|
||||||
|
v-model="applicationForm.dialogue_number"
|
||||||
|
:min="0"
|
||||||
|
:value-on-clear="0"
|
||||||
|
controls-position="right"
|
||||||
|
class="w-full"
|
||||||
|
:step="1"
|
||||||
|
:step-strictly="true"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
label="$t('views.application.applicationForm.form.relatedKnowledgeBase')"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span>{{
|
||||||
|
$t('views.application.applicationForm.form.relatedKnowledge.label')
|
||||||
|
}}</span>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" link @click="openParamSettingDialog">
|
||||||
|
<AppIcon iconName="app-operation" class="mr-4"></AppIcon>
|
||||||
|
{{ $t('common.paramSetting') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" link @click="openDatasetDialog">
|
||||||
|
<el-icon class="mr-4">
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
{{ $t('common.add') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="w-full">
|
||||||
|
<el-text type="info" v-if="applicationForm.dataset_id_list?.length === 0"
|
||||||
|
>{{ $t('views.application.applicationForm.form.relatedKnowledge.placeholder') }}
|
||||||
|
</el-text>
|
||||||
|
<el-row :gutter="12" v-else>
|
||||||
|
<el-col
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="24"
|
||||||
|
:lg="12"
|
||||||
|
:xl="12"
|
||||||
|
class="mb-8"
|
||||||
|
v-for="(item, index) in applicationForm.dataset_id_list"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<el-card class="relate-dataset-card border-r-4" shadow="never">
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center" style="width: 80%">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="relatedObject(datasetList, item, 'id')?.type === '1'"
|
||||||
|
class="mr-8 avatar-purple"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
>
|
||||||
|
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="relatedObject(datasetList, item, 'id')?.type === '2'"
|
||||||
|
class="mr-8 avatar-purple"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar v-else class="mr-8 avatar-blue" shape="square" :size="32">
|
||||||
|
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="ellipsis cursor"
|
||||||
|
:title="relatedObject(datasetList, item, 'id')?.name"
|
||||||
|
>
|
||||||
|
{{ relatedObject(datasetList, item, 'id')?.name }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<el-button text @click="removeDataset(item)">
|
||||||
|
<el-icon>
|
||||||
|
<Close />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.application.applicationForm.form.prompt.label')"
|
||||||
|
prop="model_setting.prompt"
|
||||||
|
:rules="{
|
||||||
|
required: applicationForm.model_id,
|
||||||
|
message: $t('views.application.applicationForm.form.prompt.requiredMessage'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<span class="mr-4">
|
||||||
|
{{ $t('views.application.applicationForm.form.prompt.label') }}
|
||||||
|
{{ $t('views.application.applicationForm.form.prompt.references') }}
|
||||||
|
</span>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="
|
||||||
|
$t('views.application.applicationForm.form.prompt.referencesTooltip', {
|
||||||
|
data: '{data}',
|
||||||
|
question: '{question}'
|
||||||
|
})
|
||||||
|
"
|
||||||
|
popper-class="max-w-350"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
<span class="danger ml-4" v-if="applicationForm.model_id">*</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<MdEditorMagnify
|
||||||
|
:title="
|
||||||
|
$t('views.application.applicationForm.form.prompt.label') +
|
||||||
|
$t('views.application.applicationForm.form.prompt.references')
|
||||||
|
"
|
||||||
|
v-model="applicationForm.model_setting.prompt"
|
||||||
|
style="height: 150px"
|
||||||
|
@submitDialog="submitPromptDialog"
|
||||||
|
:placeholder="defaultPrompt"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.prologue')">
|
||||||
|
<MdEditorMagnify
|
||||||
|
:title="$t('views.application.applicationForm.form.prologue')"
|
||||||
|
v-model="applicationForm.prologue"
|
||||||
|
style="height: 150px"
|
||||||
|
@submitDialog="submitPrologueDialog"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item @click.prevent>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span class="mr-4">
|
||||||
|
{{ $t('views.application.applicationForm.form.reasoningContent.label') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<el-button type="primary" link @click="openReasoningParamSettingDialog">
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-switch
|
||||||
|
class="ml-8"
|
||||||
|
size="small"
|
||||||
|
v-model="applicationForm.model_setting.reasoning_content_enable"
|
||||||
|
@change="sttModelEnableChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
prop="stt_model_id"
|
||||||
|
:rules="{
|
||||||
|
required: applicationForm.stt_model_enable,
|
||||||
|
message: $t('views.application.applicationForm.form.voiceInput.requiredMessage'),
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span class="mr-4">
|
||||||
|
{{ $t('views.application.applicationForm.form.voiceInput.label') }}
|
||||||
|
<span class="danger" v-if="applicationForm.stt_model_enable">*</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<el-checkbox
|
||||||
|
v-if="applicationForm.stt_model_enable"
|
||||||
|
v-model="applicationForm.stt_autosend"
|
||||||
|
>{{
|
||||||
|
$t('views.application.applicationForm.form.voiceInput.autoSend')
|
||||||
|
}}</el-checkbox
|
||||||
|
>
|
||||||
|
<el-switch
|
||||||
|
class="ml-8"
|
||||||
|
size="small"
|
||||||
|
v-model="applicationForm.stt_model_enable"
|
||||||
|
@change="sttModelEnableChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<ModelSelect
|
||||||
|
v-show="applicationForm.stt_model_enable"
|
||||||
|
v-model="applicationForm.stt_model_id"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.voiceInput.placeholder')"
|
||||||
|
:options="sttModelOptions"
|
||||||
|
:model-type="'STT'"
|
||||||
|
></ModelSelect>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
prop="tts_model_id"
|
||||||
|
:rules="{
|
||||||
|
required: applicationForm.tts_type === 'TTS' && applicationForm.tts_model_enable,
|
||||||
|
message: $t('views.application.applicationForm.form.voicePlay.requiredMessage'),
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span class="mr-4"
|
||||||
|
>{{ $t('views.application.applicationForm.form.voicePlay.label') }}
|
||||||
|
<span
|
||||||
|
class="danger"
|
||||||
|
v-if="
|
||||||
|
applicationForm.tts_type === 'TTS' && applicationForm.tts_model_enable
|
||||||
|
"
|
||||||
|
>*</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="flex">
|
||||||
|
<el-checkbox
|
||||||
|
v-if="applicationForm.tts_model_enable"
|
||||||
|
v-model="applicationForm.tts_autoplay"
|
||||||
|
>{{
|
||||||
|
$t('views.application.applicationForm.form.voicePlay.autoPlay')
|
||||||
|
}}</el-checkbox
|
||||||
|
>
|
||||||
|
<el-switch
|
||||||
|
class="ml-8"
|
||||||
|
size="small"
|
||||||
|
v-model="applicationForm.tts_model_enable"
|
||||||
|
@change="ttsModelEnableChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="w-full">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="applicationForm.tts_type"
|
||||||
|
v-show="applicationForm.tts_model_enable"
|
||||||
|
class="mb-8"
|
||||||
|
>
|
||||||
|
<el-radio value="BROWSER">{{
|
||||||
|
$t('views.application.applicationForm.form.voicePlay.browser')
|
||||||
|
}}</el-radio>
|
||||||
|
<el-radio value="TTS">{{
|
||||||
|
$t('views.application.applicationForm.form.voicePlay.tts')
|
||||||
|
}}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
<div class="flex-between w-full">
|
||||||
|
<ModelSelect
|
||||||
|
v-if="applicationForm.tts_type === 'TTS' && applicationForm.tts_model_enable"
|
||||||
|
v-model="applicationForm.tts_model_id"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.application.applicationForm.form.voicePlay.placeholder')
|
||||||
|
"
|
||||||
|
:options="ttsModelOptions"
|
||||||
|
@change="ttsModelChange()"
|
||||||
|
:model-type="'TTS'"
|
||||||
|
></ModelSelect>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="applicationForm.tts_type === 'TTS'"
|
||||||
|
@click="openTTSParamSettingDialog"
|
||||||
|
:disabled="!applicationForm.tts_model_id"
|
||||||
|
class="ml-8"
|
||||||
|
>
|
||||||
|
<el-icon><Operation /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="14" class="p-24 border-l">
|
||||||
|
<h4 class="title-decoration-1 mb-16">
|
||||||
|
{{ $t('views.application.applicationForm.title.appTest') }}
|
||||||
|
</h4>
|
||||||
|
<div class="dialog-bg">
|
||||||
|
<div class="flex align-center p-16 mb-8">
|
||||||
|
<div
|
||||||
|
class="edit-avatar mr-12"
|
||||||
|
@mouseenter="showEditIcon = true"
|
||||||
|
@mouseleave="showEditIcon = false"
|
||||||
|
>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(applicationForm?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="applicationForm?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="applicationForm?.name"
|
||||||
|
:name="applicationForm?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="showEditIcon"
|
||||||
|
shape="square"
|
||||||
|
class="edit-mask"
|
||||||
|
:size="32"
|
||||||
|
@click="openEditAvatar"
|
||||||
|
>
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</AppAvatar>
|
||||||
|
</div>
|
||||||
|
<h4>
|
||||||
|
{{
|
||||||
|
applicationForm?.name || $t('views.application.applicationForm.form.appName.label')
|
||||||
|
}}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="scrollbar-height">
|
||||||
|
<AiChat :applicationDetails="applicationForm" :type="'debug-ai-chat'"></AiChat>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<AIModeParamSettingDialog ref="AIModeParamSettingDialogRef" @refresh="refreshForm" />
|
||||||
|
<TTSModeParamSettingDialog ref="TTSModeParamSettingDialogRef" @refresh="refreshTTSForm" />
|
||||||
|
<ParamSettingDialog ref="ParamSettingDialogRef" @refresh="refreshParam" />
|
||||||
|
<AddDatasetDialog
|
||||||
|
ref="AddDatasetDialogRef"
|
||||||
|
@addData="addDataset"
|
||||||
|
:data="datasetList"
|
||||||
|
@refresh="refresh"
|
||||||
|
:loading="datasetLoading"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EditAvatarDialog ref="EditAvatarDialogRef" @refresh="refreshIcon" />
|
||||||
|
<ReasoningParamSettingDialog
|
||||||
|
ref="ReasoningParamSettingDialogRef"
|
||||||
|
@refresh="submitReasoningDialog"
|
||||||
|
/>
|
||||||
|
</LayoutContainer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, onMounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { groupBy } from 'lodash'
|
||||||
|
import AIModeParamSettingDialog from './component/AIModeParamSettingDialog.vue'
|
||||||
|
import ParamSettingDialog from './component/ParamSettingDialog.vue'
|
||||||
|
import AddDatasetDialog from './component/AddDatasetDialog.vue'
|
||||||
|
import EditAvatarDialog from '@/views/application-overview/component/EditAvatarDialog.vue'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import type { ApplicationFormType } from '@/api/type/application'
|
||||||
|
import { relatedObject } from '@/utils/utils'
|
||||||
|
import { MsgSuccess, MsgWarning } from '@/utils/message'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import TTSModeParamSettingDialog from './component/TTSModeParamSettingDialog.vue'
|
||||||
|
import ReasoningParamSettingDialog from './component/ReasoningParamSettingDialog.vue'
|
||||||
|
|
||||||
|
const { model, application } = useStore()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route as any
|
||||||
|
// @ts-ignore
|
||||||
|
const defaultPrompt = t('views.application.applicationForm.form.prompt.defaultPrompt', {
|
||||||
|
data: '{data}',
|
||||||
|
question: '{question}'
|
||||||
|
})
|
||||||
|
|
||||||
|
const optimizationPrompt =
|
||||||
|
t('views.application.applicationForm.dialog.defaultPrompt1', {
|
||||||
|
question: '{question}'
|
||||||
|
}) +
|
||||||
|
'<data></data>' +
|
||||||
|
t('views.application.applicationForm.dialog.defaultPrompt2')
|
||||||
|
|
||||||
|
const AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()
|
||||||
|
const ReasoningParamSettingDialogRef = ref<InstanceType<typeof ReasoningParamSettingDialog>>()
|
||||||
|
const TTSModeParamSettingDialogRef = ref<InstanceType<typeof TTSModeParamSettingDialog>>()
|
||||||
|
const ParamSettingDialogRef = ref<InstanceType<typeof ParamSettingDialog>>()
|
||||||
|
|
||||||
|
const applicationFormRef = ref<FormInstance>()
|
||||||
|
const AddDatasetDialogRef = ref()
|
||||||
|
const EditAvatarDialogRef = ref()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const datasetLoading = ref(false)
|
||||||
|
const applicationForm = ref<ApplicationFormType>({
|
||||||
|
name: '',
|
||||||
|
desc: '',
|
||||||
|
model_id: '',
|
||||||
|
dialogue_number: 1,
|
||||||
|
prologue: t('views.application.applicationForm.form.defaultPrologue'),
|
||||||
|
dataset_id_list: [],
|
||||||
|
dataset_setting: {
|
||||||
|
top_n: 3,
|
||||||
|
similarity: 0.6,
|
||||||
|
max_paragraph_char_number: 5000,
|
||||||
|
search_mode: 'embedding',
|
||||||
|
no_references_setting: {
|
||||||
|
status: 'ai_questioning',
|
||||||
|
value: '{question}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
model_setting: {
|
||||||
|
prompt: defaultPrompt,
|
||||||
|
system: t('views.application.applicationForm.form.roleSettings.placeholder'),
|
||||||
|
no_references_prompt: '{question}',
|
||||||
|
reasoning_content_enable: false
|
||||||
|
},
|
||||||
|
model_params_setting: {},
|
||||||
|
problem_optimization: false,
|
||||||
|
problem_optimization_prompt: optimizationPrompt,
|
||||||
|
stt_model_id: '',
|
||||||
|
tts_model_id: '',
|
||||||
|
stt_model_enable: false,
|
||||||
|
tts_model_enable: false,
|
||||||
|
tts_type: 'BROWSER',
|
||||||
|
type: 'SIMPLE'
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive<FormRules<ApplicationFormType>>({
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationForm.form.appName.placeholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const modelOptions = ref<any>(null)
|
||||||
|
const datasetList = ref([])
|
||||||
|
const sttModelOptions = ref<any>(null)
|
||||||
|
const ttsModelOptions = ref<any>(null)
|
||||||
|
const showEditIcon = ref(false)
|
||||||
|
|
||||||
|
function submitPrologueDialog(val: string) {
|
||||||
|
applicationForm.value.prologue = val
|
||||||
|
}
|
||||||
|
function submitPromptDialog(val: string) {
|
||||||
|
applicationForm.value.model_setting.prompt = val
|
||||||
|
}
|
||||||
|
function submitNoReferencesPromptDialog(val: string) {
|
||||||
|
applicationForm.value.model_setting.no_references_prompt = val
|
||||||
|
}
|
||||||
|
function submitSystemDialog(val: string) {
|
||||||
|
applicationForm.value.model_setting.system = val
|
||||||
|
}
|
||||||
|
function submitReasoningDialog(val: any) {
|
||||||
|
applicationForm.value.model_setting = {
|
||||||
|
...applicationForm.value.model_setting,
|
||||||
|
...val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
application.asyncPutApplication(id, applicationForm.value, loading).then((res) => {
|
||||||
|
MsgSuccess(t('common.saveSuccess'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const model_change = (model_id?: string) => {
|
||||||
|
applicationForm.value.model_id = model_id
|
||||||
|
if (model_id) {
|
||||||
|
AIModeParamSettingDialogRef.value?.reset_default(model_id, id)
|
||||||
|
} else {
|
||||||
|
refreshForm({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const openAIParamSettingDialog = () => {
|
||||||
|
if (applicationForm.value.model_id) {
|
||||||
|
AIModeParamSettingDialogRef.value?.open(
|
||||||
|
applicationForm.value.model_id,
|
||||||
|
id,
|
||||||
|
applicationForm.value.model_params_setting
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openReasoningParamSettingDialog = () => {
|
||||||
|
ReasoningParamSettingDialogRef.value?.open(applicationForm.value.model_setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openTTSParamSettingDialog = () => {
|
||||||
|
if (applicationForm.value.tts_model_id) {
|
||||||
|
TTSModeParamSettingDialogRef.value?.open(
|
||||||
|
applicationForm.value.tts_model_id,
|
||||||
|
id,
|
||||||
|
applicationForm.value.tts_model_params_setting
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openParamSettingDialog = () => {
|
||||||
|
ParamSettingDialogRef.value?.open(applicationForm.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshParam(data: any) {
|
||||||
|
applicationForm.value = { ...applicationForm.value, ...data }
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshForm(data: any) {
|
||||||
|
applicationForm.value.model_params_setting = data
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshTTSForm(data: any) {
|
||||||
|
applicationForm.value.tts_model_params_setting = data
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDataset(id: any) {
|
||||||
|
if (applicationForm.value.dataset_id_list) {
|
||||||
|
applicationForm.value.dataset_id_list.splice(
|
||||||
|
applicationForm.value.dataset_id_list.indexOf(id),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDataset(val: Array<string>) {
|
||||||
|
applicationForm.value.dataset_id_list = val
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDatasetDialog() {
|
||||||
|
AddDatasetDialogRef.value.open(applicationForm.value.dataset_id_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetail() {
|
||||||
|
application.asyncGetApplicationDetail(id, loading).then((res: any) => {
|
||||||
|
applicationForm.value = res.data
|
||||||
|
applicationForm.value.model_id = res.data.model
|
||||||
|
applicationForm.value.stt_model_id = res.data.stt_model
|
||||||
|
applicationForm.value.tts_model_id = res.data.tts_model
|
||||||
|
applicationForm.value.tts_type = res.data.tts_type
|
||||||
|
applicationForm.value.model_setting.no_references_prompt =
|
||||||
|
res.data.model_setting.no_references_prompt || '{question}'
|
||||||
|
application.asyncGetAccessToken(id, loading).then((res: any) => {
|
||||||
|
applicationForm.value = { ...applicationForm.value, ...res.data }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataset() {
|
||||||
|
application.asyncGetApplicationDataset(id, datasetLoading).then((res: any) => {
|
||||||
|
datasetList.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModel() {
|
||||||
|
loading.value = true
|
||||||
|
applicationApi
|
||||||
|
.getApplicationModel(id)
|
||||||
|
.then((res: any) => {
|
||||||
|
modelOptions.value = groupBy(res?.data, 'provider')
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSTTModel() {
|
||||||
|
loading.value = true
|
||||||
|
applicationApi
|
||||||
|
.getApplicationSTTModel(id)
|
||||||
|
.then((res: any) => {
|
||||||
|
sttModelOptions.value = groupBy(res?.data, 'provider')
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTTSModel() {
|
||||||
|
loading.value = true
|
||||||
|
applicationApi
|
||||||
|
.getApplicationTTSModel(id)
|
||||||
|
.then((res: any) => {
|
||||||
|
ttsModelOptions.value = groupBy(res?.data, 'provider')
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ttsModelChange() {
|
||||||
|
if (applicationForm.value.tts_model_id) {
|
||||||
|
TTSModeParamSettingDialogRef.value?.reset_default(applicationForm.value.tts_model_id, id)
|
||||||
|
} else {
|
||||||
|
refreshTTSForm({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ttsModelEnableChange() {
|
||||||
|
if (!applicationForm.value.tts_model_enable) {
|
||||||
|
applicationForm.value.tts_model_id = ''
|
||||||
|
applicationForm.value.tts_type = 'BROWSER'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sttModelEnableChange() {
|
||||||
|
if (!applicationForm.value.stt_model_enable) {
|
||||||
|
applicationForm.value.stt_model_id = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditAvatar() {
|
||||||
|
EditAvatarDialogRef.value.open(applicationForm.value)
|
||||||
|
}
|
||||||
|
function refreshIcon() {
|
||||||
|
getDetail()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
getDataset()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getModel()
|
||||||
|
getDataset()
|
||||||
|
getDetail()
|
||||||
|
getSTTModel()
|
||||||
|
getTTSModel()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.create-application {
|
||||||
|
.relate-dataset-card {
|
||||||
|
color: var(--app-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-bg {
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--dialog-bg-gradient-color);
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-height-left {
|
||||||
|
height: calc(var(--app-main-height) - 64px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-height {
|
||||||
|
height: calc(var(--app-main-height) - 166px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.prologue-md-editor {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
align-center
|
||||||
|
:title="$t('common.paramSetting')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
style="width: 550px"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<DynamicsForm
|
||||||
|
v-model="form_data"
|
||||||
|
:model="form_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
:render_data="model_form_field"
|
||||||
|
ref="dynamicsFormRef"
|
||||||
|
>
|
||||||
|
</DynamicsForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">
|
||||||
|
{{ $t('common.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submit" :loading="loading">
|
||||||
|
{{ $t('common.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { FormField } from '@/components/dynamics-form/type'
|
||||||
|
import modelAPi from '@/api/model'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import DynamicsForm from '@/components/dynamics-form/index.vue'
|
||||||
|
const model_form_field = ref<Array<FormField>>([])
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
|
||||||
|
const form_data = ref<any>({})
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const getApi = (model_id: string, application_id?: string) => {
|
||||||
|
return application_id
|
||||||
|
? applicationApi.getModelParamsForm(application_id, model_id, loading)
|
||||||
|
: modelAPi.getModelParamsForm(model_id, loading)
|
||||||
|
}
|
||||||
|
const open = (model_id: string, application_id?: string, model_setting_data?: any) => {
|
||||||
|
form_data.value = {}
|
||||||
|
const api = getApi(model_id, application_id)
|
||||||
|
api.then((ok) => {
|
||||||
|
model_form_field.value = ok.data
|
||||||
|
// 渲染动态表单
|
||||||
|
dynamicsFormRef.value?.render(model_form_field.value, model_setting_data)
|
||||||
|
})
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset_default = (model_id: string, application_id?: string) => {
|
||||||
|
const api = getApi(model_id, application_id)
|
||||||
|
api.then((ok) => {
|
||||||
|
model_form_field.value = ok.data
|
||||||
|
const model_setting_data = ok.data
|
||||||
|
.map((item) => {
|
||||||
|
if (item.show_default_value === false) {
|
||||||
|
return { [item.field]: undefined }
|
||||||
|
} else {
|
||||||
|
return { [item.field]: item.default_value }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.reduce((x, y) => ({ ...x, ...y }), {})
|
||||||
|
|
||||||
|
emit('refresh', model_setting_data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
dynamicsFormRef.value?.validate().then(() => {
|
||||||
|
emit('refresh', form_data.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, reset_default })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,400 @@
|
||||||
|
<template>
|
||||||
|
<el-drawer v-model="visible" size="60%" :append-to-body="true">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex align-center" style="margin-left: -8px">
|
||||||
|
<h4>{{ drawerTitle }}</h4>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-form
|
||||||
|
v-if="dataLoaded"
|
||||||
|
ref="formRef"
|
||||||
|
:model="form[configType]"
|
||||||
|
label-width="120px"
|
||||||
|
:rules="rules[configType]"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
>
|
||||||
|
<h4 class="title-decoration-1 mb-16">{{ infoTitle }}</h4>
|
||||||
|
|
||||||
|
<template v-for="(item, key) in configFields[configType]" :key="key">
|
||||||
|
<el-form-item :label="item.label" :prop="key">
|
||||||
|
<el-input
|
||||||
|
v-model="form[configType][key]"
|
||||||
|
:type="isPasswordField(key) ? (passwordVisible[key] ? 'text' : 'password') : 'text'"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
:show-password="isPasswordField(key)"
|
||||||
|
>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<div v-if="configType === 'wechat'" class="flex align-center mb-16">
|
||||||
|
<span class="lighter mr-8">{{
|
||||||
|
$t('views.application.applicationAccess.wecomSetting.authenticationSuccessful')
|
||||||
|
}}</span>
|
||||||
|
<el-switch v-if="configType === 'wechat'" v-model="form[configType].is_certification" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="title-decoration-1 mb-16">
|
||||||
|
{{ $t('views.application.applicationAccess.callback') }}
|
||||||
|
</h4>
|
||||||
|
<el-form-item label="URL" prop="callback_url">
|
||||||
|
<el-input
|
||||||
|
v-model="form[configType].callback_url"
|
||||||
|
:placeholder="$t('views.application.applicationAccess.callbackTip')"
|
||||||
|
readonly
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="copyClick(form[configType].callback_url)">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-text type="info" v-if="configType === 'wechat'">
|
||||||
|
{{ $t('views.application.applicationAccess.copyUrl') }}
|
||||||
|
<a
|
||||||
|
class="primary"
|
||||||
|
href="https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev"
|
||||||
|
target="_blank"
|
||||||
|
>{{ $t('views.application.applicationAccess.wechatPlatform') }}</a
|
||||||
|
>{{ $t('views.application.applicationAccess.wechatSetting.urlInfo') }}
|
||||||
|
</el-text>
|
||||||
|
<el-text type="info" v-if="configType === 'dingtalk'">
|
||||||
|
{{ $t('views.application.applicationAccess.copyUrl') }}
|
||||||
|
<a
|
||||||
|
class="primary"
|
||||||
|
href="https://open-dev.dingtalk.com/fe/app?hash=%23%2Fcorp%2Fapp#/corp/app"
|
||||||
|
target="_blank"
|
||||||
|
>{{ $t('views.application.applicationAccess.dingtalkPlatform') }}</a
|
||||||
|
>{{ $t('views.application.applicationAccess.dingtalkSetting.urlInfo') }}
|
||||||
|
</el-text>
|
||||||
|
<el-text type="info" v-if="configType === 'wecom'">
|
||||||
|
{{ $t('views.application.applicationAccess.copyUrl') }}
|
||||||
|
<a
|
||||||
|
class="primary"
|
||||||
|
href="https://work.weixin.qq.com/wework_admin/frame#apps"
|
||||||
|
target="_blank"
|
||||||
|
>{{ $t('views.application.applicationAccess.wecomPlatform') }}</a
|
||||||
|
>{{ $t('views.application.applicationAccess.wecomSetting.urlInfo') }}
|
||||||
|
</el-text>
|
||||||
|
<el-text type="info" v-if="configType === 'feishu'">
|
||||||
|
{{ $t('views.application.applicationAccess.copyUrl') }}
|
||||||
|
<a class="primary" href="https://open.feishu.cn/app/" target="_blank">{{
|
||||||
|
$t('views.application.applicationAccess.larkPlatform')
|
||||||
|
}}</a
|
||||||
|
>{{ $t('views.application.applicationAccess.larkSetting.urlInfo') }}
|
||||||
|
</el-text>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button @click="closeDrawer">{{ $t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="submit" :disabled="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { MsgError, MsgSuccess } from '@/utils/message'
|
||||||
|
import { copyClick } from '@/utils/clipboard'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
type PlatformType = 'wechat' | 'dingtalk' | 'wecom' | 'feishu' | 'slack'
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const visible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const dataLoaded = ref(false)
|
||||||
|
const configType = ref<PlatformType>('wechat')
|
||||||
|
const route = useRoute()
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = route as any
|
||||||
|
|
||||||
|
const form = reactive<any>({
|
||||||
|
wechat: {
|
||||||
|
app_id: '',
|
||||||
|
app_secret: '',
|
||||||
|
token: '',
|
||||||
|
encoding_aes_key: '',
|
||||||
|
is_certification: false,
|
||||||
|
callback_url: ''
|
||||||
|
},
|
||||||
|
dingtalk: { client_id: '', client_secret: '', callback_url: '' },
|
||||||
|
wecom: {
|
||||||
|
app_id: '',
|
||||||
|
agent_id: '',
|
||||||
|
secret: '',
|
||||||
|
token: '',
|
||||||
|
encoding_aes_key: '',
|
||||||
|
callback_url: ''
|
||||||
|
},
|
||||||
|
feishu: { app_id: '', app_secret: '', verification_token: '', callback_url: '' },
|
||||||
|
slack: { signing_secret: '', bot_user_token: '', callback_url: '' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive<{ [propName: string]: any }>({
|
||||||
|
wechat: {
|
||||||
|
app_id: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wechatSetting.appIdPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
app_secret: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wechatSetting.appSecretPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
token: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wechatSetting.tokenPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
encoding_aes_key: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wechatSetting.aesKeyPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dingtalk: {
|
||||||
|
client_id: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.dingtalkSetting.clientIdPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
client_secret: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.dingtalkSetting.clientSecretPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
wecom: {
|
||||||
|
app_id: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wecomSetting.cropIdPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
agent_id: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wecomSetting.agentIdPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
secret: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wecomSetting.secretPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
token: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wecomSetting.tokenPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
encoding_aes_key: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.wecomSetting.encodingAesKeyPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
feishu: {
|
||||||
|
app_id: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
app_secret: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
verification_token: [
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
message: t('views.application.applicationAccess.larkSetting.verificationTokenPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
slack: {
|
||||||
|
signing_secret: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.slackSetting.signingSecretPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
bot_user_token: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationAccess.slackSetting.botUserTokenPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const configFields: { [propName: string]: { [propName: string]: any } } = {
|
||||||
|
wechat: {
|
||||||
|
app_id: {
|
||||||
|
label: t('views.application.applicationAccess.wechatSetting.appId'),
|
||||||
|
placeholder: ''
|
||||||
|
},
|
||||||
|
app_secret: {
|
||||||
|
label: t('views.application.applicationAccess.wechatSetting.appSecret'),
|
||||||
|
placeholder: ''
|
||||||
|
},
|
||||||
|
token: { label: t('views.application.applicationAccess.wechatSetting.token'), placeholder: '' },
|
||||||
|
encoding_aes_key: {
|
||||||
|
label: t('views.application.applicationAccess.wechatSetting.aesKey'),
|
||||||
|
placeholder: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dingtalk: {
|
||||||
|
client_id: { label: 'Client ID', placeholder: '' },
|
||||||
|
client_secret: { label: 'Client Secret', placeholder: '' }
|
||||||
|
},
|
||||||
|
wecom: {
|
||||||
|
app_id: {
|
||||||
|
label: t('views.application.applicationAccess.wecomSetting.cropId'),
|
||||||
|
placeholder: ''
|
||||||
|
},
|
||||||
|
agent_id: { label: 'Agent ID', placeholder: '' },
|
||||||
|
secret: { label: 'Secret', placeholder: '' },
|
||||||
|
token: { label: 'Token', placeholder: '' },
|
||||||
|
encoding_aes_key: { label: 'EncodingAESKey', placeholder: '' }
|
||||||
|
},
|
||||||
|
feishu: {
|
||||||
|
app_id: { label: 'App ID', placeholder: '' },
|
||||||
|
app_secret: { label: 'App Secret', placeholder: '' },
|
||||||
|
verification_token: { label: 'Verification Token', placeholder: '' }
|
||||||
|
},
|
||||||
|
slack: {
|
||||||
|
signing_secret: { label: 'Signing Secret', placeholder: '' },
|
||||||
|
bot_user_token: { label: 'Bot User Token', placeholder: '' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordFields = new Set([
|
||||||
|
'app_secret',
|
||||||
|
'client_secret',
|
||||||
|
'secret',
|
||||||
|
'bot_user_token',
|
||||||
|
'signing_secret'
|
||||||
|
])
|
||||||
|
|
||||||
|
const drawerTitle = computed(
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
wechat: t('views.application.applicationAccess.wechatSetting.title'),
|
||||||
|
dingtalk: t('views.application.applicationAccess.dingtalkSetting.title'),
|
||||||
|
wecom: t('views.application.applicationAccess.wecomSetting.title'),
|
||||||
|
feishu: t('views.application.applicationAccess.larkSetting.title'),
|
||||||
|
slack: t('views.application.applicationAccess.slackSetting.title')
|
||||||
|
}[configType.value])
|
||||||
|
)
|
||||||
|
|
||||||
|
const infoTitle = computed(
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
wechat: t('views.applicationOverview.appInfo.header'),
|
||||||
|
dingtalk: t('views.applicationOverview.appInfo.header'),
|
||||||
|
wecom: t('views.applicationOverview.appInfo.header'),
|
||||||
|
feishu: t('views.applicationOverview.appInfo.header'),
|
||||||
|
slack: t('views.applicationOverview.appInfo.header')
|
||||||
|
}[configType.value])
|
||||||
|
)
|
||||||
|
|
||||||
|
const passwordVisible = reactive<Record<string, boolean>>(
|
||||||
|
Object.keys(configFields[configType.value]).reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
if (passwordFields.has(key)) {
|
||||||
|
acc[key] = false
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{} as Record<string, boolean>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const isPasswordField = (key: any) => passwordFields.has(key)
|
||||||
|
|
||||||
|
const closeDrawer = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
if (loading.value) return
|
||||||
|
|
||||||
|
formRef.value?.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
try {
|
||||||
|
applicationApi
|
||||||
|
.updatePlatformConfig(id, configType.value, form[configType.value], loading)
|
||||||
|
.then(() => {
|
||||||
|
MsgSuccess(t('common.saveSuccess'))
|
||||||
|
closeDrawer()
|
||||||
|
emit('refresh')
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
MsgError(t('views.application.tip.saveErrorMessage'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = async (id: string, type: PlatformType) => {
|
||||||
|
visible.value = true
|
||||||
|
configType.value = type
|
||||||
|
loading.value = true
|
||||||
|
dataLoaded.value = false
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
try {
|
||||||
|
const res = await applicationApi.getPlatformConfig(id, type)
|
||||||
|
if (res.data) {
|
||||||
|
form[configType.value] = res.data
|
||||||
|
}
|
||||||
|
dataLoaded.value = true
|
||||||
|
} catch {
|
||||||
|
MsgError(t('views.application.tip.loadingErrorMessage'))
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
form[configType.value].callback_url = `${window.location.origin}/api/${type}/${id}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.application.applicationForm.dialog.addDataset')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="600"
|
||||||
|
append-to-body
|
||||||
|
class="addDataset-dialog"
|
||||||
|
align-center
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<template #header="{ titleId, titleClass }">
|
||||||
|
<div class="flex-between mb-8">
|
||||||
|
<h4 :id="titleId" :class="titleClass">
|
||||||
|
{{ $t('views.application.applicationForm.dialog.addDataset') }}
|
||||||
|
</h4>
|
||||||
|
<div class="flex align-center mr-8">
|
||||||
|
<el-button link class="ml-16" @click="refresh">
|
||||||
|
<el-icon class="mr-4"><Refresh /></el-icon>{{ $t('common.refresh') }}
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-between">
|
||||||
|
<el-text type="info" class="color-secondary">
|
||||||
|
{{ $t('views.application.applicationForm.dialog.addDatasetPlaceholder') }}
|
||||||
|
</el-text>
|
||||||
|
<el-input
|
||||||
|
v-model="searchValue"
|
||||||
|
:placeholder="$t('common.search')"
|
||||||
|
prefix-icon="Search"
|
||||||
|
class="w-240"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-scrollbar>
|
||||||
|
<div class="max-height">
|
||||||
|
<el-row :gutter="12" v-loading="loading">
|
||||||
|
<el-col :span="12" v-for="(item, index) in filterData" :key="index" class="mb-16">
|
||||||
|
<CardCheckbox value-field="id" :data="item" v-model="checkList" @change="changeHandle">
|
||||||
|
<span class="ellipsis cursor" :title="item.name"> {{ item.name }}</span>
|
||||||
|
</CardCheckbox>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex">
|
||||||
|
<el-text type="info" class="color-secondary mr-8" v-if="checkList.length > 0">
|
||||||
|
{{ $t('views.application.applicationForm.dialog.selected') }} {{ checkList.length }}
|
||||||
|
{{ $t('views.application.applicationForm.dialog.countDataset') }}
|
||||||
|
</el-text>
|
||||||
|
<el-button link type="primary" v-if="checkList.length > 0" @click="clearCheck">
|
||||||
|
{{ $t('common.clear') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
<el-button @click.prevent="dialogVisible = false">
|
||||||
|
{{ $t('common.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submitHandle">
|
||||||
|
{{ $t('common.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array<any>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
loading: Boolean
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['addData', 'refresh'])
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const checkList = ref([])
|
||||||
|
const currentEmbedding = ref('')
|
||||||
|
const searchValue = ref('')
|
||||||
|
const searchDate = ref<any[]>([])
|
||||||
|
|
||||||
|
const filterData = computed(() => {
|
||||||
|
return currentEmbedding.value
|
||||||
|
? searchDate.value.filter((v) => v.embedding_mode_id === currentEmbedding.value)
|
||||||
|
: searchDate.value
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
checkList.value = []
|
||||||
|
currentEmbedding.value = ''
|
||||||
|
searchValue.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(searchValue, (val) => {
|
||||||
|
if (val) {
|
||||||
|
searchDate.value = props.data.filter((v) => v.name.includes(val))
|
||||||
|
} else {
|
||||||
|
searchDate.value = props.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function changeHandle() {
|
||||||
|
if (checkList.value.length > 0) {
|
||||||
|
currentEmbedding.value = props.data.filter(
|
||||||
|
(v) => v.id === checkList.value[0]
|
||||||
|
)[0].embedding_mode_id
|
||||||
|
} else if (checkList.value.length === 0) {
|
||||||
|
currentEmbedding.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function clearCheck() {
|
||||||
|
checkList.value = []
|
||||||
|
currentEmbedding.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = (checked: any) => {
|
||||||
|
searchDate.value = props.data
|
||||||
|
checkList.value = checked
|
||||||
|
if (checkList.value.length > 0) {
|
||||||
|
currentEmbedding.value = props.data.filter(
|
||||||
|
(v) => v.id === checkList.value[0]
|
||||||
|
)[0].embedding_mode_id
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
const submitHandle = () => {
|
||||||
|
emit('addData', checkList.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
emit('refresh')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.addDataset-dialog {
|
||||||
|
padding: 0;
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 24px 24px 8px 24px;
|
||||||
|
}
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 8px !important;
|
||||||
|
}
|
||||||
|
.el-dialog__footer {
|
||||||
|
padding: 8px 24px 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__headerbtn {
|
||||||
|
top: 9px;
|
||||||
|
}
|
||||||
|
.max-height {
|
||||||
|
max-height: calc(100vh - 260px);
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.application.copyApplication')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="650"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="applicationFormRef"
|
||||||
|
:model="applicationForm"
|
||||||
|
:rules="rules"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.appName.label')" prop="name">
|
||||||
|
<el-input
|
||||||
|
v-model="applicationForm.name"
|
||||||
|
maxlength="64"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.appName.placeholder')"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.appDescription.label')">
|
||||||
|
<el-input
|
||||||
|
v-model="applicationForm.desc"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.appDescription.placeholder')"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="256"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false" :loading="loading">
|
||||||
|
{{ $t('common.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submitValid(applicationFormRef)" :loading="loading">
|
||||||
|
{{ $t('common.copy') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, reactive } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import type { ApplicationFormType } from '@/api/type/application'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { MsgSuccess, MsgAlert } from '@/utils/message'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { ValidType, ValidCount } from '@/enums/common'
|
||||||
|
const router = useRouter()
|
||||||
|
const { common, user } = useStore()
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const defaultPrompt = t('views.application.applicationForm.form.prompt.defaultPrompt', {
|
||||||
|
data: '{data}',
|
||||||
|
question: '{question}'
|
||||||
|
})
|
||||||
|
const applicationFormRef = ref()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
// @ts-ignore
|
||||||
|
const applicationForm = ref<ApplicationFormType>({
|
||||||
|
name: '',
|
||||||
|
desc: '',
|
||||||
|
model_id: '',
|
||||||
|
dialogue_number: 0,
|
||||||
|
prologue: t('views.application.applicationForm.form.defaultPrologue'),
|
||||||
|
dataset_id_list: [],
|
||||||
|
dataset_setting: {
|
||||||
|
top_n: 3,
|
||||||
|
similarity: 0.6,
|
||||||
|
max_paragraph_char_number: 5000,
|
||||||
|
search_mode: 'embedding',
|
||||||
|
no_references_setting: {
|
||||||
|
status: 'ai_questioning',
|
||||||
|
value: '{question}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
model_setting: {
|
||||||
|
prompt: defaultPrompt
|
||||||
|
},
|
||||||
|
problem_optimization: false,
|
||||||
|
type: 'SIMPLE'
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive<FormRules<ApplicationFormType>>({
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationForm.form.appName.placeholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
applicationForm.value = {
|
||||||
|
name: '',
|
||||||
|
desc: '',
|
||||||
|
model_id: '',
|
||||||
|
dialogue_number: 0,
|
||||||
|
prologue: t('views.application.applicationForm.form.defaultPrologue'),
|
||||||
|
dataset_id_list: [],
|
||||||
|
dataset_setting: {
|
||||||
|
top_n: 3,
|
||||||
|
similarity: 0.6,
|
||||||
|
max_paragraph_char_number: 5000,
|
||||||
|
search_mode: 'embedding',
|
||||||
|
no_references_setting: {
|
||||||
|
status: 'ai_questioning',
|
||||||
|
value: '{question}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
model_setting: {
|
||||||
|
prompt: defaultPrompt
|
||||||
|
},
|
||||||
|
problem_optimization: false,
|
||||||
|
type: 'SIMPLE'
|
||||||
|
}
|
||||||
|
applicationFormRef.value?.clearValidate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (data: any) => {
|
||||||
|
const obj = cloneDeep(data)
|
||||||
|
delete obj['id']
|
||||||
|
obj['name'] = obj['name'] + ` ${t('views.application.applicationForm.title.copy')}`
|
||||||
|
applicationForm.value = obj
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitValid = (formEl: FormInstance | undefined) => {
|
||||||
|
if (user.isEnterprise()) {
|
||||||
|
submitHandle(formEl)
|
||||||
|
} else {
|
||||||
|
common
|
||||||
|
.asyncGetValid(ValidType.Application, ValidCount.Application, loading)
|
||||||
|
.then(async (res: any) => {
|
||||||
|
if (res?.data) {
|
||||||
|
submitHandle(formEl)
|
||||||
|
} else {
|
||||||
|
MsgAlert(t('common.tip'), t('views.application.tip.professionalMessage'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const submitHandle = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
applicationApi.postApplication(applicationForm.value, loading).then((res) => {
|
||||||
|
MsgSuccess(t('common.createSuccess'))
|
||||||
|
if (isWorkFlow(applicationForm.value.type)) {
|
||||||
|
router.push({ path: `/application/${res.data.id}/workflow` })
|
||||||
|
} else {
|
||||||
|
router.push({ path: `/application/${res.data.id}/${res.data.type}/setting` })
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.application.createApplication')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="650"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="applicationFormRef"
|
||||||
|
:model="applicationForm"
|
||||||
|
:rules="rules"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.appName.label')" prop="name">
|
||||||
|
<el-input
|
||||||
|
v-model="applicationForm.name"
|
||||||
|
maxlength="64"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.appName.placeholder')"
|
||||||
|
show-word-limit
|
||||||
|
@blur="applicationForm.name = applicationForm.name?.trim()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.appDescription.label')">
|
||||||
|
<el-input
|
||||||
|
v-model="applicationForm.desc"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.appDescription.placeholder')"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="256"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.appType.label')">
|
||||||
|
<el-radio-group v-model="applicationForm.type" class="card__radio">
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card shadow="never" :class="applicationForm.type === 'SIMPLE' ? 'active' : ''">
|
||||||
|
<el-radio value="SIMPLE" size="large">
|
||||||
|
<p class="mb-4">{{ $t('views.application.simple') }}</p>
|
||||||
|
<el-text type="info">{{
|
||||||
|
$t('views.application.applicationForm.form.appType.simplePlaceholder')
|
||||||
|
}}</el-text>
|
||||||
|
</el-radio>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card shadow="never" :class="isWorkFlow(applicationForm.type) ? 'active' : ''">
|
||||||
|
<el-radio value="WORK_FLOW" size="large">
|
||||||
|
<p class="mb-4">{{ $t('views.application.workflow') }}</p>
|
||||||
|
<el-text type="info">{{
|
||||||
|
$t('views.application.applicationForm.form.appType.workflowPlaceholder')
|
||||||
|
}}</el-text>
|
||||||
|
</el-radio>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.document.upload.template')"
|
||||||
|
v-if="applicationForm.type === 'WORK_FLOW'"
|
||||||
|
>
|
||||||
|
<div class="w-full">
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card
|
||||||
|
class="radio-card cursor"
|
||||||
|
shadow="never"
|
||||||
|
@click="selectedType('blank')"
|
||||||
|
:class="appTemplate === 'blank' ? 'active' : ''"
|
||||||
|
>
|
||||||
|
{{ $t('views.application.applicationForm.form.appTemplate.blankApp') }}
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card
|
||||||
|
class="radio-card cursor"
|
||||||
|
shadow="never"
|
||||||
|
:class="appTemplate === 'assistant' ? 'active' : ''"
|
||||||
|
@click="selectedType('assistant')"
|
||||||
|
>
|
||||||
|
{{ $t('views.application.applicationForm.form.appTemplate.assistantApp') }}
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false" :loading="loading">
|
||||||
|
{{ $t('common.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submitHandle(applicationFormRef)" :loading="loading">
|
||||||
|
{{ $t('common.create') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, reactive } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import type { ApplicationFormType } from '@/api/type/application'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { MsgSuccess, MsgAlert } from '@/utils/message'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
|
import { baseNodes } from '@/workflow/common/data'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const router = useRouter()
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const defaultPrompt = t('views.application.applicationForm.form.prompt.defaultPrompt', {
|
||||||
|
data: '{data}',
|
||||||
|
question: '{question}'
|
||||||
|
})
|
||||||
|
|
||||||
|
const optimizationPrompt =
|
||||||
|
t('views.application.applicationForm.dialog.defaultPrompt1', {
|
||||||
|
question: '{question}'
|
||||||
|
}) +
|
||||||
|
'<data></data>' +
|
||||||
|
t('views.application.applicationForm.dialog.defaultPrompt2')
|
||||||
|
|
||||||
|
const workflowDefault = ref<any>({
|
||||||
|
edges: [],
|
||||||
|
nodes: baseNodes
|
||||||
|
})
|
||||||
|
const appTemplate = ref('blank')
|
||||||
|
|
||||||
|
const applicationFormRef = ref()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
|
||||||
|
const applicationForm = ref<ApplicationFormType>({
|
||||||
|
name: '',
|
||||||
|
desc: '',
|
||||||
|
model_id: '',
|
||||||
|
dialogue_number: 1,
|
||||||
|
prologue: t('views.application.applicationForm.form.defaultPrologue'),
|
||||||
|
dataset_id_list: [],
|
||||||
|
dataset_setting: {
|
||||||
|
top_n: 3,
|
||||||
|
similarity: 0.6,
|
||||||
|
max_paragraph_char_number: 5000,
|
||||||
|
search_mode: 'embedding',
|
||||||
|
no_references_setting: {
|
||||||
|
status: 'ai_questioning',
|
||||||
|
value: '{question}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
model_setting: {
|
||||||
|
prompt: defaultPrompt,
|
||||||
|
system: t('views.application.applicationForm.form.roleSettings.placeholder'),
|
||||||
|
no_references_prompt: '{question}'
|
||||||
|
},
|
||||||
|
model_params_setting: {},
|
||||||
|
problem_optimization: false,
|
||||||
|
problem_optimization_prompt: optimizationPrompt,
|
||||||
|
stt_model_id: '',
|
||||||
|
tts_model_id: '',
|
||||||
|
stt_model_enable: false,
|
||||||
|
tts_model_enable: false,
|
||||||
|
tts_type: 'BROWSER',
|
||||||
|
type: 'SIMPLE'
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive<FormRules<ApplicationFormType>>({
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationForm.form.appName.placeholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
model_id: [
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
message: t('views.application.applicationForm.form.aiModel.placeholder'),
|
||||||
|
trigger: 'change'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
applicationForm.value = {
|
||||||
|
name: '',
|
||||||
|
desc: '',
|
||||||
|
model_id: '',
|
||||||
|
dialogue_number: 1,
|
||||||
|
prologue: t('views.application.applicationForm.form.defaultPrologue'),
|
||||||
|
dataset_id_list: [],
|
||||||
|
dataset_setting: {
|
||||||
|
top_n: 3,
|
||||||
|
similarity: 0.6,
|
||||||
|
max_paragraph_char_number: 5000,
|
||||||
|
search_mode: 'embedding',
|
||||||
|
no_references_setting: {
|
||||||
|
status: 'ai_questioning',
|
||||||
|
value: '{question}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
model_setting: {
|
||||||
|
prompt: defaultPrompt,
|
||||||
|
system: t('views.application.applicationForm.form.roleSettings.placeholder'),
|
||||||
|
no_references_prompt: '{question}'
|
||||||
|
},
|
||||||
|
model_params_setting: {},
|
||||||
|
problem_optimization: false,
|
||||||
|
problem_optimization_prompt: optimizationPrompt,
|
||||||
|
stt_model_id: '',
|
||||||
|
tts_model_id: '',
|
||||||
|
stt_model_enable: false,
|
||||||
|
tts_model_enable: false,
|
||||||
|
tts_type: 'BROWSER',
|
||||||
|
type: 'SIMPLE'
|
||||||
|
}
|
||||||
|
applicationFormRef.value?.clearValidate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitHandle = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (isWorkFlow(applicationForm.value.type) && appTemplate.value === 'blank') {
|
||||||
|
workflowDefault.value.nodes[0].properties.node_data.desc = applicationForm.value.desc
|
||||||
|
workflowDefault.value.nodes[0].properties.node_data.name = applicationForm.value.name
|
||||||
|
applicationForm.value['work_flow'] = workflowDefault.value
|
||||||
|
}
|
||||||
|
applicationApi.postApplication(applicationForm.value, loading).then((res) => {
|
||||||
|
MsgSuccess(t('common.createSuccess'))
|
||||||
|
emit('refresh')
|
||||||
|
if (isWorkFlow(applicationForm.value.type)) {
|
||||||
|
router.push({ path: `/application/${res.data.id}/workflow` })
|
||||||
|
} else {
|
||||||
|
router.push({ path: `/application/${res.data.id}/${res.data.type}/setting` })
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedType(type: string) {
|
||||||
|
appTemplate.value = type
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.radio-card {
|
||||||
|
line-height: 22px;
|
||||||
|
&.active {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
align-center
|
||||||
|
:title="$t('common.setting')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
style="width: 550px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
ref="paramFormRef"
|
||||||
|
:model="form"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item label="MCP" prop="mcp_enable">
|
||||||
|
<el-switch v-model="form.mcp_enable" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="form.mcp_enable"
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.mcpNode.configLabel')"
|
||||||
|
prop="mcp_servers"
|
||||||
|
:rules="[{ required: true, message: $t('common.required') }]"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="form.mcp_servers"
|
||||||
|
:rows="6"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="mcpServerJson"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="submit()" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const paramFormRef = ref()
|
||||||
|
|
||||||
|
const mcpServerJson = `{
|
||||||
|
"math": {
|
||||||
|
"url": "your_server",
|
||||||
|
"transport": "sse"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const form = ref<any>({
|
||||||
|
mcp_servers: '',
|
||||||
|
mcp_enable: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
form.value = {
|
||||||
|
mcp_servers: '',
|
||||||
|
mcp_enable: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (data: any) => {
|
||||||
|
form.value = { ...form.value, ...data }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
paramFormRef.value.validate().then((valid: any) => {
|
||||||
|
if (valid) {
|
||||||
|
emit('refresh', form.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,327 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
align-center
|
||||||
|
:title="$t('common.paramSetting')"
|
||||||
|
class="param-dialog"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
style="width: 550px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-scrollbar max-height="550">
|
||||||
|
<div class="p-16">
|
||||||
|
<el-form label-position="top" ref="paramFormRef" :model="form" v-loading="loading">
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.dialog.selectSearchMode')">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="form.dataset_setting.search_mode"
|
||||||
|
class="card__radio"
|
||||||
|
@change="changeHandle"
|
||||||
|
>
|
||||||
|
<el-card
|
||||||
|
shadow="never"
|
||||||
|
class="mb-16"
|
||||||
|
:class="form.search_mode === 'embedding' ? 'active' : ''"
|
||||||
|
>
|
||||||
|
<el-radio value="embedding" size="large">
|
||||||
|
<p class="mb-4">
|
||||||
|
{{ $t('views.application.applicationForm.dialog.vectorSearch') }}
|
||||||
|
</p>
|
||||||
|
<el-text type="info">{{
|
||||||
|
$t('views.application.applicationForm.dialog.vectorSearchTooltip')
|
||||||
|
}}</el-text>
|
||||||
|
</el-radio>
|
||||||
|
</el-card>
|
||||||
|
<el-card
|
||||||
|
shadow="never"
|
||||||
|
class="mb-16"
|
||||||
|
:class="form.dataset_setting.search_mode === 'keywords' ? 'active' : ''"
|
||||||
|
>
|
||||||
|
<el-radio value="keywords" size="large">
|
||||||
|
<p class="mb-4">
|
||||||
|
{{ $t('views.application.applicationForm.dialog.fullTextSearch') }}
|
||||||
|
</p>
|
||||||
|
<el-text type="info">{{
|
||||||
|
$t('views.application.applicationForm.dialog.fullTextSearchTooltip')
|
||||||
|
}}</el-text>
|
||||||
|
</el-radio>
|
||||||
|
</el-card>
|
||||||
|
<el-card
|
||||||
|
shadow="never"
|
||||||
|
:class="form.dataset_setting.search_mode === 'blend' ? 'active' : ''"
|
||||||
|
>
|
||||||
|
<el-radio value="blend" size="large">
|
||||||
|
<p class="mb-4">
|
||||||
|
{{ $t('views.application.applicationForm.dialog.hybridSearch') }}
|
||||||
|
</p>
|
||||||
|
<el-text type="info">{{
|
||||||
|
$t('views.application.applicationForm.dialog.hybridSearchTooltip')
|
||||||
|
}}</el-text>
|
||||||
|
</el-radio>
|
||||||
|
</el-card>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<span class="mr-4">{{
|
||||||
|
$t('views.application.applicationForm.dialog.similarityThreshold')
|
||||||
|
}}</span>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.application.applicationForm.dialog.similarityTooltip')"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.dataset_setting.similarity"
|
||||||
|
:min="0"
|
||||||
|
:max="form.dataset_setting.search_mode === 'blend' ? 2 : 1"
|
||||||
|
:precision="3"
|
||||||
|
:step="0.1"
|
||||||
|
:value-on-clear="0"
|
||||||
|
controls-position="right"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.dialog.topReferences')">
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.dataset_setting.top_n"
|
||||||
|
:min="1"
|
||||||
|
:max="10000"
|
||||||
|
:value-on-clear="1"
|
||||||
|
controls-position="right"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.dialog.maxCharacters')">
|
||||||
|
<el-slider
|
||||||
|
v-model="form.dataset_setting.max_paragraph_char_number"
|
||||||
|
show-input
|
||||||
|
:show-input-controls="false"
|
||||||
|
:min="500"
|
||||||
|
:max="100000"
|
||||||
|
class="custom-slider"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="!isWorkflowType"
|
||||||
|
:label="$t('views.application.applicationForm.dialog.noReferencesAction')"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
ref="noReferencesformRef"
|
||||||
|
:model="noReferencesform"
|
||||||
|
:rules="noReferencesRules"
|
||||||
|
:hide-required-asterisk="true"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="form.dataset_setting.no_references_setting.status"
|
||||||
|
class="radio-block-avatar"
|
||||||
|
>
|
||||||
|
<el-radio value="ai_questioning">
|
||||||
|
<p>
|
||||||
|
{{ $t('views.application.applicationForm.dialog.continueQuestioning') }}
|
||||||
|
</p>
|
||||||
|
</el-radio>
|
||||||
|
|
||||||
|
<el-radio value="designated_answer">
|
||||||
|
<p>{{ $t('views.application.applicationForm.dialog.provideAnswer') }}</p>
|
||||||
|
<el-form-item
|
||||||
|
v-if="form.dataset_setting.no_references_setting.status === 'designated_answer'"
|
||||||
|
prop="designated_answer"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="noReferencesform.designated_answer"
|
||||||
|
:rows="2"
|
||||||
|
type="textarea"
|
||||||
|
maxlength="2048"
|
||||||
|
:placeholder="defaultValue['designated_answer']"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item @click.prevent v-if="!isWorkflowType">
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<span class="mr-4">{{
|
||||||
|
$t('views.application.applicationForm.form.problemOptimization.label')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-switch size="small" v-model="form.problem_optimization"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="form.problem_optimization"
|
||||||
|
:label="$t('views.application.applicationForm.form.prompt.label')"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="form.problem_optimization_prompt"
|
||||||
|
:rows="6"
|
||||||
|
type="textarea"
|
||||||
|
maxlength="2048"
|
||||||
|
:placeholder="defaultPrompt"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer p-16">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="submit(noReferencesformRef)" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, reactive } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const paramFormRef = ref()
|
||||||
|
const noReferencesformRef = ref()
|
||||||
|
|
||||||
|
const defaultValue = {
|
||||||
|
ai_questioning: '{question}',
|
||||||
|
// @ts-ignore
|
||||||
|
designated_answer: t('views.application.applicationForm.dialog.designated_answer')
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPrompt =
|
||||||
|
t('views.application.applicationForm.dialog.defaultPrompt1', {
|
||||||
|
question: '{question}'
|
||||||
|
}) +
|
||||||
|
'<data></data>' +
|
||||||
|
t('views.application.applicationForm.dialog.defaultPrompt2')
|
||||||
|
|
||||||
|
const form = ref<any>({
|
||||||
|
dataset_setting: {
|
||||||
|
search_mode: 'embedding',
|
||||||
|
top_n: 3,
|
||||||
|
similarity: 0.6,
|
||||||
|
max_paragraph_char_number: 5000,
|
||||||
|
no_references_setting: {
|
||||||
|
status: 'ai_questioning',
|
||||||
|
value: '{question}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
problem_optimization: false,
|
||||||
|
problem_optimization_prompt: defaultPrompt
|
||||||
|
})
|
||||||
|
|
||||||
|
const noReferencesform = ref<any>({
|
||||||
|
ai_questioning: defaultValue['ai_questioning'],
|
||||||
|
designated_answer: defaultValue['designated_answer']
|
||||||
|
})
|
||||||
|
|
||||||
|
const noReferencesRules = reactive<FormRules<any>>({
|
||||||
|
ai_questioning: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationForm.form.aiModel.placeholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
designated_answer: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.application.applicationForm.form.prompt.requiredMessage'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const isWorkflowType = ref(false)
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
// form.value = {
|
||||||
|
// dataset_setting: {
|
||||||
|
// search_mode: 'embedding',
|
||||||
|
// top_n: 3,
|
||||||
|
// similarity: 0.6,
|
||||||
|
// max_paragraph_char_number: 5000,
|
||||||
|
// no_references_setting: {
|
||||||
|
// status: 'ai_questioning',
|
||||||
|
// value: '{question}'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// problem_optimization: false,
|
||||||
|
// problem_optimization_prompt: ''
|
||||||
|
// }
|
||||||
|
noReferencesform.value = {
|
||||||
|
ai_questioning: defaultValue['ai_questioning'],
|
||||||
|
designated_answer: defaultValue['designated_answer']
|
||||||
|
}
|
||||||
|
noReferencesformRef.value?.clearValidate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (data: any, type?: string) => {
|
||||||
|
isWorkflowType.value = isWorkFlow(type)
|
||||||
|
form.value = {
|
||||||
|
dataset_setting: { ...data.dataset_setting },
|
||||||
|
problem_optimization: data.problem_optimization,
|
||||||
|
problem_optimization_prompt: data.problem_optimization_prompt
|
||||||
|
}
|
||||||
|
if (!isWorkflowType.value) {
|
||||||
|
noReferencesform.value[form.value.dataset_setting.no_references_setting.status] =
|
||||||
|
form.value.dataset_setting.no_references_setting.value
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (isWorkflowType.value) {
|
||||||
|
delete form.value['no_references_setting']
|
||||||
|
emit('refresh', form.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
} else {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
form.value.dataset_setting.no_references_setting.value =
|
||||||
|
noReferencesform.value[form.value.dataset_setting.no_references_setting.status]
|
||||||
|
emit('refresh', form.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeHandle(val: string) {
|
||||||
|
if (val === 'keywords') {
|
||||||
|
form.value.dataset_setting.similarity = 0
|
||||||
|
} else {
|
||||||
|
form.value.dataset_setting.similarity = 0.6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
align-center
|
||||||
|
:title="$t('common.setting')"
|
||||||
|
class="param-dialog"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
style="width: 550px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-form label-position="top" ref="paramFormRef" :model="form" class="p-12-16">
|
||||||
|
<el-text type="info" class="color-secondary">{{
|
||||||
|
$t('views.application.applicationForm.form.reasoningContent.tooltip')
|
||||||
|
}}</el-text>
|
||||||
|
<el-row class="mt-16" :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.application.applicationForm.form.reasoningContent.start')"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="form.reasoning_content_start"
|
||||||
|
:rows="6"
|
||||||
|
maxlength="50"
|
||||||
|
placeholder="<think>"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.reasoningContent.end')">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="form.reasoning_content_end"
|
||||||
|
:rows="6"
|
||||||
|
maxlength="50"
|
||||||
|
placeholder="</think>"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer p-16">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">{{ $t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="submit()" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, reactive } from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const form = ref<any>({
|
||||||
|
reasoning_content_start: '<think>',
|
||||||
|
reasoning_content_end: '</think>'
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
form.value = {
|
||||||
|
reasoning_content_start: '<think>',
|
||||||
|
reasoning_content_end: '</think>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (data: any) => {
|
||||||
|
form.value = { ...form.value, ...data }
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
emit('refresh', form.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
align-center
|
||||||
|
:title="$t('common.paramSetting')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
style="width: 550px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<DynamicsForm
|
||||||
|
v-model="form_data"
|
||||||
|
:model="form_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
:render_data="model_form_field"
|
||||||
|
ref="dynamicsFormRef"
|
||||||
|
>
|
||||||
|
</DynamicsForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex-between">
|
||||||
|
<el-button @click="testPlay" :loading="playLoading">
|
||||||
|
<AppIcon iconName="app-video-play" class="mr-4"></AppIcon>
|
||||||
|
{{ $t('views.application.applicationForm.form.voicePlay.listeningTest') }}
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">
|
||||||
|
{{ $t('common.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="submit" :loading="loading">
|
||||||
|
{{ $t('common.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 先渲染,不然不能播放 -->
|
||||||
|
<audio ref="audioPlayer" controls hidden="hidden"></audio>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { FormField } from '@/components/dynamics-form/type'
|
||||||
|
import modelAPi from '@/api/model'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import DynamicsForm from '@/components/dynamics-form/index.vue'
|
||||||
|
import { keys } from 'lodash'
|
||||||
|
import { app } from '@/main'
|
||||||
|
import { MsgError } from '@/utils/message'
|
||||||
|
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = app.config.globalProperties.$route as any
|
||||||
|
|
||||||
|
const tts_model_id = ref('')
|
||||||
|
const model_form_field = ref<Array<FormField>>([])
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
|
||||||
|
const form_data = ref<any>({})
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const playLoading = ref(false)
|
||||||
|
const getApi = (model_id: string, application_id?: string) => {
|
||||||
|
return application_id
|
||||||
|
? applicationApi.getModelParamsForm(application_id, model_id, loading)
|
||||||
|
: modelAPi.getModelParamsForm(model_id, loading)
|
||||||
|
}
|
||||||
|
const open = (model_id: string, application_id?: string, model_setting_data?: any) => {
|
||||||
|
form_data.value = {}
|
||||||
|
tts_model_id.value = model_id
|
||||||
|
const api = getApi(model_id, application_id)
|
||||||
|
api.then((ok) => {
|
||||||
|
model_form_field.value = ok.data
|
||||||
|
const resp = ok.data
|
||||||
|
.map((item: any) => ({
|
||||||
|
[item.field]: item.show_default_value !== false ? item.default_value : undefined
|
||||||
|
}))
|
||||||
|
.reduce((x, y) => ({ ...x, ...y }), {})
|
||||||
|
// 删除不存在的字段
|
||||||
|
if (model_setting_data) {
|
||||||
|
Object.keys(model_setting_data).forEach((key) => {
|
||||||
|
if (!(key in resp)) {
|
||||||
|
delete model_setting_data[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
model_setting_data = { ...resp, ...model_setting_data }
|
||||||
|
// 渲染动态表单
|
||||||
|
dynamicsFormRef.value?.render(model_form_field.value, model_setting_data)
|
||||||
|
})
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset_default = (model_id: string, application_id?: string) => {
|
||||||
|
const api = getApi(model_id, application_id)
|
||||||
|
api.then((ok) => {
|
||||||
|
model_form_field.value = ok.data
|
||||||
|
const model_setting_data = ok.data
|
||||||
|
.map((item) => ({
|
||||||
|
[item.field]: item.show_default_value !== false ? item.default_value : undefined
|
||||||
|
}))
|
||||||
|
.reduce((x, y) => ({ ...x, ...y }), {})
|
||||||
|
|
||||||
|
emit('refresh', model_setting_data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
dynamicsFormRef.value?.validate().then(() => {
|
||||||
|
emit('refresh', form_data.value)
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const audioPlayer = ref<HTMLAudioElement | null>(null)
|
||||||
|
const testPlay = () => {
|
||||||
|
const data = {
|
||||||
|
...form_data.value,
|
||||||
|
tts_model_id: tts_model_id.value
|
||||||
|
}
|
||||||
|
applicationApi
|
||||||
|
.playDemoText(id as string, data, playLoading)
|
||||||
|
.then(async (res: any) => {
|
||||||
|
if (res.type === 'application/json') {
|
||||||
|
const text = await res.text()
|
||||||
|
MsgError(text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 创建 Blob 对象
|
||||||
|
const blob = new Blob([res], { type: 'audio/mp3' })
|
||||||
|
|
||||||
|
// 创建对象 URL
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
// 检查 audioPlayer 是否已经引用了 DOM 元素
|
||||||
|
if (audioPlayer.value instanceof HTMLAudioElement) {
|
||||||
|
audioPlayer.value.src = url
|
||||||
|
audioPlayer.value.play() // 自动播放音频
|
||||||
|
} else {
|
||||||
|
console.error('audioPlayer.value is not an instance of HTMLAudioElement')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('err: ', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, reset_default })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,444 @@
|
||||||
|
<template>
|
||||||
|
<div class="application-list-container p-24" style="padding-top: 16px">
|
||||||
|
<div class="flex-between mb-16">
|
||||||
|
<h4>{{ $t('views.application.title') }}</h4>
|
||||||
|
<div class="flex-between">
|
||||||
|
<el-select
|
||||||
|
v-model="selectUserId"
|
||||||
|
class="mr-12"
|
||||||
|
@change="searchHandle"
|
||||||
|
style="max-width: 240px; width: 150px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in userOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-model="searchValue"
|
||||||
|
@change="searchHandle"
|
||||||
|
:placeholder="$t('views.application.searchBar.placeholder')"
|
||||||
|
prefix-icon="Search"
|
||||||
|
class="w-240"
|
||||||
|
style="min-width: 240px"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading">
|
||||||
|
<InfiniteScroll
|
||||||
|
:size="applicationList.length"
|
||||||
|
:total="paginationConfig.total"
|
||||||
|
:page_size="paginationConfig.page_size"
|
||||||
|
v-model:current_page="paginationConfig.current_page"
|
||||||
|
@load="getList"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<el-row :gutter="15">
|
||||||
|
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-16">
|
||||||
|
<el-card shadow="hover" class="application-card-add" style="--el-card-padding: 8px">
|
||||||
|
<div class="card-add-button flex align-center cursor p-8" @click="openCreateDialog">
|
||||||
|
<AppIcon iconName="app-add-application" class="mr-8"></AppIcon>
|
||||||
|
{{ $t('views.application.createApplication') }}
|
||||||
|
</div>
|
||||||
|
<el-divider style="margin: 8px 0" />
|
||||||
|
<el-upload
|
||||||
|
ref="elUploadRef"
|
||||||
|
:file-list="[]"
|
||||||
|
action="#"
|
||||||
|
multiple
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
:limit="1"
|
||||||
|
:on-change="(file: any, fileList: any) => importApplication(file)"
|
||||||
|
class="card-add-button"
|
||||||
|
>
|
||||||
|
<div class="flex align-center cursor p-8">
|
||||||
|
<AppIcon iconName="app-import" class="mr-8"></AppIcon>
|
||||||
|
{{ $t('views.application.importApplication') }}
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col
|
||||||
|
:xs="24"
|
||||||
|
:sm="12"
|
||||||
|
:md="8"
|
||||||
|
:lg="6"
|
||||||
|
:xl="6"
|
||||||
|
v-for="(item, index) in applicationList"
|
||||||
|
:key="index"
|
||||||
|
class="mb-16"
|
||||||
|
>
|
||||||
|
<CardBox
|
||||||
|
:title="item.name"
|
||||||
|
:description="item.desc"
|
||||||
|
class="application-card cursor"
|
||||||
|
@click="router.push({ path: `/application/${item.id}/${item.type}/overview` })"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(item?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
class="mr-8"
|
||||||
|
>
|
||||||
|
<img :src="item?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="item?.name"
|
||||||
|
:name="item?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
class="mr-8"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #subTitle>
|
||||||
|
<el-text class="color-secondary" size="small">
|
||||||
|
<auto-tooltip :content="item.username">
|
||||||
|
{{ $t('common.creator') }}: {{ item.username }}
|
||||||
|
</auto-tooltip>
|
||||||
|
</el-text>
|
||||||
|
</template>
|
||||||
|
<div class="status-tag">
|
||||||
|
<el-tag type="warning" v-if="isWorkFlow(item.type)" style="height: 22px">
|
||||||
|
{{ $t('views.application.workflow') }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag class="blue-tag" v-else style="height: 22px">
|
||||||
|
{{ $t('views.application.simple') }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="footer-content">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.application.setting.demo')"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-button text @click.stop @click="getAccessToken(item.id)">
|
||||||
|
<AppIcon iconName="app-view"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.setting')" placement="top">
|
||||||
|
<el-button text @click.stop="settingApplication(item)">
|
||||||
|
<AppIcon iconName="Setting"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<span @click.stop>
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<el-button text @click.stop>
|
||||||
|
<el-icon><MoreFilled /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-if="is_show_copy_button(item)"
|
||||||
|
@click="copyApplication(item)"
|
||||||
|
>
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
{{ $t('common.copy') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click.stop="exportApplication(item)">
|
||||||
|
<AppIcon iconName="app-export"></AppIcon>
|
||||||
|
|
||||||
|
{{ $t('common.export') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item icon="Delete" @click.stop="deleteApplication(item)">{{
|
||||||
|
$t('common.delete')
|
||||||
|
}}</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</InfiniteScroll>
|
||||||
|
</div>
|
||||||
|
<CreateApplicationDialog ref="CreateApplicationDialogRef" />
|
||||||
|
<CopyApplicationDialog ref="CopyApplicationDialogRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, reactive } from 'vue'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import CreateApplicationDialog from './component/CreateApplicationDialog.vue'
|
||||||
|
import CopyApplicationDialog from './component/CopyApplicationDialog.vue'
|
||||||
|
import { MsgSuccess, MsgConfirm, MsgAlert, MsgError } from '@/utils/message'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
|
import { ValidType, ValidCount } from '@/enums/common'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
|
||||||
|
const elUploadRef = ref<any>()
|
||||||
|
const { application, user, common } = useStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const CopyApplicationDialogRef = ref()
|
||||||
|
const CreateApplicationDialogRef = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const applicationList = ref<any[]>([])
|
||||||
|
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 30,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
interface UserOption {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const userOptions = ref<UserOption[]>([])
|
||||||
|
|
||||||
|
const selectUserId = ref('all')
|
||||||
|
|
||||||
|
const searchValue = ref('')
|
||||||
|
|
||||||
|
const apiInputParams = ref([])
|
||||||
|
|
||||||
|
function copyApplication(row: any) {
|
||||||
|
application.asyncGetApplicationDetail(row.id, loading).then((res: any) => {
|
||||||
|
if (res?.data) {
|
||||||
|
CopyApplicationDialogRef.value.open({ ...res.data, model_id: res.data.model })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const is_show_copy_button = (row: any) => {
|
||||||
|
return user.userInfo ? user.userInfo.id == row.user_id : false
|
||||||
|
}
|
||||||
|
|
||||||
|
function settingApplication(row: any) {
|
||||||
|
if (isWorkFlow(row.type)) {
|
||||||
|
router.push({ path: `/application/${row.id}/workflow` })
|
||||||
|
} else {
|
||||||
|
router.push({ path: `/application/${row.id}/${row.type}/setting` })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportApplication = (application: any) => {
|
||||||
|
applicationApi.exportApplication(application.id, application.name, loading).catch((e) => {
|
||||||
|
if (e.response.status !== 403) {
|
||||||
|
e.response.data.text().then((res: string) => {
|
||||||
|
MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const importApplication = (file: any) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file.raw, file.name)
|
||||||
|
elUploadRef.value.clearFiles()
|
||||||
|
applicationApi
|
||||||
|
.importApplication(formData, loading)
|
||||||
|
.then(async (res: any) => {
|
||||||
|
if (res?.data) {
|
||||||
|
searchHandle()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (e.code === 400) {
|
||||||
|
MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {
|
||||||
|
cancelButtonText: t('common.confirm'),
|
||||||
|
confirmButtonText: t('common.professional')
|
||||||
|
}).then(() => {
|
||||||
|
window.open('https://maxkb.cn/pricing.html', '_blank')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCreateDialog() {
|
||||||
|
common
|
||||||
|
.asyncGetValid(ValidType.Application, ValidCount.Application, loading)
|
||||||
|
.then(async (res: any) => {
|
||||||
|
if (res?.data) {
|
||||||
|
CreateApplicationDialogRef.value.open()
|
||||||
|
} else if (res?.code === 400) {
|
||||||
|
MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {
|
||||||
|
cancelButtonText: t('common.confirm'),
|
||||||
|
confirmButtonText: t('common.professional')
|
||||||
|
}).then(() => {
|
||||||
|
window.open('https://maxkb.cn/pricing.html', '_blank')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchHandle() {
|
||||||
|
if (user.userInfo) {
|
||||||
|
localStorage.setItem(user.userInfo.id + 'application', selectUserId.value)
|
||||||
|
}
|
||||||
|
applicationList.value = []
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
paginationConfig.total = 0
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapToUrlParams(map: any[]) {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
|
map.forEach((item: any) => {
|
||||||
|
params.append(encodeURIComponent(item.name), encodeURIComponent(item.value))
|
||||||
|
})
|
||||||
|
|
||||||
|
return params.toString() // 返回 URL 查询字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAccessToken(id: string) {
|
||||||
|
applicationList.value
|
||||||
|
.filter((app) => app.id === id)[0]
|
||||||
|
?.work_flow?.nodes?.filter((v: any) => v.id === 'base-node')
|
||||||
|
.map((v: any) => {
|
||||||
|
apiInputParams.value = v.properties.api_input_field_list
|
||||||
|
? v.properties.api_input_field_list.map((v: any) => {
|
||||||
|
return {
|
||||||
|
name: v.variable,
|
||||||
|
value: v.default_value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: v.properties.input_field_list
|
||||||
|
? v.properties.input_field_list
|
||||||
|
.filter((v: any) => v.assignment_method === 'api_input')
|
||||||
|
.map((v: any) => {
|
||||||
|
return {
|
||||||
|
name: v.variable,
|
||||||
|
value: v.default_value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const apiParams = mapToUrlParams(apiInputParams.value)
|
||||||
|
? '?' + mapToUrlParams(apiInputParams.value)
|
||||||
|
: ''
|
||||||
|
application.asyncGetAccessToken(id, loading).then((res: any) => {
|
||||||
|
window.open(application.location + res?.data?.access_token + apiParams)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteApplication(row: any) {
|
||||||
|
MsgConfirm(
|
||||||
|
// @ts-ignore
|
||||||
|
`${t('views.application.delete.confirmTitle')}${row.name} ?`,
|
||||||
|
t('views.application.delete.confirmMessage'),
|
||||||
|
{
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
cancelButtonText: t('common.cancel'),
|
||||||
|
confirmButtonClass: 'danger'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
applicationApi.delApplication(row.id, loading).then(() => {
|
||||||
|
const index = applicationList.value.findIndex((v) => v.id === row.id)
|
||||||
|
applicationList.value.splice(index, 1)
|
||||||
|
MsgSuccess(t('common.deleteSuccess'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getList() {
|
||||||
|
const params = {
|
||||||
|
...(searchValue.value && { name: searchValue.value }),
|
||||||
|
...(selectUserId.value &&
|
||||||
|
selectUserId.value !== 'all' && { select_user_id: selectUserId.value })
|
||||||
|
}
|
||||||
|
applicationApi.getApplication(paginationConfig, params, loading).then((res) => {
|
||||||
|
res.data.records.forEach((item: any) => {
|
||||||
|
if (user.userInfo && item.user_id === user.userInfo.id) {
|
||||||
|
item.username = user.userInfo.username
|
||||||
|
} else {
|
||||||
|
item.username = userOptions.value.find((v) => v.value === item.user_id)?.label
|
||||||
|
}
|
||||||
|
})
|
||||||
|
applicationList.value = [...applicationList.value, ...res.data.records]
|
||||||
|
paginationConfig.total = res.data.total
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserList() {
|
||||||
|
applicationApi.getUserList('APPLICATION', loading).then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
userOptions.value = res.data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: item.username,
|
||||||
|
value: item.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (user.userInfo) {
|
||||||
|
const selectUserIdValue = localStorage.getItem(user.userInfo.id + 'application')
|
||||||
|
if (selectUserIdValue && userOptions.value.find((v) => v.value === selectUserIdValue)) {
|
||||||
|
selectUserId.value = selectUserIdValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getUserList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.application-card-add {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
min-height: var(--card-min-height);
|
||||||
|
border: 1px dashed var(--el-border-color);
|
||||||
|
background: var(--el-disabled-bg-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid var(--el-card-bg-color);
|
||||||
|
background-color: var(--el-card-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-add-button {
|
||||||
|
&:hover {
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--app-text-color-light-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-upload) {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.application-card {
|
||||||
|
.status-tag {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-custom-switch {
|
||||||
|
padding: 5px 11px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:modelValue="show"
|
||||||
|
modal-class="positioned-mask"
|
||||||
|
width="300"
|
||||||
|
:title="$t('chat.passwordValidator.title')"
|
||||||
|
custom-class="no-close-button"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:show-close="false"
|
||||||
|
top="25vh"
|
||||||
|
center
|
||||||
|
:modal="true"
|
||||||
|
>
|
||||||
|
<el-form ref="FormRef" :model="form" @submit.prevent="validator">
|
||||||
|
<el-form-item prop="value" :rules="rules.value">
|
||||||
|
<el-input show-password v-model="form.value" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-button class="w-full mt-8" type="primary" @click="validator" :loading="loading">
|
||||||
|
{{ $t('common.confirm') }}</el-button
|
||||||
|
>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const route = useRoute()
|
||||||
|
const FormRef = ref()
|
||||||
|
const {
|
||||||
|
params: { accessToken }
|
||||||
|
} = route as any
|
||||||
|
const { application } = useStore()
|
||||||
|
const props = defineProps<{ applicationProfile: any; modelValue: boolean }>()
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const show = computed(() => {
|
||||||
|
if (props.applicationProfile) {
|
||||||
|
if (props.modelValue) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return props.applicationProfile.authentication
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const auth = () => {
|
||||||
|
return application.asyncAppAuthentication(accessToken, loading, form.value).then(() => {
|
||||||
|
emit('update:modelValue', true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const validator_auth = (rule: any, value: string, callback: any) => {
|
||||||
|
if (value === '') {
|
||||||
|
callback(new Error(t('chat.passwordValidator.errorMessage1')))
|
||||||
|
} else {
|
||||||
|
auth().catch(() => {
|
||||||
|
callback(new Error(t('chat.passwordValidator.errorMessage2')))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const validator = () => {
|
||||||
|
FormRef.value.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
value: [{ required: true, validator: validator_auth, trigger: 'manual' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
type: 'password',
|
||||||
|
value: ''
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.positioned-mask {
|
||||||
|
top: var(--app-header-height);
|
||||||
|
height: calc(100% - var(--app-header-height));
|
||||||
|
.el-overlay-dialog {
|
||||||
|
top: var(--app-header-height);
|
||||||
|
height: calc(100% - var(--app-header-height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<div class="chat-pc__header" :style="customStyle">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-12 ml-24 flex">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(application_profile?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="application_profile?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="application_profile?.name"
|
||||||
|
:name="application_profile?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h4>{{ application_profile?.name }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<component
|
||||||
|
:is="auth_components[`/src/views/chat/auth/component/${auth_type}.vue`].default"
|
||||||
|
v-model="is_auth"
|
||||||
|
:applicationProfile="application_profile"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
|
||||||
|
const auth_components: any = import.meta.glob('@/views/chat/auth/component/*.vue', {
|
||||||
|
eager: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{ modelValue: boolean; application_profile: any; auth_type?: string; style?: any }>(),
|
||||||
|
{
|
||||||
|
auth_type: 'password',
|
||||||
|
style: {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const is_auth = computed({
|
||||||
|
get: () => {
|
||||||
|
return props.modelValue
|
||||||
|
},
|
||||||
|
set: (v) => {
|
||||||
|
emit('update:modelValue', v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const customStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
background: props.application_profile?.custom_theme?.theme_color,
|
||||||
|
color: props.application_profile?.custom_theme?.header_font_color,
|
||||||
|
border: 'none',
|
||||||
|
...props.style
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
<template>
|
||||||
|
<div class="chat layout-bg" v-loading="loading">
|
||||||
|
<div class="chat__header" :class="!isDefaultTheme ? 'custom-header' : ''">
|
||||||
|
<div class="chat-width flex align-center">
|
||||||
|
<div class="mr-12 ml-24 flex">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(applicationDetail?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="applicationDetail?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="applicationDetail?.name"
|
||||||
|
:name="applicationDetail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>{{ applicationDetail?.name }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chat__main chat-width">
|
||||||
|
<AiChat
|
||||||
|
v-model:applicationDetails="applicationDetail"
|
||||||
|
type="ai-chat"
|
||||||
|
:available="applicationAvailable"
|
||||||
|
:appId="applicationDetail?.id"
|
||||||
|
:record="recordList"
|
||||||
|
:chatId="currentChatId"
|
||||||
|
@refresh="refresh"
|
||||||
|
>
|
||||||
|
<template #operateBefore>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" link class="new-chat-button mb-8" @click="newChat">
|
||||||
|
<el-icon><Plus /></el-icon><span class="ml-4">{{ $t('chat.createChat') }}</span>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</AiChat>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
|
||||||
|
const { user } = useStore()
|
||||||
|
|
||||||
|
const isDefaultTheme = computed(() => {
|
||||||
|
return user.isDefaultTheme()
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const props = defineProps<{
|
||||||
|
application_profile: any
|
||||||
|
applicationAvailable: boolean
|
||||||
|
}>()
|
||||||
|
const applicationDetail = computed({
|
||||||
|
get: () => {
|
||||||
|
return props.application_profile
|
||||||
|
},
|
||||||
|
set: (v) => {}
|
||||||
|
})
|
||||||
|
const recordList = ref([])
|
||||||
|
const currentChatId = ref('')
|
||||||
|
|
||||||
|
function newChat() {
|
||||||
|
currentChatId.value = 'new'
|
||||||
|
recordList.value = []
|
||||||
|
}
|
||||||
|
function refresh(id: string) {
|
||||||
|
currentChatId.value = id
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.chat {
|
||||||
|
overflow: hidden;
|
||||||
|
&__header {
|
||||||
|
background: var(--app-header-bg-color);
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
height: var(--app-header-height);
|
||||||
|
line-height: var(--app-header-height);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
&__main {
|
||||||
|
padding-top: calc(var(--app-header-height) + 24px);
|
||||||
|
height: calc(100vh - var(--app-header-height) - 24px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-width {
|
||||||
|
// max-width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,404 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="chat-embed layout-bg"
|
||||||
|
:class="{ 'chat-embed--popup': isPopup }"
|
||||||
|
v-loading="loading"
|
||||||
|
:style="{
|
||||||
|
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
|
||||||
|
'--el-color-primary-light-9': hexToRgba(applicationDetail?.custom_theme?.theme_color, 0.1)
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="chat-embed__header" :style="customStyle">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-12 ml-24 flex">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(applicationDetail?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="applicationDetail?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="applicationDetail?.name"
|
||||||
|
:name="applicationDetail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{ applicationDetail?.name }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="chat-embed__main">
|
||||||
|
<AiChat
|
||||||
|
ref="AiChatRef"
|
||||||
|
v-model:applicationDetails="applicationDetail"
|
||||||
|
:available="applicationAvailable"
|
||||||
|
:appId="applicationDetail?.id"
|
||||||
|
:record="currentRecordList"
|
||||||
|
:chatId="currentChatId"
|
||||||
|
type="ai-chat"
|
||||||
|
@refresh="refresh"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
class="AiChat-embed"
|
||||||
|
>
|
||||||
|
<template #operateBefore>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" link class="new-chat-button mb-8" @click="newChat">
|
||||||
|
<el-icon><Plus /></el-icon><span class="ml-4">{{ $t('chat.createChat') }}</span>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</AiChat>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 历史记录弹出层 -->
|
||||||
|
<div
|
||||||
|
v-if="applicationDetail.show_history || !user.isEnterprise()"
|
||||||
|
@click.prevent.stop="show = !show"
|
||||||
|
class="chat-popover-button cursor color-secondary"
|
||||||
|
>
|
||||||
|
<AppIcon
|
||||||
|
iconName="app-history-outlined"
|
||||||
|
:style="{
|
||||||
|
color: applicationDetail?.custom_theme?.header_font_color
|
||||||
|
}"
|
||||||
|
></AppIcon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-collapse-transition>
|
||||||
|
<div v-show="show" class="chat-popover w-full" v-click-outside="clickoutside">
|
||||||
|
<div class="border-b p-16-24">
|
||||||
|
<span>{{ $t('chat.history') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-scrollbar max-height="300">
|
||||||
|
<div class="p-8">
|
||||||
|
<common-list
|
||||||
|
:style="{ '--el-color-primary': applicationDetail?.custom_theme?.theme_color }"
|
||||||
|
:data="chatLogData"
|
||||||
|
v-loading="left_loading"
|
||||||
|
:defaultActive="currentChatId"
|
||||||
|
@click="clickListHandle"
|
||||||
|
@mouseenter="mouseenter"
|
||||||
|
@mouseleave="mouseId = ''"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex-between">
|
||||||
|
<ReadWrite
|
||||||
|
@change="editName($event, row)"
|
||||||
|
:data="row.abstract"
|
||||||
|
trigger="manual"
|
||||||
|
:write="row.writeStatus"
|
||||||
|
@close="closeWrite(row)"
|
||||||
|
:maxlength="1024"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
@click.stop
|
||||||
|
v-if="mouseId === row.id && row.id !== 'new' && !row.writeStatus"
|
||||||
|
class="flex"
|
||||||
|
>
|
||||||
|
<el-button style="padding: 0" link @click.stop="openWrite(row)">
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-button style="padding: 0" link @click.stop="deleteLog(row)">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #empty>
|
||||||
|
<div class="text-center mt-24">
|
||||||
|
<el-text type="info">{{ $t('chat.noHistory') }}</el-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</common-list>
|
||||||
|
</div>
|
||||||
|
<div v-if="chatLogData.length" class="gradient-divider lighter mt-8">
|
||||||
|
<span>{{ $t('chat.only20history') }}</span>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-collapse-transition>
|
||||||
|
<div class="chat-popover-mask" v-show="show"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, reactive, nextTick, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import { hexToRgba } from '@/utils/theme'
|
||||||
|
import { MsgError } from '@/utils/message'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const { user, log } = useStore()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const isPopup = computed(() => {
|
||||||
|
return route.query.popup !== 'no'
|
||||||
|
})
|
||||||
|
const AiChatRef = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
const left_loading = ref(false)
|
||||||
|
const chatLogData = ref<any[]>([])
|
||||||
|
const show = ref(false)
|
||||||
|
const props = defineProps<{
|
||||||
|
application_profile: any
|
||||||
|
applicationAvailable: boolean
|
||||||
|
}>()
|
||||||
|
const applicationDetail = computed({
|
||||||
|
get: () => {
|
||||||
|
return props.application_profile
|
||||||
|
},
|
||||||
|
set: (v) => {}
|
||||||
|
})
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentRecordList = ref<any>([])
|
||||||
|
const currentChatId = ref('new') // 当前历史记录Id 默认为'new'
|
||||||
|
|
||||||
|
const mouseId = ref('')
|
||||||
|
|
||||||
|
const customStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
background: applicationDetail.value?.custom_theme?.theme_color,
|
||||||
|
color: applicationDetail.value?.custom_theme?.header_font_color
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function editName(val: string, item: any) {
|
||||||
|
if (val) {
|
||||||
|
const obj = {
|
||||||
|
abstract: val
|
||||||
|
}
|
||||||
|
log.asyncPutChatClientLog(applicationDetail.value.id, item.id, obj, loading).then(() => {
|
||||||
|
const find = chatLogData.value.find((row: any) => row.id === item.id)
|
||||||
|
if (find) {
|
||||||
|
find.abstract = val
|
||||||
|
}
|
||||||
|
item['writeStatus'] = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.nameMessage'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openWrite(item: any) {
|
||||||
|
item['writeStatus'] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeWrite(item: any) {
|
||||||
|
item['writeStatus'] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseenter(row: any) {
|
||||||
|
mouseId.value = row.id
|
||||||
|
}
|
||||||
|
function deleteLog(row: any) {
|
||||||
|
log.asyncDelChatClientLog(applicationDetail.value.id, row.id, left_loading).then(() => {
|
||||||
|
if (currentChatId.value === row.id) {
|
||||||
|
currentChatId.value = 'new'
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
paginationConfig.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
}
|
||||||
|
getChatLog(applicationDetail.value.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScroll(event: any) {
|
||||||
|
if (
|
||||||
|
currentChatId.value !== 'new' &&
|
||||||
|
event.scrollTop === 0 &&
|
||||||
|
paginationConfig.total > currentRecordList.value.length
|
||||||
|
) {
|
||||||
|
const history_height = event.dialogScrollbar.offsetHeight
|
||||||
|
paginationConfig.current_page += 1
|
||||||
|
getChatRecord().then(() => {
|
||||||
|
event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickoutside() {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function newChat() {
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
currentRecordList.value = []
|
||||||
|
currentChatId.value = 'new'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChatLog(id: string) {
|
||||||
|
const page = {
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
log.asyncGetChatLogClient(id, page, left_loading).then((res: any) => {
|
||||||
|
chatLogData.value = res.data.records
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
paginationConfig.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
currentChatId.value = chatLogData.value?.[0]?.id || 'new'
|
||||||
|
if (currentChatId.value !== 'new') {
|
||||||
|
getChatRecord()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChatRecord() {
|
||||||
|
return log
|
||||||
|
.asyncChatRecordLog(
|
||||||
|
applicationDetail.value.id,
|
||||||
|
currentChatId.value,
|
||||||
|
paginationConfig,
|
||||||
|
loading,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
paginationConfig.total = res.data.total
|
||||||
|
const list = res.data.records
|
||||||
|
list.map((v: any) => {
|
||||||
|
v['write_ed'] = true
|
||||||
|
v['record_id'] = v.id
|
||||||
|
})
|
||||||
|
currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) =>
|
||||||
|
a.create_time.localeCompare(b.create_time)
|
||||||
|
)
|
||||||
|
if (paginationConfig.current_page === 1) {
|
||||||
|
nextTick(() => {
|
||||||
|
// 将滚动条滚动到最下面
|
||||||
|
AiChatRef.value.setScrollBottom()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickListHandle = (item: any) => {
|
||||||
|
if (item.id !== currentChatId.value) {
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
currentRecordList.value = []
|
||||||
|
currentChatId.value = item.id
|
||||||
|
if (currentChatId.value !== 'new') {
|
||||||
|
getChatRecord()
|
||||||
|
}
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh(id: string) {
|
||||||
|
getChatLog(applicationDetail.value.id)
|
||||||
|
currentChatId.value = id
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*初始化历史对话记录
|
||||||
|
*/
|
||||||
|
const init = () => {
|
||||||
|
if (applicationDetail.value.show_history || !user.isEnterprise()) {
|
||||||
|
getChatLog(applicationDetail.value.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.chat-embed {
|
||||||
|
overflow: hidden;
|
||||||
|
&__header {
|
||||||
|
background: var(--app-header-bg-color);
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
height: var(--app-header-height);
|
||||||
|
line-height: var(--app-header-height);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
&__main {
|
||||||
|
padding-top: calc(var(--app-header-height) + 16px);
|
||||||
|
height: calc(100vh - var(--app-header-height) - 16px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.new-chat-button {
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
// 历史对话弹出层
|
||||||
|
.chat-popover {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--app-header-height);
|
||||||
|
background: #ffffff;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
z-index: 2009;
|
||||||
|
}
|
||||||
|
.chat-popover-button {
|
||||||
|
z-index: 2009;
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
&.chat-embed--popup {
|
||||||
|
.chat-popover-button {
|
||||||
|
right: 85px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.chat-popover-mask {
|
||||||
|
background-color: var(--el-overlay-color-lighter);
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
overflow: auto;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: var(--app-header-height);
|
||||||
|
z-index: 2008;
|
||||||
|
}
|
||||||
|
.gradient-divider {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--el-color-info);
|
||||||
|
::before {
|
||||||
|
content: '';
|
||||||
|
width: 17%;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
::after {
|
||||||
|
content: '';
|
||||||
|
width: 17%;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.AiChat-embed {
|
||||||
|
.ai-chat__operate {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-overlay) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<template>
|
||||||
|
<component
|
||||||
|
v-if="chat_show && init_data_end"
|
||||||
|
:applicationAvailable="applicationAvailable"
|
||||||
|
:is="currentTemplate"
|
||||||
|
:application_profile="application_profile"
|
||||||
|
:key="route.fullPath"
|
||||||
|
v-loading="loading"
|
||||||
|
/>
|
||||||
|
<Auth
|
||||||
|
v-else
|
||||||
|
:application_profile="application_profile"
|
||||||
|
:auth_type="application_profile.authentication_type"
|
||||||
|
v-model="is_auth"
|
||||||
|
:style="{
|
||||||
|
'--el-color-primary': application_profile?.custom_theme?.theme_color,
|
||||||
|
'--el-color-primary-light-9': hexToRgba(application_profile?.custom_theme?.theme_color, 0.1)
|
||||||
|
}"
|
||||||
|
></Auth>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onBeforeMount, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import Auth from '@/views/chat/auth/index.vue'
|
||||||
|
import { hexToRgba } from '@/utils/theme'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { getBrowserLang } from '@/locales/index'
|
||||||
|
const { locale } = useI18n({ useScope: 'global' })
|
||||||
|
const route = useRoute()
|
||||||
|
const { application, user } = useStore()
|
||||||
|
|
||||||
|
const components: any = import.meta.glob('@/views/chat/**/index.vue', {
|
||||||
|
eager: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
query: { mode },
|
||||||
|
params: { accessToken }
|
||||||
|
} = route as any
|
||||||
|
const is_auth = ref<boolean>(false)
|
||||||
|
const currentTemplate = computed(() => {
|
||||||
|
let modeName = ''
|
||||||
|
if (!mode || mode === 'pc') {
|
||||||
|
modeName = show_history.value || !user.isEnterprise() ? 'pc' : 'base'
|
||||||
|
} else {
|
||||||
|
modeName = mode
|
||||||
|
}
|
||||||
|
const name = `/src/views/chat/${modeName}/index.vue`
|
||||||
|
return components[name].default
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 是否显示对话
|
||||||
|
*/
|
||||||
|
const chat_show = computed(() => {
|
||||||
|
if (init_data_end.value) {
|
||||||
|
if (!applicationAvailable.value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (application_profile.value) {
|
||||||
|
if (application_profile.value.authentication && is_auth.value) {
|
||||||
|
return true
|
||||||
|
} else if (!application_profile.value.authentication) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const show_history = ref(false)
|
||||||
|
|
||||||
|
const application_profile = ref<any>({})
|
||||||
|
/**
|
||||||
|
|
||||||
|
* 初始化结束
|
||||||
|
*/
|
||||||
|
const init_data_end = ref<boolean>(false)
|
||||||
|
|
||||||
|
const applicationAvailable = ref<boolean>(true)
|
||||||
|
function getAppProfile() {
|
||||||
|
return application.asyncGetAppProfile(loading).then((res: any) => {
|
||||||
|
locale.value = res.data?.language || getBrowserLang()
|
||||||
|
show_history.value = res.data?.show_history
|
||||||
|
application_profile.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getAccessToken(token: string) {
|
||||||
|
return application.asyncAppAuthentication(token, loading).then(() => {
|
||||||
|
return getAppProfile()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onBeforeMount(() => {
|
||||||
|
user.changeUserType(2, accessToken)
|
||||||
|
Promise.all([user.asyncGetProfile(), getAccessToken(accessToken)])
|
||||||
|
.catch(() => {
|
||||||
|
applicationAvailable.value = false
|
||||||
|
})
|
||||||
|
.finally(() => (init_data_end.value = true))
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
|
|
@ -0,0 +1,401 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="chat-mobile layout-bg"
|
||||||
|
v-loading="loading"
|
||||||
|
:style="{
|
||||||
|
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
|
||||||
|
'--el-color-primary-light-9': hexToRgba(applicationDetail?.custom_theme?.theme_color, 0.1)
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="chat-embed__header" :style="customStyle">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-12 ml-24 flex">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(applicationDetail?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="applicationDetail?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="applicationDetail?.name"
|
||||||
|
:name="applicationDetail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{ applicationDetail?.name }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="chat-embed__main">
|
||||||
|
<AiChat
|
||||||
|
ref="AiChatRef"
|
||||||
|
v-model:applicationDetails="applicationDetail"
|
||||||
|
:available="applicationAvailable"
|
||||||
|
:appId="applicationDetail?.id"
|
||||||
|
:record="currentRecordList"
|
||||||
|
:chatId="currentChatId"
|
||||||
|
type="ai-chat"
|
||||||
|
@refresh="refresh"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
class="AiChat-embed"
|
||||||
|
>
|
||||||
|
<template #operateBefore>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" link class="new-chat-button mb-8" @click="newChat">
|
||||||
|
<el-icon><Plus /></el-icon><span class="ml-4">{{ $t('chat.createChat') }}</span>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</AiChat>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 历史记录弹出层 -->
|
||||||
|
<div
|
||||||
|
v-if="applicationDetail.show_history || !user.isEnterprise()"
|
||||||
|
@click.prevent.stop="show = !show"
|
||||||
|
class="chat-popover-button cursor color-secondary"
|
||||||
|
>
|
||||||
|
<AppIcon
|
||||||
|
iconName="app-history-outlined"
|
||||||
|
:style="{
|
||||||
|
color: applicationDetail?.custom_theme?.header_font_color
|
||||||
|
}"
|
||||||
|
></AppIcon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-collapse-transition>
|
||||||
|
<div v-show="show" class="chat-popover w-full" v-click-outside="clickoutside">
|
||||||
|
<div class="border-b p-16-24">
|
||||||
|
<span>{{ $t('chat.history') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-scrollbar max-height="300">
|
||||||
|
<div class="p-8">
|
||||||
|
<common-list
|
||||||
|
:style="{ '--el-color-primary': applicationDetail?.custom_theme?.theme_color }"
|
||||||
|
:data="chatLogData"
|
||||||
|
v-loading="left_loading"
|
||||||
|
:defaultActive="currentChatId"
|
||||||
|
@click="clickListHandle"
|
||||||
|
@mouseenter="mouseenter"
|
||||||
|
@mouseleave="mouseId = ''"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex-between">
|
||||||
|
<ReadWrite
|
||||||
|
@change="editName($event, row)"
|
||||||
|
:data="row.abstract"
|
||||||
|
trigger="manual"
|
||||||
|
:write="row.writeStatus"
|
||||||
|
@close="closeWrite(row)"
|
||||||
|
:maxlength="1024"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
@click.stop
|
||||||
|
v-if="mouseId === row.id && row.id !== 'new' && !row.writeStatus"
|
||||||
|
class="flex"
|
||||||
|
>
|
||||||
|
<el-button style="padding: 0" link @click.stop="openWrite(row)">
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-button style="padding: 0" link @click.stop="deleteLog(row)">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #empty>
|
||||||
|
<div class="text-center mt-24">
|
||||||
|
<el-text type="info">{{ $t('chat.noHistory') }}</el-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</common-list>
|
||||||
|
</div>
|
||||||
|
<div v-if="chatLogData.length" class="gradient-divider lighter mt-8">
|
||||||
|
<span>{{ $t('chat.only20history') }}</span>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-collapse-transition>
|
||||||
|
<div class="chat-popover-mask" v-show="show"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, reactive, nextTick, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import { hexToRgba } from '@/utils/theme'
|
||||||
|
import { MsgError } from '@/utils/message'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const { user, log } = useStore()
|
||||||
|
|
||||||
|
const AiChatRef = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
const left_loading = ref(false)
|
||||||
|
const chatLogData = ref<any[]>([])
|
||||||
|
const show = ref(false)
|
||||||
|
const props = defineProps<{
|
||||||
|
application_profile: any
|
||||||
|
applicationAvailable: boolean
|
||||||
|
}>()
|
||||||
|
const applicationDetail = computed({
|
||||||
|
get: () => {
|
||||||
|
return props.application_profile
|
||||||
|
},
|
||||||
|
set: (v) => {}
|
||||||
|
})
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentRecordList = ref<any>([])
|
||||||
|
const currentChatId = ref('new') // 当前历史记录Id 默认为'new'
|
||||||
|
|
||||||
|
const mouseId = ref('')
|
||||||
|
|
||||||
|
const customStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
background: applicationDetail.value?.custom_theme?.theme_color,
|
||||||
|
color: applicationDetail.value?.custom_theme?.header_font_color
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function editName(val: string, item: any) {
|
||||||
|
if (val) {
|
||||||
|
const obj = {
|
||||||
|
abstract: val
|
||||||
|
}
|
||||||
|
|
||||||
|
log.asyncPutChatClientLog(applicationDetail.value.id, item.id, obj, loading).then(() => {
|
||||||
|
const find = chatLogData.value.find((row: any) => row.id === item.id)
|
||||||
|
if (find) {
|
||||||
|
find.abstract = val
|
||||||
|
}
|
||||||
|
item['writeStatus'] = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.nameMessage'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openWrite(item: any) {
|
||||||
|
item['writeStatus'] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeWrite(item: any) {
|
||||||
|
item['writeStatus'] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseenter(row: any) {
|
||||||
|
mouseId.value = row.id
|
||||||
|
}
|
||||||
|
function deleteLog(row: any) {
|
||||||
|
log.asyncDelChatClientLog(applicationDetail.value.id, row.id, left_loading).then(() => {
|
||||||
|
if (currentChatId.value === row.id) {
|
||||||
|
currentChatId.value = 'new'
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
paginationConfig.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
}
|
||||||
|
getChatLog(applicationDetail.value.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScroll(event: any) {
|
||||||
|
if (
|
||||||
|
currentChatId.value !== 'new' &&
|
||||||
|
event.scrollTop === 0 &&
|
||||||
|
paginationConfig.total > currentRecordList.value.length
|
||||||
|
) {
|
||||||
|
const history_height = event.dialogScrollbar.offsetHeight
|
||||||
|
paginationConfig.current_page += 1
|
||||||
|
getChatRecord().then(() => {
|
||||||
|
event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickoutside() {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function newChat() {
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
currentRecordList.value = []
|
||||||
|
currentChatId.value = 'new'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChatLog(id: string) {
|
||||||
|
const page = {
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
log.asyncGetChatLogClient(id, page, left_loading).then((res: any) => {
|
||||||
|
chatLogData.value = res.data.records
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
paginationConfig.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
currentChatId.value = chatLogData.value?.[0]?.id || 'new'
|
||||||
|
if (currentChatId.value !== 'new') {
|
||||||
|
getChatRecord()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChatRecord() {
|
||||||
|
return log
|
||||||
|
.asyncChatRecordLog(
|
||||||
|
applicationDetail.value.id,
|
||||||
|
currentChatId.value,
|
||||||
|
paginationConfig,
|
||||||
|
loading,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
paginationConfig.total = res.data.total
|
||||||
|
const list = res.data.records
|
||||||
|
list.map((v: any) => {
|
||||||
|
v['write_ed'] = true
|
||||||
|
v['record_id'] = v.id
|
||||||
|
})
|
||||||
|
currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) =>
|
||||||
|
a.create_time.localeCompare(b.create_time)
|
||||||
|
)
|
||||||
|
if (paginationConfig.current_page === 1) {
|
||||||
|
nextTick(() => {
|
||||||
|
// 将滚动条滚动到最下面
|
||||||
|
AiChatRef.value.setScrollBottom()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickListHandle = (item: any) => {
|
||||||
|
if (item.id !== currentChatId.value) {
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
currentRecordList.value = []
|
||||||
|
currentChatId.value = item.id
|
||||||
|
if (currentChatId.value !== 'new') {
|
||||||
|
getChatRecord()
|
||||||
|
}
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh(id: string) {
|
||||||
|
getChatLog(applicationDetail.value.id)
|
||||||
|
currentChatId.value = id
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*初始化历史对话记录
|
||||||
|
*/
|
||||||
|
const init = () => {
|
||||||
|
if (applicationDetail.value.show_history || !user.isEnterprise()) {
|
||||||
|
getChatLog(applicationDetail.value.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.chat-mobile {
|
||||||
|
overflow: hidden;
|
||||||
|
&__header {
|
||||||
|
background: var(--app-header-bg-color);
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
height: var(--app-header-height);
|
||||||
|
line-height: var(--app-header-height);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
&__main {
|
||||||
|
padding-top: calc(var(--app-header-height) + 16px);
|
||||||
|
height: calc(100vh - var(--app-header-height) - 16px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.new-chat-button {
|
||||||
|
z-index: 11;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
// 历史对话弹出层
|
||||||
|
.chat-popover {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--app-header-height);
|
||||||
|
background: #ffffff;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
z-index: 2009;
|
||||||
|
}
|
||||||
|
.chat-popover-button {
|
||||||
|
z-index: 2009;
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
// &.chat-embed--popup {
|
||||||
|
// .chat-popover-button {
|
||||||
|
// right: 85px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
.chat-popover-mask {
|
||||||
|
background-color: var(--el-overlay-color-lighter);
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
overflow: auto;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: var(--app-header-height);
|
||||||
|
z-index: 2008;
|
||||||
|
}
|
||||||
|
.gradient-divider {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--el-color-info);
|
||||||
|
::before {
|
||||||
|
content: '';
|
||||||
|
width: 17%;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
::after {
|
||||||
|
content: '';
|
||||||
|
width: 17%;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// .AiChat-embed {
|
||||||
|
// .ai-chat__operate {
|
||||||
|
// padding-top: 12px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-overlay) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
class="responsive-dialog"
|
||||||
|
:title="$t('chat.editTitle')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
ref="fieldFormRef"
|
||||||
|
:model="form"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
prop="abstract"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: $t('common.inputPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="form.abstract"
|
||||||
|
maxlength="1024"
|
||||||
|
show-word-limit
|
||||||
|
type="textarea"
|
||||||
|
@blur="form.abstract = form.abstract.trim()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
const { log } = useStore()
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const fieldFormRef = ref()
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const applicationId = ref<string>('')
|
||||||
|
const chatId = ref<string>('')
|
||||||
|
|
||||||
|
const form = ref<any>({
|
||||||
|
abstract: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
|
||||||
|
const open = (row: any, id: string) => {
|
||||||
|
applicationId.value = id
|
||||||
|
chatId.value = row.id
|
||||||
|
form.value.abstract = row.abstract
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
log.asyncPutChatClientLog(applicationId.value, chatId.value, form.value, loading).then(() => {
|
||||||
|
emit('refresh', chatId.value, form.value.abstract)
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, close })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,527 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="chat-pc layout-bg"
|
||||||
|
:class="classObj"
|
||||||
|
v-loading="loading"
|
||||||
|
:style="{
|
||||||
|
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
|
||||||
|
'--el-color-primary-light-9': hexToRgba(applicationDetail?.custom_theme?.theme_color, 0.1)
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="chat-pc__header" :style="customStyle">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-12 ml-24 flex">
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(applicationDetail?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="applicationDetail?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="applicationDetail?.name"
|
||||||
|
:name="applicationDetail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h4>{{ applicationDetail?.name }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="chat-pc__left border-r">
|
||||||
|
<div class="p-24 pb-0">
|
||||||
|
<el-button class="add-button w-full primary" @click="newChat">
|
||||||
|
<el-icon>
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
<span class="ml-4">{{ $t('chat.createChat') }}</span>
|
||||||
|
</el-button>
|
||||||
|
<p class="mt-20 mb-8">{{ $t('chat.history') }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="left-height pt-0">
|
||||||
|
<el-scrollbar>
|
||||||
|
<div class="p-8 pt-0">
|
||||||
|
<common-list
|
||||||
|
:style="{
|
||||||
|
'--el-color-primary': applicationDetail?.custom_theme?.theme_color,
|
||||||
|
'--el-color-primary-light-9': hexToRgba(
|
||||||
|
applicationDetail?.custom_theme?.theme_color,
|
||||||
|
0.1
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
:data="chatLogData"
|
||||||
|
class="mt-8"
|
||||||
|
v-loading="left_loading"
|
||||||
|
:defaultActive="currentChatId"
|
||||||
|
@click="clickListHandle"
|
||||||
|
@mouseenter="mouseenter"
|
||||||
|
@mouseleave="mouseId = ''"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex-between">
|
||||||
|
<auto-tooltip :content="row.abstract">
|
||||||
|
{{ row.abstract }}
|
||||||
|
</auto-tooltip>
|
||||||
|
<div @click.stop v-show="mouseId === row.id && row.id !== 'new'">
|
||||||
|
<el-dropdown trigger="click" :teleported="false">
|
||||||
|
<el-icon class="rotate-90 mt-4"><MoreFilled /></el-icon>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click.stop="editLogTitle(row)">
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
{{ $t('common.edit') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click.stop="deleteLog(row)">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
{{ $t('common.delete') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #empty>
|
||||||
|
<div class="text-center">
|
||||||
|
<el-text type="info">{{ $t('chat.noHistory') }}</el-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</common-list>
|
||||||
|
</div>
|
||||||
|
<div v-if="chatLogData?.length" class="gradient-divider lighter mt-8">
|
||||||
|
<span>{{ $t('chat.only20history') }}</span>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chat-pc__right">
|
||||||
|
<div class="right-header border-b mb-24 p-16-24 flex-between">
|
||||||
|
<h4 class="ellipsis-1" style="width: 66%">
|
||||||
|
{{ currentChatName }}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<span class="flex align-center" v-if="currentRecordList.length">
|
||||||
|
<AppIcon
|
||||||
|
v-if="paginationConfig.total"
|
||||||
|
iconName="app-chat-record"
|
||||||
|
class="info mr-8"
|
||||||
|
style="font-size: 16px"
|
||||||
|
></AppIcon>
|
||||||
|
<span v-if="paginationConfig.total" class="lighter">
|
||||||
|
{{ paginationConfig.total }} {{ $t('chat.question_count') }}
|
||||||
|
</span>
|
||||||
|
<el-dropdown class="ml-8">
|
||||||
|
<AppIcon
|
||||||
|
iconName="app-export"
|
||||||
|
class="cursor"
|
||||||
|
:title="$t('chat.exportRecords')"
|
||||||
|
></AppIcon>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click="exportMarkdown"
|
||||||
|
>{{ $t('common.export') }} Markdown</el-dropdown-item
|
||||||
|
>
|
||||||
|
<el-dropdown-item @click="exportHTML"
|
||||||
|
>{{ $t('common.export') }} HTML</el-dropdown-item
|
||||||
|
>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="right-height chat-width">
|
||||||
|
<AiChat
|
||||||
|
ref="AiChatRef"
|
||||||
|
v-model:applicationDetails="applicationDetail"
|
||||||
|
:available="applicationAvailable"
|
||||||
|
type="ai-chat"
|
||||||
|
:appId="applicationDetail?.id"
|
||||||
|
:record="currentRecordList"
|
||||||
|
:chatId="currentChatId"
|
||||||
|
@refresh="refresh"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
>
|
||||||
|
</AiChat>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="collapse">
|
||||||
|
<el-button @click="isCollapse = !isCollapse">
|
||||||
|
<el-icon> <component :is="isCollapse ? 'Fold' : 'Expand'" /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<EditTitleDialog ref="EditTitleDialogRef" @refresh="refreshFieldTitle" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, nextTick, computed } from 'vue'
|
||||||
|
import { marked } from 'marked'
|
||||||
|
import { saveAs } from 'file-saver'
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import useResize from '@/layout/hooks/useResize'
|
||||||
|
import { hexToRgba } from '@/utils/theme'
|
||||||
|
import EditTitleDialog from './EditTitleDialog.vue'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
useResize()
|
||||||
|
|
||||||
|
const { user, log, common } = useStore()
|
||||||
|
|
||||||
|
const EditTitleDialogRef = ref()
|
||||||
|
|
||||||
|
const isCollapse = ref(false)
|
||||||
|
|
||||||
|
const customStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
background: applicationDetail.value?.custom_theme?.theme_color,
|
||||||
|
color: applicationDetail.value?.custom_theme?.header_font_color
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const classObj = computed(() => {
|
||||||
|
return {
|
||||||
|
mobile: common.isMobile(),
|
||||||
|
hideLeft: !isCollapse.value,
|
||||||
|
openLeft: isCollapse.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const newObj = {
|
||||||
|
id: 'new',
|
||||||
|
abstract: t('chat.createChat')
|
||||||
|
}
|
||||||
|
const props = defineProps<{
|
||||||
|
application_profile: any
|
||||||
|
applicationAvailable: boolean
|
||||||
|
}>()
|
||||||
|
const AiChatRef = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
const left_loading = ref(false)
|
||||||
|
|
||||||
|
const applicationDetail = computed({
|
||||||
|
get: () => {
|
||||||
|
return props.application_profile
|
||||||
|
},
|
||||||
|
set: (v) => {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const chatLogData = ref<any[]>([])
|
||||||
|
|
||||||
|
const paginationConfig = ref({
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentRecordList = ref<any>([])
|
||||||
|
const currentChatId = ref('new') // 当前历史记录Id 默认为'new'
|
||||||
|
const currentChatName = ref(t('chat.createChat'))
|
||||||
|
const mouseId = ref('')
|
||||||
|
|
||||||
|
function mouseenter(row: any) {
|
||||||
|
mouseId.value = row.id
|
||||||
|
}
|
||||||
|
|
||||||
|
function editLogTitle(row: any) {
|
||||||
|
EditTitleDialogRef.value.open(row, applicationDetail.value.id)
|
||||||
|
}
|
||||||
|
function refreshFieldTitle(chatId: string, abstract: string) {
|
||||||
|
const find = chatLogData.value.find((item: any) => item.id == chatId)
|
||||||
|
if (find) {
|
||||||
|
find.abstract = abstract
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function deleteLog(row: any) {
|
||||||
|
log.asyncDelChatClientLog(applicationDetail.value.id, row.id, left_loading).then(() => {
|
||||||
|
if (currentChatId.value === row.id) {
|
||||||
|
currentChatId.value = 'new'
|
||||||
|
currentChatName.value = t('chat.createChat')
|
||||||
|
paginationConfig.value.current_page = 1
|
||||||
|
paginationConfig.value.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
}
|
||||||
|
getChatLog(applicationDetail.value.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScroll(event: any) {
|
||||||
|
if (
|
||||||
|
currentChatId.value !== 'new' &&
|
||||||
|
event.scrollTop === 0 &&
|
||||||
|
paginationConfig.value.total > currentRecordList.value.length
|
||||||
|
) {
|
||||||
|
const history_height = event.dialogScrollbar.offsetHeight
|
||||||
|
paginationConfig.value.current_page += 1
|
||||||
|
getChatRecord().then(() => {
|
||||||
|
event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function newChat() {
|
||||||
|
if (!chatLogData.value.some((v) => v.id === 'new')) {
|
||||||
|
paginationConfig.value.current_page = 1
|
||||||
|
paginationConfig.value.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
chatLogData.value.unshift(newObj)
|
||||||
|
} else {
|
||||||
|
paginationConfig.value.current_page = 1
|
||||||
|
paginationConfig.value.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
}
|
||||||
|
currentChatId.value = 'new'
|
||||||
|
currentChatName.value = t('chat.createChat')
|
||||||
|
if (common.isMobile()) {
|
||||||
|
isCollapse.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChatLog(id: string, refresh?: boolean) {
|
||||||
|
const page = {
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
log.asyncGetChatLogClient(id, page, left_loading).then((res: any) => {
|
||||||
|
chatLogData.value = res.data.records
|
||||||
|
if (refresh) {
|
||||||
|
currentChatName.value = chatLogData.value?.[0]?.abstract
|
||||||
|
} else {
|
||||||
|
paginationConfig.value.current_page = 1
|
||||||
|
paginationConfig.value.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
currentChatId.value = chatLogData.value?.[0]?.id || 'new'
|
||||||
|
currentChatName.value = chatLogData.value?.[0]?.abstract || t('chat.createChat')
|
||||||
|
if (currentChatId.value !== 'new') {
|
||||||
|
getChatRecord()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChatRecord() {
|
||||||
|
return log
|
||||||
|
.asyncChatRecordLog(
|
||||||
|
applicationDetail.value.id,
|
||||||
|
currentChatId.value,
|
||||||
|
paginationConfig.value,
|
||||||
|
loading,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
paginationConfig.value.total = res.data.total
|
||||||
|
const list = res.data.records
|
||||||
|
list.map((v: any) => {
|
||||||
|
v['write_ed'] = true
|
||||||
|
v['record_id'] = v.id
|
||||||
|
})
|
||||||
|
currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) =>
|
||||||
|
a.create_time.localeCompare(b.create_time)
|
||||||
|
)
|
||||||
|
if (paginationConfig.value.current_page === 1) {
|
||||||
|
nextTick(() => {
|
||||||
|
// 将滚动条滚动到最下面
|
||||||
|
AiChatRef.value.setScrollBottom()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickListHandle = (item: any) => {
|
||||||
|
if (item.id !== currentChatId.value) {
|
||||||
|
paginationConfig.value.current_page = 1
|
||||||
|
paginationConfig.value.total = 0
|
||||||
|
currentRecordList.value = []
|
||||||
|
currentChatId.value = item.id
|
||||||
|
currentChatName.value = item.abstract
|
||||||
|
if (currentChatId.value !== 'new') {
|
||||||
|
getChatRecord()
|
||||||
|
|
||||||
|
// 切换对话后,取消暂停的浏览器播放
|
||||||
|
if (window.speechSynthesis.paused && window.speechSynthesis.speaking) {
|
||||||
|
window.speechSynthesis.resume()
|
||||||
|
nextTick(() => {
|
||||||
|
window.speechSynthesis.cancel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (common.isMobile()) {
|
||||||
|
isCollapse.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh(id: string) {
|
||||||
|
getChatLog(applicationDetail.value.id, true)
|
||||||
|
currentChatId.value = id
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportMarkdown(): Promise<void> {
|
||||||
|
const suggestedName: string = `${currentChatId.value}.md`
|
||||||
|
const markdownContent: string = currentRecordList.value
|
||||||
|
.map((record: any) => `# ${record.problem_text}\n\n${record.answer_text}\n\n`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
const blob: Blob = new Blob([markdownContent], { type: 'text/markdown;charset=utf-8' })
|
||||||
|
saveAs(blob, suggestedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportHTML(): Promise<void> {
|
||||||
|
const suggestedName: string = `${currentChatId.value}.html`
|
||||||
|
const markdownContent: string = currentRecordList.value
|
||||||
|
.map((record: any) => `# ${record.problem_text}\n\n${record.answer_text}\n\n`)
|
||||||
|
.join('\n')
|
||||||
|
const htmlContent: any = marked(markdownContent)
|
||||||
|
|
||||||
|
const blob: Blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })
|
||||||
|
saveAs(blob, suggestedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*初始化历史对话记录
|
||||||
|
*/
|
||||||
|
const init = () => {
|
||||||
|
if (
|
||||||
|
(applicationDetail.value.show_history || !user.isEnterprise()) &&
|
||||||
|
props.applicationAvailable
|
||||||
|
) {
|
||||||
|
getChatLog(applicationDetail.value.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.chat-pc {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
background: var(--app-header-bg-color);
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
height: var(--app-header-height);
|
||||||
|
line-height: var(--app-header-height);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
padding-top: calc(var(--app-header-height) - 8px);
|
||||||
|
background: #ffffff;
|
||||||
|
width: 280px;
|
||||||
|
|
||||||
|
.add-button {
|
||||||
|
border: 1px solid var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-height {
|
||||||
|
height: calc(100vh - var(--app-header-height) - 135px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__right {
|
||||||
|
width: calc(100% - 280px);
|
||||||
|
padding-top: calc(var(--app-header-height));
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.right-header {
|
||||||
|
background: #ffffff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-height {
|
||||||
|
height: calc(100vh - var(--app-header-height) * 2 - 24px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-divider {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--el-color-info);
|
||||||
|
|
||||||
|
::before {
|
||||||
|
content: '';
|
||||||
|
width: 17%;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
::after {
|
||||||
|
content: '';
|
||||||
|
width: 17%;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 适配移动端
|
||||||
|
.mobile {
|
||||||
|
.chat-pc {
|
||||||
|
&__right {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
&__left {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.collapse {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 90px;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
&.openLeft {
|
||||||
|
.chat-pc {
|
||||||
|
&__left {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 99;
|
||||||
|
height: calc(100vh - var(--app-header-height) + 6px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.collapse {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 90px;
|
||||||
|
right: 0;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-width {
|
||||||
|
max-width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 1000px) {
|
||||||
|
.chat-width {
|
||||||
|
max-width: 100% !important;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
<div class="flex-between">
|
<div class="flex-between">
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
<AppAvatar class="mr-8 avatar-purple" shape="square" :size="32">
|
<AppAvatar class="mr-8 avatar-purple" shape="square" :size="32">
|
||||||
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||||
</AppAvatar>
|
</AppAvatar>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -106,7 +106,7 @@
|
||||||
<!-- <div class="flex-between">-->
|
<!-- <div class="flex-between">-->
|
||||||
<!-- <div class="flex align-center">-->
|
<!-- <div class="flex align-center">-->
|
||||||
<!-- <AppAvatar class="mr-8" :size="32">-->
|
<!-- <AppAvatar class="mr-8" :size="32">-->
|
||||||
<!-- <img src="@/assets/icon_web.svg" style="width: 100%" alt="" />-->
|
<!-- <img src="@/assets/knowledge/icon_web.svg" style="width: 100%" alt="" />-->
|
||||||
<!-- </AppAvatar>-->
|
<!-- </AppAvatar>-->
|
||||||
<!-- <div>-->
|
<!-- <div>-->
|
||||||
<!-- <p>-->
|
<!-- <p>-->
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
shape="square"
|
shape="square"
|
||||||
:size="24"
|
:size="24"
|
||||||
>
|
>
|
||||||
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||||
</AppAvatar>
|
</AppAvatar>
|
||||||
<AppAvatar
|
<AppAvatar
|
||||||
v-else-if="!item.dataset_id && item.type === '2'"
|
v-else-if="!item.dataset_id && item.type === '2'"
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
:size="24"
|
:size="24"
|
||||||
style="background: none"
|
style="background: none"
|
||||||
>
|
>
|
||||||
<img src="@/assets/logo_lark.svg" style="width: 100%" alt="" />
|
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
|
||||||
</AppAvatar>
|
</AppAvatar>
|
||||||
<AppAvatar
|
<AppAvatar
|
||||||
v-else-if="!item.dataset_id && item.type === '0'"
|
v-else-if="!item.dataset_id && item.type === '0'"
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
shape="square"
|
shape="square"
|
||||||
:size="24"
|
:size="24"
|
||||||
>
|
>
|
||||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||||
</AppAvatar>
|
</AppAvatar>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,15 @@
|
||||||
v-model="filterText"
|
v-model="filterText"
|
||||||
:placeholder="$t('common.search')"
|
:placeholder="$t('common.search')"
|
||||||
prefix-icon="Search"
|
prefix-icon="Search"
|
||||||
class="p-24 pt-0 pb-0 mb-16 mt-4"
|
class="mb-16 mt-4"
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
<div class="p-24 pt-0">
|
<div class="pt-0">
|
||||||
<el-table :data="filterData" :max-height="tableHeight">
|
<el-table :data="filterData" :max-height="tableHeight">
|
||||||
<el-table-column
|
<el-table-column prop="name" :label="$t('common.name')">
|
||||||
prop="name"
|
|
||||||
:label="
|
|
||||||
isApplication
|
|
||||||
? $t('views.application.applicationForm.form.appName.label')
|
|
||||||
: $t('views.dataset.datasetForm.form.datasetName.label')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
<AppAvatar
|
<el-avatar
|
||||||
v-if="isApplication && isAppIcon(row?.icon)"
|
v-if="isApplication && isAppIcon(row?.icon)"
|
||||||
style="background: none"
|
style="background: none"
|
||||||
class="mr-12"
|
class="mr-12"
|
||||||
|
|
@ -26,9 +19,9 @@
|
||||||
:size="24"
|
:size="24"
|
||||||
>
|
>
|
||||||
<img :src="row?.icon" alt="" />
|
<img :src="row?.icon" alt="" />
|
||||||
</AppAvatar>
|
</el-avatar>
|
||||||
|
|
||||||
<AppAvatar
|
<el-avatar
|
||||||
v-else-if="row?.name && isApplication"
|
v-else-if="row?.name && isApplication"
|
||||||
:name="row?.name"
|
:name="row?.name"
|
||||||
pinyinColor
|
pinyinColor
|
||||||
|
|
@ -36,26 +29,26 @@
|
||||||
:size="24"
|
:size="24"
|
||||||
class="mr-12"
|
class="mr-12"
|
||||||
/>
|
/>
|
||||||
<AppAvatar
|
<el-avatar
|
||||||
v-if="row.icon === '1' && isDataset"
|
v-if="row.icon === '1' && isDataset"
|
||||||
class="mr-8 avatar-purple"
|
class="mr-8 avatar-purple"
|
||||||
shape="square"
|
shape="square"
|
||||||
:size="24"
|
:size="24"
|
||||||
>
|
>
|
||||||
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||||
</AppAvatar>
|
</el-avatar>
|
||||||
<AppAvatar
|
<el-avatar
|
||||||
v-else-if="row.icon === '2' && isDataset"
|
v-else-if="row.icon === '2' && isDataset"
|
||||||
class="mr-8 avatar-purple"
|
class="mr-8 avatar-purple"
|
||||||
shape="square"
|
shape="square"
|
||||||
:size="24"
|
:size="24"
|
||||||
style="background: none"
|
style="background: none"
|
||||||
>
|
>
|
||||||
<img src="@/assets/logo_lark.svg" style="width: 100%" alt="" />
|
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
|
||||||
</AppAvatar>
|
</el-avatar>
|
||||||
<AppAvatar v-else-if="isDataset" class="mr-8 avatar-blue" shape="square" :size="24">
|
<el-avatar v-else-if="isDataset" class="mr-8 avatar-blue" shape="square" :size="24">
|
||||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||||
</AppAvatar>
|
</el-avatar>
|
||||||
<auto-tooltip :content="row?.name">
|
<auto-tooltip :content="row?.name">
|
||||||
{{ row?.name }}
|
{{ row?.name }}
|
||||||
</auto-tooltip>
|
</auto-tooltip>
|
||||||
|
|
@ -63,7 +56,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="$t('views.team.setting.management')"
|
:label="$t('views.resourceAuthorization.setting.management')"
|
||||||
align="center"
|
align="center"
|
||||||
width="100"
|
width="100"
|
||||||
fixed="right"
|
fixed="right"
|
||||||
|
|
@ -71,21 +64,21 @@
|
||||||
<template #header>
|
<template #header>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
:disabled="props.manage"
|
:disabled="props.manage"
|
||||||
v-model="allChecked[TeamEnum.MANAGE]"
|
v-model="allChecked[AuthorizationEnum.MANAGE]"
|
||||||
:indeterminate="allIndeterminate[TeamEnum.MANAGE]"
|
:indeterminate="allIndeterminate[AuthorizationEnum.MANAGE]"
|
||||||
:label="$t('views.team.setting.management')"
|
:label="$t('views.resourceAuthorization.setting.management')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
:disabled="props.manage"
|
:disabled="props.manage"
|
||||||
v-model="row.operate[TeamEnum.MANAGE]"
|
v-model="row.operate[AuthorizationEnum.MANAGE]"
|
||||||
@change="(e: boolean) => checkedOperateChange(TeamEnum.MANAGE, row, e)"
|
@change="(e: boolean) => checkedOperateChange(AuthorizationEnum.MANAGE, row, e)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="$t('views.team.setting.check')"
|
:label="$t('views.resourceAuthorization.setting.check')"
|
||||||
align="center"
|
align="center"
|
||||||
width="100"
|
width="100"
|
||||||
fixed="right"
|
fixed="right"
|
||||||
|
|
@ -93,16 +86,16 @@
|
||||||
<template #header>
|
<template #header>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
:disabled="props.manage"
|
:disabled="props.manage"
|
||||||
v-model="allChecked[TeamEnum.USE]"
|
v-model="allChecked[AuthorizationEnum.USE]"
|
||||||
:indeterminate="allIndeterminate[TeamEnum.USE]"
|
:indeterminate="allIndeterminate[AuthorizationEnum.USE]"
|
||||||
:label="$t('views.team.setting.check')"
|
:label="$t('views.resourceAuthorization.setting.check')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
:disabled="props.manage"
|
:disabled="props.manage"
|
||||||
v-model="row.operate[TeamEnum.USE]"
|
v-model="row.operate[AuthorizationEnum.USE]"
|
||||||
@change="(e: boolean) => checkedOperateChange(TeamEnum.USE, row, e)"
|
@change="(e: boolean) => checkedOperateChange(AuthorizationEnum.USE, row, e)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
@ -111,92 +104,96 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch, computed } from 'vue'
|
import { ref, onMounted, watch, computed } from 'vue'
|
||||||
import { TeamEnum } from '@/enums/team'
|
import { AuthorizationEnum } from '@/enums/system'
|
||||||
import { isAppIcon } from '@/utils/application'
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
id: String,
|
id: String,
|
||||||
type: String,
|
type: String,
|
||||||
tableHeight: Number,
|
tableHeight: Number,
|
||||||
manage: Boolean
|
manage: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
const isDataset = computed(() => props.type === TeamEnum.DATASET)
|
const isDataset = computed(() => props.type === AuthorizationEnum.DATASET)
|
||||||
const isApplication = computed(() => props.type === TeamEnum.APPLICATION)
|
const isApplication = computed(() => props.type === AuthorizationEnum.APPLICATION)
|
||||||
|
|
||||||
const emit = defineEmits(['update:data'])
|
const emit = defineEmits(['update:data'])
|
||||||
const allChecked: any = ref({
|
const allChecked: any = ref({
|
||||||
[TeamEnum.MANAGE]: computed({
|
[AuthorizationEnum.MANAGE]: computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
return filterData.value.some((item: any) => item.operate[TeamEnum.MANAGE])
|
return filterData.value.some((item: any) => item.operate[AuthorizationEnum.MANAGE])
|
||||||
},
|
},
|
||||||
set: (val: boolean) => {
|
set: (val: boolean) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
filterData.value.map((item: any) => {
|
filterData.value.map((item: any) => {
|
||||||
item.operate[TeamEnum.MANAGE] = true
|
item.operate[AuthorizationEnum.MANAGE] = true
|
||||||
item.operate[TeamEnum.USE] = true
|
item.operate[AuthorizationEnum.USE] = true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
filterData.value.map((item: any) => {
|
filterData.value.map((item: any) => {
|
||||||
item.operate[TeamEnum.MANAGE] = false
|
item.operate[AuthorizationEnum.MANAGE] = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
[TeamEnum.USE]: computed({
|
[AuthorizationEnum.USE]: computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
return filterData.value.some((item: any) => item.operate[TeamEnum.USE])
|
return filterData.value.some((item: any) => item.operate[AuthorizationEnum.USE])
|
||||||
},
|
},
|
||||||
set: (val: boolean) => {
|
set: (val: boolean) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
filterData.value.map((item: any) => {
|
filterData.value.map((item: any) => {
|
||||||
item.operate[TeamEnum.USE] = true
|
item.operate[AuthorizationEnum.USE] = true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
filterData.value.map((item: any) => {
|
filterData.value.map((item: any) => {
|
||||||
item.operate[TeamEnum.USE] = false
|
item.operate[AuthorizationEnum.USE] = false
|
||||||
item.operate[TeamEnum.MANAGE] = false
|
item.operate[AuthorizationEnum.MANAGE] = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const filterText = ref('')
|
const filterText = ref('')
|
||||||
|
|
||||||
const filterData = computed(() =>
|
const filterData = computed(() =>
|
||||||
props.data.filter((v: any) => v.name.toLowerCase().includes(filterText.value.toLowerCase()))
|
props.data.filter((v: any) => v.name.toLowerCase().includes(filterText.value.toLowerCase())),
|
||||||
)
|
)
|
||||||
|
|
||||||
const allIndeterminate: any = ref({
|
const allIndeterminate: any = ref({
|
||||||
[TeamEnum.MANAGE]: computed(() => {
|
[AuthorizationEnum.MANAGE]: computed(() => {
|
||||||
const all_not_checked = filterData.value.every((item: any) => !item.operate[TeamEnum.MANAGE])
|
const all_not_checked = filterData.value.every(
|
||||||
|
(item: any) => !item.operate[AuthorizationEnum.MANAGE],
|
||||||
|
)
|
||||||
if (all_not_checked) {
|
if (all_not_checked) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return !filterData.value.every((item: any) => item.operate[TeamEnum.MANAGE])
|
return !filterData.value.every((item: any) => item.operate[AuthorizationEnum.MANAGE])
|
||||||
}),
|
}),
|
||||||
[TeamEnum.USE]: computed(() => {
|
[AuthorizationEnum.USE]: computed(() => {
|
||||||
const all_not_checked = filterData.value.every((item: any) => !item.operate[TeamEnum.USE])
|
const all_not_checked = filterData.value.every(
|
||||||
|
(item: any) => !item.operate[AuthorizationEnum.USE],
|
||||||
|
)
|
||||||
if (all_not_checked) {
|
if (all_not_checked) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return !filterData.value.every((item: any) => item.operate[TeamEnum.USE])
|
return !filterData.value.every((item: any) => item.operate[AuthorizationEnum.USE])
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
function checkedOperateChange(Name: string | number, row: any, e: boolean) {
|
function checkedOperateChange(Name: string | number, row: any, e: boolean) {
|
||||||
props.data.map((item: any) => {
|
props.data.map((item: any) => {
|
||||||
if (item.id === row.id) {
|
if (item.id === row.id) {
|
||||||
item.operate[Name] = e
|
item.operate[Name] = e
|
||||||
if (Name === TeamEnum.MANAGE && e) {
|
if (Name === AuthorizationEnum.MANAGE && e) {
|
||||||
item.operate[TeamEnum.USE] = true
|
item.operate[AuthorizationEnum.USE] = true
|
||||||
} else if (Name === TeamEnum.USE && !e) {
|
} else if (Name === AuthorizationEnum.USE && !e) {
|
||||||
item.operate[TeamEnum.MANAGE] = false
|
item.operate[AuthorizationEnum.MANAGE] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-16-24">
|
<div class="resource-authorization p-16-24">
|
||||||
<h4 class="mb-16">{{ $t('views.userManage.title') }}</h4>
|
<h4 class="mb-16">{{ $t('views.userManage.title') }}</h4>
|
||||||
<el-card>
|
<el-card style="--el-card-padding: 0">
|
||||||
<div class="resource-authorization flex main-calc-height">
|
<div class="flex main-calc-height">
|
||||||
<div class="team-member p-8 border-r">
|
<div class="resource-authorization__left border-r p-8">
|
||||||
<div class="flex-between p-16">
|
<div class="p-8">
|
||||||
<h4>{{ $t('views.resourceAuthorization.member') }}</h4>
|
<h4 class="mb-12">{{ $t('views.resourceAuthorization.member') }}</h4>
|
||||||
</div>
|
|
||||||
<div class="team-member-input">
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="filterText"
|
v-model="filterText"
|
||||||
:placeholder="$t('common.search')"
|
:placeholder="$t('common.search')"
|
||||||
|
|
@ -27,48 +25,34 @@
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="flex-between">
|
<div class="flex-between">
|
||||||
<div>
|
<div>
|
||||||
<span class="mr-8">{{ row.username }}</span>
|
<span class="mr-8">{{ row.nick_name }}</span>
|
||||||
<el-tag v-if="isManage(row.type)" class="default-tag">{{
|
<el-tag v-if="isManage(row.type)" class="default-tag">{{
|
||||||
$t('views.resourceAuthorization.manage')
|
$t('views.resourceAuthorization.manage')
|
||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div @click.stop style="margin-top: 5px">
|
|
||||||
<el-dropdown trigger="click" v-if="!isManage(row.type)">
|
|
||||||
<span class="cursor">
|
|
||||||
<el-icon class="rotate-90"><MoreFilled /></el-icon>
|
|
||||||
</span>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item @click.prevent="deleteMember(row)">{{
|
|
||||||
$t('views.resourceAuthorization.delete.button')
|
|
||||||
}}</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</common-list>
|
</common-list>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="permission-setting flex" v-loading="rLoading">
|
<div class="permission-setting p-16 flex" v-loading="rLoading">
|
||||||
<div class="team-manage__table">
|
<div class="resource-authorization__table">
|
||||||
<h4 class="p-24 pb-0 mb-4">{{ $t('views.resourceAuthorization.permissionSetting') }}</h4>
|
<h4 class="mb-4">{{ $t('views.resourceAuthorization.permissionSetting') }}</h4>
|
||||||
<el-tabs v-model="activeName" class="team-manage__tabs">
|
<el-tabs v-model="activeName" class="resource-authorization__tabs">
|
||||||
<el-tab-pane
|
<el-tab-pane
|
||||||
v-for="(item, index) in settingTags"
|
v-for="(item, index) in settingTags"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:name="item.value"
|
:name="item.value"
|
||||||
>
|
>
|
||||||
<!-- <PermissionSetting
|
<PermissionSetting
|
||||||
:key="index"
|
:key="index"
|
||||||
:data="item.data"
|
:data="item.data"
|
||||||
:type="item.value"
|
:type="item.value"
|
||||||
:tableHeight="tableHeight"
|
:tableHeight="tableHeight"
|
||||||
:manage="isManage(currentType)"
|
:manage="isManage(currentType)"
|
||||||
></PermissionSetting> -->
|
></PermissionSetting>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -79,36 +63,33 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
<!-- <CreateMemberDialog ref="CreateMemberRef" @refresh="refresh" /> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, reactive, watch } from 'vue'
|
import { onMounted, ref, reactive, watch } from 'vue'
|
||||||
import AuthorizationApi from '@/api/user/resource-authorization'
|
import AuthorizationApi from '@/api/user/resource-authorization'
|
||||||
import type { TeamMember } from '@/api/type/team'
|
import PermissionSetting from './component/PermissionSetting.vue'
|
||||||
// import CreateMemberDialog from './component/CreateMemberDialog.vue'
|
|
||||||
// import PermissionSetting from './component/PermissionSetting.vue'
|
|
||||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
import { AuthorizationEnum } from '@/enums/system'
|
import { AuthorizationEnum } from '@/enums/system'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
// const CreateMemberRef = ref<InstanceType<typeof CreateMemberDialog>>()
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const rLoading = ref(false)
|
const rLoading = ref(false)
|
||||||
const memberList = ref<TeamMember[]>([]) // 全部成员
|
const memberList = ref<any[]>([]) // 全部成员
|
||||||
const filterMember = ref<TeamMember[]>([]) // 搜索过滤后列表
|
const filterMember = ref<any[]>([]) // 搜索过滤后列表
|
||||||
const currentUser = ref<String>('')
|
const currentUser = ref<String>('')
|
||||||
const currentType = ref<String>('')
|
const currentType = ref<String>('')
|
||||||
|
|
||||||
const filterText = ref('')
|
const filterText = ref('')
|
||||||
|
|
||||||
const activeName = ref(AuthorizationEnum.DATASET)
|
const activeName = ref(AuthorizationEnum.KNOWLEDGE)
|
||||||
const tableHeight = ref(0)
|
const tableHeight = ref(0)
|
||||||
|
|
||||||
const settingTags = reactive([
|
const settingTags = reactive([
|
||||||
{
|
{
|
||||||
label: t('views.knowledge.title'),
|
label: t('views.knowledge.title'),
|
||||||
value: AuthorizationEnum.DATASET,
|
value: AuthorizationEnum.KNOWLEDGE,
|
||||||
data: [] as any,
|
data: [] as any,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -156,10 +137,40 @@ function submitPermissions() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clickMemberHandle(item: any) {
|
||||||
|
currentUser.value = item.id
|
||||||
|
currentType.value = item.type
|
||||||
|
ResourcePermissions(item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMember(id?: string) {
|
||||||
|
loading.value = true
|
||||||
|
AuthorizationApi.getUserList()
|
||||||
|
.then((res) => {
|
||||||
|
memberList.value = res.data
|
||||||
|
filterMember.value = res.data
|
||||||
|
|
||||||
|
const user = (id && memberList.value.find((p) => p.user_id === id)) || null
|
||||||
|
currentUser.value = user ? user.id : memberList.value[0].id
|
||||||
|
currentType.value = user ? user.type : memberList.value[0].type
|
||||||
|
ResourcePermissions(currentUser.value)
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
function ResourcePermissions() {
|
function ResourcePermissions() {
|
||||||
rLoading.value = true
|
rLoading.value = true
|
||||||
AuthorizationApi.getResourceAuthorization('default')
|
AuthorizationApi.getResourceAuthorization('default')
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
if (!res.data || Object.keys(res.data).length > 0) {
|
||||||
|
settingTags.map((item) => {
|
||||||
|
if (Object.keys(res.data).indexOf(item.value) !== -1) {
|
||||||
|
item.data = res.data[item.value]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
rLoading.value = false
|
rLoading.value = false
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|
@ -176,19 +187,13 @@ onMounted(() => {
|
||||||
tableHeight.value = window.innerHeight - 330
|
tableHeight.value = window.innerHeight - 330
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
ResourcePermissions()
|
getMember()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.resource-authorization {
|
.resource-authorization {
|
||||||
.add-user-icon {
|
.resource-authorization__left {
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
.team-member-input {
|
|
||||||
padding: 0 calc(var(--app-base-px) * 2);
|
|
||||||
}
|
|
||||||
.team-member {
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: var(--setting-left-width);
|
width: var(--setting-left-width);
|
||||||
min-width: var(--setting-left-width);
|
min-width: var(--setting-left-width);
|
||||||
|
|
@ -196,28 +201,17 @@ onMounted(() => {
|
||||||
|
|
||||||
.permission-setting {
|
.permission-setting {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: calc(100% - var(--setting-left-width));
|
width: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
.submit-button {
|
.submit-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 54px;
|
top: 16px;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.list-height-left {
|
.list-height-left {
|
||||||
height: calc(var(--create-dataset-height) - 60px);
|
height: calc(100vh - 240px);
|
||||||
}
|
|
||||||
|
|
||||||
&__tabs {
|
|
||||||
margin-top: 10px;
|
|
||||||
|
|
||||||
:deep(.el-tabs__nav-scroll) {
|
|
||||||
padding: 0 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&__table {
|
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="title"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:before-close="close"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<DynamicsFormConstructor
|
||||||
|
v-model="dynamicsFormData"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
ref="dynamicsFormConstructorRef"
|
||||||
|
></DynamicsFormConstructor>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false"> {{$t('common.cancel')}} </el-button>
|
||||||
|
<el-button type="primary" @click="submit()" :loading="loading"> {{$t('common.add')}} </el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{ title?: string; addFormField: (form_data: any) => void }>(),
|
||||||
|
{ title: t('views.template.templateForm.title.addParam') }
|
||||||
|
)
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const dynamicsFormConstructorRef = ref<InstanceType<typeof DynamicsFormConstructor>>()
|
||||||
|
const emit = defineEmits(['submit'])
|
||||||
|
const dynamicsFormData = ref<any>({})
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const open = () => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
const close = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
dynamicsFormData.value = {}
|
||||||
|
}
|
||||||
|
const submit = () => {
|
||||||
|
dynamicsFormConstructorRef.value?.validate().then(() => {
|
||||||
|
props.addFormField(dynamicsFormConstructorRef.value?.getData())
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defineExpose({ close, open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div class="custom-edge cursor" @mouseup.stop @click.stop v-show="props.model.isHovered">
|
||||||
|
<svg
|
||||||
|
@click="deleteEdge"
|
||||||
|
width="22"
|
||||||
|
height="22"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 23.0001C5.925 23.0001 1 18.0751 1 12.0001C1 5.92512 5.925 1.00012 12 1.00012C18.075 1.00012 23 5.92512 23 12.0001C23 18.0751 18.075 23.0001 12 23.0001Z"
|
||||||
|
fill="#3370FF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9.02524 7.61124L12.0002 10.5862L14.9752 7.61124C15.069 7.5175 15.1962 7.46484 15.3287 7.46484C15.4613 7.46484 15.5885 7.5175 15.6822 7.61124L16.3892 8.31824C16.483 8.412 16.5356 8.53915 16.5356 8.67174C16.5356 8.80432 16.483 8.93147 16.3892 9.02524L13.4142 12.0002L16.3892 14.9752C16.483 15.069 16.5356 15.1962 16.5356 15.3287C16.5356 15.4613 16.483 15.5885 16.3892 15.6822L15.6822 16.3892C15.5885 16.483 15.4613 16.5356 15.3287 16.5356C15.1962 16.5356 15.069 16.483 14.9752 16.3892L12.0002 13.4142L9.02524 16.3892C8.93147 16.483 8.80432 16.5356 8.67174 16.5356C8.53916 16.5356 8.412 16.483 8.31824 16.3892L7.61124 15.6822C7.5175 15.5885 7.46484 15.4613 7.46484 15.3287C7.46484 15.1962 7.5175 15.069 7.61124 14.9752L10.5862 12.0002L7.61124 9.02524C7.5175 8.93147 7.46484 8.80432 7.46484 8.67174C7.46484 8.53915 7.5175 8.412 7.61124 8.31824L8.31824 7.61124C8.412 7.5175 8.53916 7.46484 8.67174 7.46484C8.80432 7.46484 8.93147 7.5175 9.02524 7.61124Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{ model: any }>()
|
||||||
|
const deleteEdge = () => {
|
||||||
|
props.model.graphModel.deleteEdgeById(props.model.id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.custom-edge {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
stroke: none;
|
||||||
|
z-index: 100000;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="title"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:before-close="close"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<DynamicsFormConstructor
|
||||||
|
v-model="dynamicsFormData"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
ref="dynamicsFormConstructorRef"
|
||||||
|
></DynamicsFormConstructor>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit()" :loading="loading">
|
||||||
|
{{ $t('common.modify') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{ title?: string; editFormField: (form_data: any, index: number) => void }>(),
|
||||||
|
{ title: t('views.template.templateForm.title.editParam') }
|
||||||
|
)
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const dynamicsFormConstructorRef = ref<InstanceType<typeof DynamicsFormConstructor>>()
|
||||||
|
const emit = defineEmits(['submit'])
|
||||||
|
const dynamicsFormData = ref<any>({})
|
||||||
|
const currentIndex = ref<number>(0)
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const open = (form_data: any, index: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dynamicsFormData.value = form_data
|
||||||
|
currentIndex.value = index
|
||||||
|
}
|
||||||
|
const close = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
dynamicsFormData.value = {}
|
||||||
|
}
|
||||||
|
const submit = () => {
|
||||||
|
dynamicsFormConstructorRef.value?.validate().then(() => {
|
||||||
|
props.editFormField(dynamicsFormConstructorRef.value?.getData(), currentIndex.value)
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defineExpose({ close, open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<el-cascader
|
||||||
|
@wheel="wheel"
|
||||||
|
:teleported="false"
|
||||||
|
:options="options"
|
||||||
|
@visible-change="visibleChange"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model="data"
|
||||||
|
separator=" > "
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<span class="flex align-center" @wheel="wheel">
|
||||||
|
<component :is="iconComponent(`${data.type}-icon`)" class="mr-8" :size="18" />{{
|
||||||
|
data.label
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-cascader>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { iconComponent } from '../icons/utils'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const props = defineProps<{
|
||||||
|
nodeModel: any
|
||||||
|
modelValue: Array<any>
|
||||||
|
global?: Boolean
|
||||||
|
}>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const data = computed({
|
||||||
|
set: (value) => {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
},
|
||||||
|
get: () => {
|
||||||
|
return props.modelValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const options = ref<Array<any>>([])
|
||||||
|
|
||||||
|
const wheel = (e: any) => {
|
||||||
|
if (e.ctrlKey === true) {
|
||||||
|
e.preventDefault()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
e.stopPropagation()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function visibleChange(bool: boolean) {
|
||||||
|
if (bool) {
|
||||||
|
options.value = props.global
|
||||||
|
? props.nodeModel.get_up_node_field_list(false, true).filter((v: any) => v.value === 'global')
|
||||||
|
: props.nodeModel.get_up_node_field_list(false, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
const incomingNodeValue = props.nodeModel.get_up_node_field_list(false, true)
|
||||||
|
if (!data.value || data.value.length === 0) {
|
||||||
|
return Promise.reject(t('views.applicationWorkflow.variable.ReferencingRequired'))
|
||||||
|
}
|
||||||
|
if (data.value.length < 2) {
|
||||||
|
return Promise.reject(t('views.applicationWorkflow.variable.ReferencingError'))
|
||||||
|
}
|
||||||
|
const node_id = data.value[0]
|
||||||
|
const node_field = data.value[1]
|
||||||
|
const nodeParent = incomingNodeValue.find((item: any) => item.value === node_id)
|
||||||
|
if (!nodeParent) {
|
||||||
|
data.value = []
|
||||||
|
return Promise.reject(t('views.applicationWorkflow.variable.NoReferencing'))
|
||||||
|
}
|
||||||
|
if (!nodeParent.children.some((item: any) => item.value === node_field)) {
|
||||||
|
data.value = []
|
||||||
|
return Promise.reject(t('views.applicationWorkflow.variable.NoReferencing'))
|
||||||
|
}
|
||||||
|
return Promise.resolve('')
|
||||||
|
}
|
||||||
|
defineExpose({ validate })
|
||||||
|
onMounted(() => {
|
||||||
|
options.value = props.global
|
||||||
|
? props.nodeModel.get_up_node_field_list(false, true).filter((v: any) => v.value === 'global')
|
||||||
|
: props.nodeModel.get_up_node_field_list(false, true)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
<template>
|
||||||
|
<div @mousedown="mousedown" class="workflow-node-container p-16" style="overflow: visible">
|
||||||
|
<div
|
||||||
|
class="step-container app-card p-16"
|
||||||
|
:class="{ isSelected: props.nodeModel.isSelected, error: node_status !== 200 }"
|
||||||
|
style="overflow: visible"
|
||||||
|
>
|
||||||
|
<div v-resize="resizeStepContainer">
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center" style="width: 70%">
|
||||||
|
<component
|
||||||
|
:is="iconComponent(`${nodeModel.type}-icon`)"
|
||||||
|
class="mr-8"
|
||||||
|
:size="24"
|
||||||
|
:item="nodeModel?.properties.node_data"
|
||||||
|
/>
|
||||||
|
<h4 class="ellipsis-1 break-all">{{ nodeModel.properties.stepName }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div @mousemove.stop @mousedown.stop @keydown.stop @click.stop>
|
||||||
|
<el-button text @click="showNode = !showNode">
|
||||||
|
<el-icon class="arrow-icon color-secondary" :class="showNode ? 'rotate-180' : ''"
|
||||||
|
><ArrowDownBold />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-dropdown
|
||||||
|
v-if="showOperate(nodeModel.type)"
|
||||||
|
:teleported="false"
|
||||||
|
trigger="click"
|
||||||
|
placement="bottom-start"
|
||||||
|
>
|
||||||
|
<el-button text>
|
||||||
|
<img src="@/assets/icon_or.svg" alt="" v-if="condition === 'OR'" />
|
||||||
|
<img src="@/assets/icon_and.svg" alt="" v-if="condition === 'AND'" />
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<div style="width: 280px" class="p-12-16">
|
||||||
|
<h5>{{ $t('views.applicationWorkflow.condition.title') }}</h5>
|
||||||
|
<p class="mt-8 lighter">
|
||||||
|
<span>{{ $t('views.applicationWorkflow.condition.front') }}</span>
|
||||||
|
<el-select v-model="condition" size="small" style="width: 60px; margin: 0 8px">
|
||||||
|
<el-option
|
||||||
|
:label="$t('views.applicationWorkflow.condition.AND')"
|
||||||
|
value="AND"
|
||||||
|
/>
|
||||||
|
<el-option :label="$t('views.applicationWorkflow.condition.OR')" value="OR" />
|
||||||
|
</el-select>
|
||||||
|
<span>{{ $t('views.applicationWorkflow.condition.text') }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
<el-dropdown v-if="showOperate(nodeModel.type)" :teleported="false" trigger="click">
|
||||||
|
<el-button text>
|
||||||
|
<el-icon class="color-secondary"><MoreFilled /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu style="min-width: 80px">
|
||||||
|
<el-dropdown-item @click="renameNode" class="p-8">{{
|
||||||
|
$t('common.rename')
|
||||||
|
}}</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click="copyNode" class="p-8">{{
|
||||||
|
$t('common.copy')
|
||||||
|
}}</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click="deleteNode" class="border-t p-8">{{
|
||||||
|
$t('common.delete')
|
||||||
|
}}</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-collapse-transition>
|
||||||
|
<div @mousedown.stop @keydown.stop @click.stop v-show="showNode" class="mt-16">
|
||||||
|
<el-alert
|
||||||
|
v-if="node_status != 200"
|
||||||
|
class="mb-16"
|
||||||
|
:title="
|
||||||
|
props.nodeModel.type === 'application-node'
|
||||||
|
? $t('views.applicationWorkflow.tip.applicationNodeError')
|
||||||
|
: $t('views.applicationWorkflow.tip.functionNodeError')
|
||||||
|
"
|
||||||
|
type="error"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
/>
|
||||||
|
<slot></slot>
|
||||||
|
<template v-if="nodeFields.length > 0">
|
||||||
|
<h5 class="title-decoration-1 mb-8 mt-8">
|
||||||
|
{{ $t('common.param.outputParam') }}
|
||||||
|
</h5>
|
||||||
|
<template v-for="(item, index) in nodeFields" :key="index">
|
||||||
|
<div
|
||||||
|
class="flex-between border-r-4 p-8-12 mb-8 layout-bg lighter"
|
||||||
|
@mouseenter="showicon = index"
|
||||||
|
@mouseleave="showicon = null"
|
||||||
|
>
|
||||||
|
<span style="max-width: 92%">{{ item.label }} {{ '{' + item.value + '}' }}</span>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.applicationWorkflow.setting.copyParam')"
|
||||||
|
placement="top"
|
||||||
|
v-if="showicon === index"
|
||||||
|
>
|
||||||
|
<el-button link @click="copyClick(item.globeLabel)" style="padding: 0">
|
||||||
|
<AppIcon iconName="app-copy"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</el-collapse-transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-collapse-transition>
|
||||||
|
<DropdownMenu
|
||||||
|
v-if="showAnchor"
|
||||||
|
@mousemove.stop
|
||||||
|
@mousedown.stop
|
||||||
|
@click.stop
|
||||||
|
@wheel="handleWheel"
|
||||||
|
:show="showAnchor"
|
||||||
|
:id="id"
|
||||||
|
style="left: 100%; top: 50%; transform: translate(0, -50%)"
|
||||||
|
@clickNodes="clickNodes"
|
||||||
|
/>
|
||||||
|
</el-collapse-transition>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.applicationWorkflow.nodeName')"
|
||||||
|
v-model="nodeNameDialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
append-to-body
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<el-form label-position="top" ref="titleFormRef" :model="form">
|
||||||
|
<el-form-item
|
||||||
|
prop="title"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: $t('common.inputPlaceholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<el-input v-model="form.title" @blur="form.title = form.title.trim()" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="nodeNameDialogVisible = false">
|
||||||
|
{{ $t('common.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="editName(titleFormRef)">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { app } from '@/main'
|
||||||
|
import DropdownMenu from '@/views/application-workflow/component/DropdownMenu.vue'
|
||||||
|
import { set } from 'lodash'
|
||||||
|
import { iconComponent } from '../icons/utils'
|
||||||
|
import { copyClick } from '@/utils/clipboard'
|
||||||
|
import { WorkflowType } from '@/enums/workflow'
|
||||||
|
import { MsgError, MsgConfirm } from '@/utils/message'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = app.config.globalProperties.$route as any
|
||||||
|
|
||||||
|
const height = ref<{
|
||||||
|
stepContainerHeight: number
|
||||||
|
inputContainerHeight: number
|
||||||
|
outputContainerHeight: number
|
||||||
|
}>({
|
||||||
|
stepContainerHeight: 0,
|
||||||
|
inputContainerHeight: 0,
|
||||||
|
outputContainerHeight: 0
|
||||||
|
})
|
||||||
|
const showAnchor = ref<boolean>(false)
|
||||||
|
const anchorData = ref<any>()
|
||||||
|
const titleFormRef = ref()
|
||||||
|
const nodeNameDialogVisible = ref<boolean>(false)
|
||||||
|
const form = ref<any>({
|
||||||
|
title: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const condition = computed({
|
||||||
|
set: (v) => {
|
||||||
|
set(props.nodeModel.properties, 'condition', v)
|
||||||
|
},
|
||||||
|
get: () => {
|
||||||
|
if (props.nodeModel.properties.condition) {
|
||||||
|
return props.nodeModel.properties.condition
|
||||||
|
}
|
||||||
|
set(props.nodeModel.properties, 'condition', 'AND')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const showNode = computed({
|
||||||
|
set: (v) => {
|
||||||
|
set(props.nodeModel.properties, 'showNode', v)
|
||||||
|
},
|
||||||
|
get: () => {
|
||||||
|
if (props.nodeModel.properties.showNode !== undefined) {
|
||||||
|
return props.nodeModel.properties.showNode
|
||||||
|
}
|
||||||
|
set(props.nodeModel.properties, 'showNode', true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleWheel = (event: any) => {
|
||||||
|
const isCombinationKeyPressed = event.ctrlKey || event.metaKey
|
||||||
|
if (!isCombinationKeyPressed) {
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const node_status = computed(() => {
|
||||||
|
if (props.nodeModel.properties.status) {
|
||||||
|
return props.nodeModel.properties.status
|
||||||
|
}
|
||||||
|
return 200
|
||||||
|
})
|
||||||
|
|
||||||
|
function renameNode() {
|
||||||
|
form.value.title = props.nodeModel.properties.stepName
|
||||||
|
nodeNameDialogVisible.value = true
|
||||||
|
}
|
||||||
|
const editName = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (
|
||||||
|
!props.nodeModel.graphModel.nodes?.some(
|
||||||
|
(node: any) => node.properties.stepName === form.value.title
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
set(props.nodeModel.properties, 'stepName', form.value.title)
|
||||||
|
nodeNameDialogVisible.value = false
|
||||||
|
formEl.resetFields()
|
||||||
|
} else {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.repeatedNodeError'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const mousedown = () => {
|
||||||
|
props.nodeModel.graphModel.clearSelectElements()
|
||||||
|
set(props.nodeModel, 'isSelected', true)
|
||||||
|
set(props.nodeModel, 'isHovered', true)
|
||||||
|
props.nodeModel.graphModel.toFront(props.nodeModel.id)
|
||||||
|
}
|
||||||
|
const showicon = ref<number | null>(null)
|
||||||
|
const copyNode = () => {
|
||||||
|
props.nodeModel.graphModel.clearSelectElements()
|
||||||
|
const cloneNode = props.nodeModel.graphModel.cloneNode(props.nodeModel.id)
|
||||||
|
set(cloneNode, 'isSelected', true)
|
||||||
|
set(cloneNode, 'isHovered', true)
|
||||||
|
props.nodeModel.graphModel.toFront(cloneNode.id)
|
||||||
|
}
|
||||||
|
const deleteNode = () => {
|
||||||
|
MsgConfirm(t('common.tip'), t('views.applicationWorkflow.delete.confirmTitle'), {
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
confirmButtonClass: 'danger'
|
||||||
|
}).then(() => {
|
||||||
|
props.nodeModel.graphModel.deleteNode(props.nodeModel.id)
|
||||||
|
})
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('delete_node')
|
||||||
|
}
|
||||||
|
const resizeStepContainer = (wh: any) => {
|
||||||
|
if (wh.height) {
|
||||||
|
if (!props.nodeModel.virtual) {
|
||||||
|
height.value.stepContainerHeight = wh.height
|
||||||
|
props.nodeModel.setHeight(height.value.stepContainerHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickNodes(item: any) {
|
||||||
|
const width = item.properties.width ? item.properties.width : 214
|
||||||
|
const nodeModel = props.nodeModel.graphModel.addNode({
|
||||||
|
type: item.type,
|
||||||
|
properties: item.properties,
|
||||||
|
x: anchorData.value?.x + width / 2 + 200,
|
||||||
|
y: anchorData.value?.y - item.height
|
||||||
|
})
|
||||||
|
props.nodeModel.graphModel.addEdge({
|
||||||
|
type: 'app-edge',
|
||||||
|
sourceNodeId: props.nodeModel.id,
|
||||||
|
sourceAnchorId: anchorData.value?.id,
|
||||||
|
targetNodeId: nodeModel.id
|
||||||
|
})
|
||||||
|
|
||||||
|
closeNodeMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
nodeModel: any
|
||||||
|
}>()
|
||||||
|
const nodeFields = computed(() => {
|
||||||
|
if (props.nodeModel.properties.config.fields) {
|
||||||
|
const fields = props.nodeModel.properties.config.fields?.map((field: any) => {
|
||||||
|
return {
|
||||||
|
label: field.label,
|
||||||
|
value: field.value,
|
||||||
|
globeLabel: `{{${props.nodeModel.properties.stepName}.${field.value}}}`,
|
||||||
|
globeValue: `{{context['${props.nodeModel.id}'].${field.value}}}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
function showOperate(type: string) {
|
||||||
|
return type !== WorkflowType.Base && type !== WorkflowType.Start
|
||||||
|
}
|
||||||
|
const openNodeMenu = (anchorValue: any) => {
|
||||||
|
showAnchor.value = true
|
||||||
|
anchorData.value = anchorValue
|
||||||
|
}
|
||||||
|
const closeNodeMenu = () => {
|
||||||
|
showAnchor.value = false
|
||||||
|
anchorData.value = undefined
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
set(props.nodeModel, 'openNodeMenu', (anchorData: any) => {
|
||||||
|
showAnchor.value ? closeNodeMenu() : openNodeMenu(anchorData)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.workflow-node-container {
|
||||||
|
.step-container {
|
||||||
|
border: 2px solid #ffffff !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
|
||||||
|
}
|
||||||
|
&.isSelected {
|
||||||
|
border: 2px solid var(--el-color-primary) !important;
|
||||||
|
}
|
||||||
|
&.error {
|
||||||
|
border: 1px solid #f54a45 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.arrow-icon {
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.el-card) {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
<template>
|
||||||
|
<el-card shadow="always" style="--el-card-padding: 8px 12px; --el-card-border-radius: 8px">
|
||||||
|
<el-button link @click="zoomOut">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.applicationWorkflow.control.zoomOut')"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-icon :size="16" :title="$t('views.applicationWorkflow.control.zoomOut')"
|
||||||
|
><ZoomOut
|
||||||
|
/></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-button>
|
||||||
|
<el-button link @click="zoomIn">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.applicationWorkflow.control.zoomIn')"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-icon :size="16" :title="$t('views.applicationWorkflow.control.zoomIn')"
|
||||||
|
><ZoomIn
|
||||||
|
/></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-button>
|
||||||
|
<el-button link @click="fitView">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.applicationWorkflow.control.fitView')"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<AppIcon
|
||||||
|
iconName="app-fitview"
|
||||||
|
:title="$t('views.applicationWorkflow.control.fitView')"
|
||||||
|
></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button link @click="retract">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.applicationWorkflow.control.retract')"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<AppIcon
|
||||||
|
style="font-size: 16px"
|
||||||
|
iconName="app-retract"
|
||||||
|
:title="$t('views.applicationWorkflow.control.retract')"
|
||||||
|
></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-button>
|
||||||
|
<el-button link @click="extend">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.applicationWorkflow.control.extend')"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<AppIcon
|
||||||
|
style="font-size: 16px"
|
||||||
|
iconName="app-extend"
|
||||||
|
:title="$t('views.applicationWorkflow.control.extend')"
|
||||||
|
></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-button>
|
||||||
|
<el-button link @click="layout">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.applicationWorkflow.control.beautify')"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<AppIcon
|
||||||
|
style="font-size: 16px"
|
||||||
|
iconName="app-beautify"
|
||||||
|
:title="$t('views.applicationWorkflow.control.beautify')"
|
||||||
|
></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-button>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
lf: Object || String || null
|
||||||
|
})
|
||||||
|
|
||||||
|
function zoomIn() {
|
||||||
|
props.lf?.zoom(true, [0, 0])
|
||||||
|
}
|
||||||
|
function zoomOut() {
|
||||||
|
props.lf?.zoom(false, [0, 0])
|
||||||
|
}
|
||||||
|
function fitView() {
|
||||||
|
props.lf?.resetZoom()
|
||||||
|
props.lf?.resetTranslate()
|
||||||
|
props.lf?.fitView()
|
||||||
|
}
|
||||||
|
const layout = () => {
|
||||||
|
props.lf?.extension.dagre.layout()
|
||||||
|
}
|
||||||
|
const retract = () => {
|
||||||
|
props.lf?.graphModel.nodes.forEach((element: any) => {
|
||||||
|
element.properties.showNode = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const extend = () => {
|
||||||
|
props.lf?.graphModel.nodes.forEach((element: any) => {
|
||||||
|
element.properties.showNode = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,407 @@
|
||||||
|
import Components from '@/components'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import * as ElementPlusIcons from '@element-plus/icons-vue'
|
||||||
|
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||||
|
import { HtmlResize } from '@logicflow/extension'
|
||||||
|
import { h as lh } from '@logicflow/core'
|
||||||
|
import { createApp, h } from 'vue'
|
||||||
|
import directives from '@/directives'
|
||||||
|
import i18n from '@/locales'
|
||||||
|
import { WorkflowType } from '@/enums/workflow'
|
||||||
|
import { nodeDict } from '@/workflow/common/data'
|
||||||
|
import { isActive, connect, disconnect } from './teleport'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import { type Dict } from '@/api/type/common'
|
||||||
|
class AppNode extends HtmlResize.view {
|
||||||
|
isMounted
|
||||||
|
r?: any
|
||||||
|
component: any
|
||||||
|
app: any
|
||||||
|
root?: any
|
||||||
|
VueNode: any
|
||||||
|
up_node_field_dict?: Dict<Array<any>>
|
||||||
|
constructor(props: any, VueNode: any) {
|
||||||
|
super(props)
|
||||||
|
this.component = VueNode
|
||||||
|
this.isMounted = false
|
||||||
|
props.model.clear_next_node_field = this.clear_next_node_field.bind(this)
|
||||||
|
props.model.get_up_node_field_dict = this.get_up_node_field_dict.bind(this)
|
||||||
|
props.model.get_node_field_list = this.get_node_field_list.bind(this)
|
||||||
|
props.model.get_up_node_field_list = this.get_up_node_field_list.bind(this)
|
||||||
|
|
||||||
|
if (props.model.properties.noRender) {
|
||||||
|
delete props.model.properties.noRender
|
||||||
|
} else {
|
||||||
|
const filterNodes = props.graphModel.nodes.filter((v: any) => v.type === props.model.type)
|
||||||
|
const filterNameSameNodes = filterNodes.filter(
|
||||||
|
(v: any) => v.properties.stepName === props.model.properties.stepName
|
||||||
|
)
|
||||||
|
if (filterNameSameNodes.length - 1 > 0) {
|
||||||
|
getNodesName(filterNameSameNodes.length - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getNodesName(num: number) {
|
||||||
|
const number = num
|
||||||
|
const name = props.model.properties.stepName + number
|
||||||
|
if (!props.graphModel.nodes?.some((node: any) => node.properties.stepName === name.trim())) {
|
||||||
|
props.model.properties.stepName = name
|
||||||
|
} else {
|
||||||
|
getNodesName(number + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.model.properties.config = nodeDict[props.model.type].properties.config
|
||||||
|
if (props.model.properties.height) {
|
||||||
|
props.model.height = props.model.properties.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get_node_field_list() {
|
||||||
|
const result = []
|
||||||
|
if (this.props.model.type === 'start-node') {
|
||||||
|
result.push({
|
||||||
|
value: 'global',
|
||||||
|
label: t('views.applicationWorkflow.variable.global'),
|
||||||
|
type: 'global',
|
||||||
|
children: this.props.model.properties?.config?.globalFields || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
value: this.props.model.id,
|
||||||
|
label: this.props.model.properties.stepName,
|
||||||
|
type: this.props.model.type,
|
||||||
|
children: this.props.model.properties?.config?.fields || []
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
get_up_node_field_dict(contain_self: boolean, use_cache: boolean) {
|
||||||
|
if (!this.up_node_field_dict || !use_cache) {
|
||||||
|
const up_node_list = this.props.graphModel.getNodeIncomingNode(this.props.model.id)
|
||||||
|
this.up_node_field_dict = up_node_list
|
||||||
|
.filter((node) => node.id != 'start-node')
|
||||||
|
.map((node) => node.get_up_node_field_dict(true, use_cache))
|
||||||
|
.reduce((pre, next) => ({ ...pre, ...next }), {})
|
||||||
|
}
|
||||||
|
if (contain_self) {
|
||||||
|
return {
|
||||||
|
...this.up_node_field_dict,
|
||||||
|
[this.props.model.id]: this.get_node_field_list()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.up_node_field_dict ? this.up_node_field_dict : {}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_up_node_field_list(contain_self: boolean, use_cache: boolean) {
|
||||||
|
const result = Object.values(this.get_up_node_field_dict(contain_self, use_cache)).reduce(
|
||||||
|
(pre, next) => [...pre, ...next],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const start_node_field_list = this.props.graphModel
|
||||||
|
.getNodeModelById('start-node')
|
||||||
|
.get_node_field_list()
|
||||||
|
return [...start_node_field_list, ...result]
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_next_node_field(contain_self: boolean) {
|
||||||
|
const next_node_list = this.props.graphModel.getNodeOutgoingNode(this.props.model.id)
|
||||||
|
next_node_list.forEach((node) => {
|
||||||
|
node.clear_next_node_field(true)
|
||||||
|
})
|
||||||
|
if (contain_self) {
|
||||||
|
this.up_node_field_dict = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getAnchorShape(anchorData: any) {
|
||||||
|
const { x, y, type } = anchorData
|
||||||
|
let isConnect = false
|
||||||
|
|
||||||
|
if (type == 'left') {
|
||||||
|
isConnect = this.props.graphModel.edges.some((edge) => edge.targetAnchorId == anchorData.id)
|
||||||
|
} else {
|
||||||
|
isConnect = this.props.graphModel.edges.some((edge) => edge.sourceAnchorId == anchorData.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lh(
|
||||||
|
'foreignObject',
|
||||||
|
{
|
||||||
|
...anchorData,
|
||||||
|
x: x - 10,
|
||||||
|
y: y - 12,
|
||||||
|
width: 30,
|
||||||
|
height: 30
|
||||||
|
},
|
||||||
|
[
|
||||||
|
lh('div', {
|
||||||
|
style: { zindex: 0 },
|
||||||
|
onClick: () => {
|
||||||
|
if (type == 'right') {
|
||||||
|
this.props.model.openNodeMenu(anchorData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dangerouslySetInnerHTML: {
|
||||||
|
__html: isConnect
|
||||||
|
? `<svg width="100%" height="100%" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d_5119_232585)">
|
||||||
|
<path d="M20.9998 29.8333C28.0875 29.8333 33.8332 24.0876 33.8332 17C33.8332 9.91231 28.0875 4.16663 20.9998 4.16663C13.9122 4.16663 8.1665 9.91231 8.1665 17C8.1665 24.0876 13.9122 29.8333 20.9998 29.8333Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.9998 27.5C26.7988 27.5 31.4998 22.799 31.4998 17C31.4998 11.201 26.7988 6.49996 20.9998 6.49996C15.2008 6.49996 10.4998 11.201 10.4998 17C10.4998 22.799 15.2008 27.5 20.9998 27.5ZM33.8332 17C33.8332 24.0876 28.0875 29.8333 20.9998 29.8333C13.9122 29.8333 8.1665 24.0876 8.1665 17C8.1665 9.91231 13.9122 4.16663 20.9998 4.16663C28.0875 4.16663 33.8332 9.91231 33.8332 17Z" fill="#3370FF"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_5119_232585" x="-1" y="-1" width="44" height="44" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="4"/>
|
||||||
|
<feGaussianBlur stdDeviation="4"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.2 0 0 0 0 0.439216 0 0 0 0 1 0 0 0 0.1 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5119_232585"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5119_232585" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
: `<svg width="100%" height="100%" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d_5199_166905)">
|
||||||
|
<path d="M20.9998 29.8333C28.0875 29.8333 33.8332 24.0876 33.8332 17C33.8332 9.91231 28.0875 4.16663 20.9998 4.16663C13.9122 4.16663 8.1665 9.91231 8.1665 17C8.1665 24.0876 13.9122 29.8333 20.9998 29.8333Z" fill="#3370FF"/>
|
||||||
|
<path d="M19.8332 11.75C19.8332 11.4278 20.0943 11.1666 20.4165 11.1666H21.5832C21.9053 11.1666 22.1665 11.4278 22.1665 11.75V15.8333H26.2498C26.572 15.8333 26.8332 16.0945 26.8332 16.4166V17.5833C26.8332 17.9055 26.572 18.1666 26.2498 18.1666H22.1665V22.25C22.1665 22.5721 21.9053 22.8333 21.5832 22.8333H20.4165C20.0943 22.8333 19.8332 22.5721 19.8332 22.25V18.1666H15.7498C15.4277 18.1666 15.1665 17.9055 15.1665 17.5833V16.4166C15.1665 16.0945 15.4277 15.8333 15.7498 15.8333H19.8332V11.75Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_5199_166905" x="-1" y="-1" width="44" height="44" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="4"/>
|
||||||
|
<feGaussianBlur stdDeviation="4"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.2 0 0 0 0 0.439216 0 0 0 0 1 0 0 0 0.1 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5199_166905"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5199_166905" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setHtml(rootEl: HTMLElement) {
|
||||||
|
if (!this.isMounted) {
|
||||||
|
this.isMounted = true
|
||||||
|
const node = document.createElement('div')
|
||||||
|
rootEl.appendChild(node)
|
||||||
|
this.renderVueComponent(node)
|
||||||
|
} else {
|
||||||
|
if (this.r && this.r.component) {
|
||||||
|
this.r.component.props.properties = this.props.model.getProperties()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
super.componentWillUnmount()
|
||||||
|
this.unmount()
|
||||||
|
}
|
||||||
|
getComponentContainer() {
|
||||||
|
return this.root
|
||||||
|
}
|
||||||
|
protected targetId() {
|
||||||
|
return `${this.props.graphModel.flowId}:${this.props.model.id}`
|
||||||
|
}
|
||||||
|
protected renderVueComponent(root: any) {
|
||||||
|
this.unmountVueComponent()
|
||||||
|
this.root = root
|
||||||
|
const { model, graphModel } = this.props
|
||||||
|
|
||||||
|
if (root) {
|
||||||
|
if (isActive()) {
|
||||||
|
connect(this.targetId(), this.component, root, model, graphModel)
|
||||||
|
} else {
|
||||||
|
this.r = h(this.component, {
|
||||||
|
properties: this.props.model.properties,
|
||||||
|
nodeModel: this.props.model
|
||||||
|
})
|
||||||
|
this.app = createApp({
|
||||||
|
render() {
|
||||||
|
return this.r
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
getNode: () => model,
|
||||||
|
getGraph: () => graphModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.app.use(ElementPlus, {
|
||||||
|
locale: zhCn
|
||||||
|
})
|
||||||
|
this.app.use(Components)
|
||||||
|
this.app.use(directives)
|
||||||
|
this.app.use(i18n)
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIcons)) {
|
||||||
|
this.app.component(key, component)
|
||||||
|
}
|
||||||
|
this.app?.mount(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected unmountVueComponent() {
|
||||||
|
if (this.app) {
|
||||||
|
this.app.unmount()
|
||||||
|
this.app = null
|
||||||
|
}
|
||||||
|
if (this.root) {
|
||||||
|
this.root.innerHTML = ''
|
||||||
|
}
|
||||||
|
return this.root
|
||||||
|
}
|
||||||
|
|
||||||
|
unmount() {
|
||||||
|
if (isActive()) {
|
||||||
|
disconnect(this.targetId())
|
||||||
|
}
|
||||||
|
this.unmountVueComponent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppNodeModel extends HtmlResize.model {
|
||||||
|
refreshDeges() {
|
||||||
|
// 更新节点连接边的path
|
||||||
|
this.incoming.edges.forEach((edge: any) => {
|
||||||
|
// 调用自定义的更新方案
|
||||||
|
edge.updatePathByAnchor()
|
||||||
|
})
|
||||||
|
this.outgoing.edges.forEach((edge: any) => {
|
||||||
|
edge.updatePathByAnchor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
set_position(position: { x?: number; y?: number }) {
|
||||||
|
const { x, y } = position
|
||||||
|
if (x) {
|
||||||
|
this.x = x
|
||||||
|
}
|
||||||
|
if (y) {
|
||||||
|
this.y = y
|
||||||
|
}
|
||||||
|
this.refreshDeges()
|
||||||
|
}
|
||||||
|
getResizeOutlineStyle() {
|
||||||
|
const style = super.getResizeOutlineStyle()
|
||||||
|
style.stroke = 'none'
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
getControlPointStyle() {
|
||||||
|
const style = super.getControlPointStyle()
|
||||||
|
style.stroke = 'none'
|
||||||
|
style.fill = 'none'
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
getNodeStyle() {
|
||||||
|
return {
|
||||||
|
overflow: 'visible'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getOutlineStyle() {
|
||||||
|
const style = super.getOutlineStyle()
|
||||||
|
style.stroke = 'none'
|
||||||
|
if (style.hover) {
|
||||||
|
style.hover.stroke = 'none'
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
// 如果不用修改锚地形状,可以重写颜色相关样式
|
||||||
|
getAnchorStyle(anchorInfo: any) {
|
||||||
|
const style = super.getAnchorStyle(anchorInfo)
|
||||||
|
if (anchorInfo.type === 'left') {
|
||||||
|
style.fill = 'red'
|
||||||
|
style.hover.fill = 'transparent'
|
||||||
|
style.hover.stroke = 'transpanrent'
|
||||||
|
style.className = 'lf-hide-default'
|
||||||
|
} else {
|
||||||
|
style.fill = 'green'
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
|
setHeight(height: number) {
|
||||||
|
const sourceHeight = this.height
|
||||||
|
const targetHeight = height + 100
|
||||||
|
this.height = targetHeight
|
||||||
|
this.properties['height'] = targetHeight
|
||||||
|
this.move(0, (targetHeight - sourceHeight) / 2)
|
||||||
|
this.outgoing.edges.forEach((edge: any) => {
|
||||||
|
// 调用自定义的更新方案
|
||||||
|
edge.updatePathByAnchor()
|
||||||
|
})
|
||||||
|
this.incoming.edges.forEach((edge: any) => {
|
||||||
|
// 调用自定义的更新方案
|
||||||
|
edge.updatePathByAnchor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
get_width() {
|
||||||
|
return this.properties?.width || 340
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttributes() {
|
||||||
|
const { t } = i18n.global
|
||||||
|
this.width = this.get_width()
|
||||||
|
const isLoop = (node_id: string, target_node_id: string) => {
|
||||||
|
const up_node_list = this.graphModel.getNodeIncomingNode(node_id)
|
||||||
|
for (const index in up_node_list) {
|
||||||
|
const item = up_node_list[index]
|
||||||
|
if (item.id === target_node_id) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
const result = isLoop(item.id, target_node_id)
|
||||||
|
if (result) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const circleOnlyAsTarget = {
|
||||||
|
message: t('views.applicationWorkflow.tip.onlyRight'),
|
||||||
|
validate: (sourceNode: any, targetNode: any, sourceAnchor: any) => {
|
||||||
|
return sourceAnchor.type === 'right'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.sourceRules.push({
|
||||||
|
message: t('views.applicationWorkflow.tip.notRecyclable'),
|
||||||
|
validate: (sourceNode: any, targetNode: any, sourceAnchor: any, targetAnchor: any) => {
|
||||||
|
return !isLoop(sourceNode.id, targetNode.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.sourceRules.push(circleOnlyAsTarget)
|
||||||
|
this.targetRules.push({
|
||||||
|
message: t('views.applicationWorkflow.tip.onlyLeft'),
|
||||||
|
validate: (sourceNode: any, targetNode: any, sourceAnchor: any, targetAnchor: any) => {
|
||||||
|
return targetAnchor.type === 'left'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getDefaultAnchor() {
|
||||||
|
const { id, x, y, width } = this
|
||||||
|
const showNode = this.properties.showNode === undefined ? true : this.properties.showNode
|
||||||
|
const anchors: any = []
|
||||||
|
|
||||||
|
if (this.type !== WorkflowType.Base) {
|
||||||
|
if (this.type !== WorkflowType.Start) {
|
||||||
|
anchors.push({
|
||||||
|
x: x - width / 2 + 10,
|
||||||
|
y: showNode ? y : y - 15,
|
||||||
|
id: `${id}_left`,
|
||||||
|
edgeAddable: false,
|
||||||
|
type: 'left'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
anchors.push({
|
||||||
|
x: x + width / 2 - 10,
|
||||||
|
y: showNode ? y : y - 15,
|
||||||
|
id: `${id}_right`,
|
||||||
|
type: 'right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { AppNodeModel, AppNode }
|
||||||
|
|
@ -0,0 +1,465 @@
|
||||||
|
import { WorkflowType } from '@/enums/workflow'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
export const startNode = {
|
||||||
|
id: WorkflowType.Start,
|
||||||
|
type: WorkflowType.Start,
|
||||||
|
x: 480,
|
||||||
|
y: 3340,
|
||||||
|
properties: {
|
||||||
|
height: 364,
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.startNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.startNode.question'),
|
||||||
|
value: 'question'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
globalFields: [
|
||||||
|
{ label: t('views.applicationWorkflow.nodes.startNode.currentTime'), value: 'time' },
|
||||||
|
{
|
||||||
|
label: t('views.application.applicationForm.form.historyRecord.label'),
|
||||||
|
value: 'history_context'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('chat.chatId'),
|
||||||
|
value: 'chat_id'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
fields: [{ label: t('views.applicationWorkflow.nodes.startNode.question'), value: 'question' }],
|
||||||
|
globalFields: [
|
||||||
|
{ label: t('views.applicationWorkflow.nodes.startNode.currentTime'), value: 'time' }
|
||||||
|
],
|
||||||
|
showNode: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const baseNode = {
|
||||||
|
id: WorkflowType.Base,
|
||||||
|
type: WorkflowType.Base,
|
||||||
|
x: 360,
|
||||||
|
y: 2761.3875,
|
||||||
|
text: '',
|
||||||
|
properties: {
|
||||||
|
height: 728.375,
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.baseNode.label'),
|
||||||
|
input_field_list: [],
|
||||||
|
node_data: {
|
||||||
|
name: '',
|
||||||
|
desc: '',
|
||||||
|
// @ts-ignore
|
||||||
|
prologue: t('views.application.applicationForm.form.defaultPrologue'),
|
||||||
|
tts_type: 'BROWSER'
|
||||||
|
},
|
||||||
|
config: {},
|
||||||
|
showNode: true,
|
||||||
|
user_input_config: { title: t('chat.userInput') },
|
||||||
|
user_input_field_list: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 说明
|
||||||
|
* type 与 nodes 文件对应
|
||||||
|
*/
|
||||||
|
export const baseNodes = [baseNode, startNode]
|
||||||
|
/**
|
||||||
|
* ai对话节点配置数据
|
||||||
|
*/
|
||||||
|
export const aiChatNode = {
|
||||||
|
type: WorkflowType.AiChat,
|
||||||
|
text: t('views.applicationWorkflow.nodes.aiChatNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.aiChatNode.label'),
|
||||||
|
height: 340,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.aiChatNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.aiChatNode.answer'),
|
||||||
|
value: 'answer'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.aiChatNode.think'),
|
||||||
|
value: 'reasoning_content'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 知识库检索配置数据
|
||||||
|
*/
|
||||||
|
export const searchDatasetNode = {
|
||||||
|
type: WorkflowType.SearchDataset,
|
||||||
|
text: t('views.applicationWorkflow.nodes.searchDatasetNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.searchDatasetNode.label'),
|
||||||
|
height: 355,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.searchDatasetNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.searchDatasetNode.paragraph_list'),
|
||||||
|
value: 'paragraph_list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.searchDatasetNode.is_hit_handling_method_list'),
|
||||||
|
value: 'is_hit_handling_method_list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.searchDatasetNode.result'),
|
||||||
|
value: 'data'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.searchDatasetNode.directly_return'),
|
||||||
|
value: 'directly_return'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const questionNode = {
|
||||||
|
type: WorkflowType.Question,
|
||||||
|
text: t('views.applicationWorkflow.nodes.questionNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.questionNode.label'),
|
||||||
|
height: 345,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.questionNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.questionNode.result'),
|
||||||
|
value: 'answer'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const conditionNode = {
|
||||||
|
type: WorkflowType.Condition,
|
||||||
|
text: t('views.applicationWorkflow.nodes.conditionNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.conditionNode.label'),
|
||||||
|
height: 175,
|
||||||
|
properties: {
|
||||||
|
width: 600,
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.conditionNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.conditionNode.branch_name'),
|
||||||
|
value: 'branch_name'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const replyNode = {
|
||||||
|
type: WorkflowType.Reply,
|
||||||
|
text: t('views.applicationWorkflow.nodes.replyNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.replyNode.label'),
|
||||||
|
height: 210,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.replyNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.replyNode.content'),
|
||||||
|
value: 'answer'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const rerankerNode = {
|
||||||
|
type: WorkflowType.RrerankerNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.rerankerNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.rerankerNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.rerankerNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.rerankerNode.result_list'),
|
||||||
|
value: 'result_list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.rerankerNode.result'),
|
||||||
|
value: 'result'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const formNode = {
|
||||||
|
type: WorkflowType.FormNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.formNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.formNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
width: 600,
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.formNode.label'),
|
||||||
|
node_data: {
|
||||||
|
is_result: true,
|
||||||
|
form_field_list: [],
|
||||||
|
form_content_format: `${t('views.applicationWorkflow.nodes.formNode.form_content_format1')}
|
||||||
|
{{form}}
|
||||||
|
${t('views.applicationWorkflow.nodes.formNode.form_content_format2')}`
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.formNode.form_data'),
|
||||||
|
value: 'form_data'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const documentExtractNode = {
|
||||||
|
type: WorkflowType.DocumentExtractNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.documentExtractNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.documentExtractNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.documentExtractNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.documentExtractNode.content'),
|
||||||
|
value: 'content'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const imageUnderstandNode = {
|
||||||
|
type: WorkflowType.ImageUnderstandNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.imageUnderstandNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.imageUnderstandNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.imageUnderstandNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.imageUnderstandNode.answer'),
|
||||||
|
value: 'answer'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const variableAssignNode = {
|
||||||
|
type: WorkflowType.VariableAssignNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.variableAssignNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.variableAssignNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.variableAssignNode.label'),
|
||||||
|
config: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mcpNode = {
|
||||||
|
type: WorkflowType.McpNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.mcpNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.mcpNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.mcpNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('common.result'),
|
||||||
|
value: 'result'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const imageGenerateNode = {
|
||||||
|
type: WorkflowType.ImageGenerateNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.imageGenerateNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.imageGenerateNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.imageGenerateNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.imageGenerateNode.answer'),
|
||||||
|
value: 'answer'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('common.fileUpload.image'),
|
||||||
|
value: 'image'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const speechToTextNode = {
|
||||||
|
type: WorkflowType.SpeechToTextNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.speechToTextNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.speechToTextNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.speechToTextNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('common.result'),
|
||||||
|
value: 'result'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const textToSpeechNode = {
|
||||||
|
type: WorkflowType.TextToSpeechNode,
|
||||||
|
text: t('views.applicationWorkflow.nodes.textToSpeechNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.textToSpeechNode.label'),
|
||||||
|
height: 252,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.textToSpeechNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('common.result'),
|
||||||
|
value: 'result'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const menuNodes = [
|
||||||
|
aiChatNode,
|
||||||
|
imageUnderstandNode,
|
||||||
|
imageGenerateNode,
|
||||||
|
searchDatasetNode,
|
||||||
|
rerankerNode,
|
||||||
|
conditionNode,
|
||||||
|
replyNode,
|
||||||
|
formNode,
|
||||||
|
questionNode,
|
||||||
|
documentExtractNode,
|
||||||
|
speechToTextNode,
|
||||||
|
textToSpeechNode,
|
||||||
|
variableAssignNode,
|
||||||
|
mcpNode
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义函数配置数据
|
||||||
|
*/
|
||||||
|
export const functionNode = {
|
||||||
|
type: WorkflowType.FunctionLibCustom,
|
||||||
|
text: t('views.applicationWorkflow.nodes.functionNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.functionNode.label'),
|
||||||
|
height: 260,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.functionNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('common.result'),
|
||||||
|
value: 'result'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const functionLibNode = {
|
||||||
|
type: WorkflowType.FunctionLib,
|
||||||
|
text: t('views.applicationWorkflow.nodes.functionNode.text'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.functionNode.label'),
|
||||||
|
height: 170,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.functionNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('common.result'),
|
||||||
|
value: 'result'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const applicationNode = {
|
||||||
|
type: WorkflowType.Application,
|
||||||
|
text: t('views.applicationWorkflow.nodes.applicationNode.label'),
|
||||||
|
label: t('views.applicationWorkflow.nodes.applicationNode.label'),
|
||||||
|
height: 260,
|
||||||
|
properties: {
|
||||||
|
stepName: t('views.applicationWorkflow.nodes.applicationNode.label'),
|
||||||
|
config: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: t('common.result'),
|
||||||
|
value: 'result'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const compareList = [
|
||||||
|
{ value: 'is_null', label: t('views.applicationWorkflow.compare.is_null') },
|
||||||
|
{ value: 'is_not_null', label: t('views.applicationWorkflow.compare.is_not_null') },
|
||||||
|
{ value: 'contain', label: t('views.applicationWorkflow.compare.contain') },
|
||||||
|
{ value: 'not_contain', label: t('views.applicationWorkflow.compare.not_contain') },
|
||||||
|
{ value: 'eq', label: t('views.applicationWorkflow.compare.eq') },
|
||||||
|
{ value: 'ge', label: t('views.applicationWorkflow.compare.ge') },
|
||||||
|
{ value: 'gt', label: t('views.applicationWorkflow.compare.gt') },
|
||||||
|
{ value: 'le', label: t('views.applicationWorkflow.compare.le') },
|
||||||
|
{ value: 'lt', label: t('views.applicationWorkflow.compare.lt') },
|
||||||
|
{ value: 'len_eq', label: t('views.applicationWorkflow.compare.len_eq') },
|
||||||
|
{ value: 'len_ge', label: t('views.applicationWorkflow.compare.len_ge') },
|
||||||
|
{ value: 'len_gt', label: t('views.applicationWorkflow.compare.len_gt') },
|
||||||
|
{ value: 'len_le', label: t('views.applicationWorkflow.compare.len_le') },
|
||||||
|
{ value: 'len_lt', label: t('views.applicationWorkflow.compare.len_lt') },
|
||||||
|
{ value: 'is_true', label: t('views.applicationWorkflow.compare.is_true') },
|
||||||
|
{ value: 'is_not_true', label: t('views.applicationWorkflow.compare.is_not_true') }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const nodeDict: any = {
|
||||||
|
[WorkflowType.AiChat]: aiChatNode,
|
||||||
|
[WorkflowType.SearchDataset]: searchDatasetNode,
|
||||||
|
[WorkflowType.Question]: questionNode,
|
||||||
|
[WorkflowType.Condition]: conditionNode,
|
||||||
|
[WorkflowType.Base]: baseNode,
|
||||||
|
[WorkflowType.Start]: startNode,
|
||||||
|
[WorkflowType.Reply]: replyNode,
|
||||||
|
[WorkflowType.FunctionLib]: functionLibNode,
|
||||||
|
[WorkflowType.FunctionLibCustom]: functionNode,
|
||||||
|
[WorkflowType.RrerankerNode]: rerankerNode,
|
||||||
|
[WorkflowType.FormNode]: formNode,
|
||||||
|
[WorkflowType.Application]: applicationNode,
|
||||||
|
[WorkflowType.DocumentExtractNode]: documentExtractNode,
|
||||||
|
[WorkflowType.ImageUnderstandNode]: imageUnderstandNode,
|
||||||
|
[WorkflowType.TextToSpeechNode]: textToSpeechNode,
|
||||||
|
[WorkflowType.SpeechToTextNode]: speechToTextNode,
|
||||||
|
[WorkflowType.ImageGenerateNode]: imageGenerateNode,
|
||||||
|
[WorkflowType.VariableAssignNode]: variableAssignNode,
|
||||||
|
[WorkflowType.McpNode]: mcpNode
|
||||||
|
}
|
||||||
|
export function isWorkFlow(type: string | undefined) {
|
||||||
|
return type === 'WORK_FLOW'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLastNode(nodeModel: any) {
|
||||||
|
const incoming = nodeModel.graphModel.getNodeIncomingNode(nodeModel.id)
|
||||||
|
const outcomming = nodeModel.graphModel.getNodeOutgoingNode(nodeModel.id)
|
||||||
|
if (incoming.length > 0 && outcomming.length === 0) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
import { BezierEdge, BezierEdgeModel, h } from '@logicflow/core'
|
||||||
|
import { createApp, h as vh } from 'vue'
|
||||||
|
import { isActive, connect, disconnect } from './teleport'
|
||||||
|
import CustomLine from './CustomLine.vue'
|
||||||
|
function isMouseInElement(element: any, e: any) {
|
||||||
|
const rect = element.getBoundingClientRect()
|
||||||
|
return (
|
||||||
|
e.clientX >= rect.left &&
|
||||||
|
e.clientX <= rect.right &&
|
||||||
|
e.clientY >= rect.top &&
|
||||||
|
e.clientY <= rect.bottom
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const DEFAULT_WIDTH = 32
|
||||||
|
const DEFAULT_HEIGHT = 32
|
||||||
|
class CustomEdge2 extends BezierEdge {
|
||||||
|
isMounted
|
||||||
|
customLineApp?: any
|
||||||
|
root?: any
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.isMounted = false
|
||||||
|
this.handleMouseUp = (e: any) => {
|
||||||
|
this.props.graphModel.clearSelectElements()
|
||||||
|
this.props.model.isSelected = true
|
||||||
|
const element = e.target.parentNode.parentNode.querySelector('.lf-custom-edge-wrapper')
|
||||||
|
if (isMouseInElement(element, e)) {
|
||||||
|
this.props.model.graphModel.deleteEdgeById(this.props.model.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 渲染vue组件
|
||||||
|
* @param root
|
||||||
|
*/
|
||||||
|
protected renderVueComponent(root: any) {
|
||||||
|
this.unmountVueComponent()
|
||||||
|
this.root = root
|
||||||
|
const { graphModel } = this.props
|
||||||
|
if (root) {
|
||||||
|
if (isActive()) {
|
||||||
|
connect(
|
||||||
|
this.targetId(),
|
||||||
|
CustomLine,
|
||||||
|
root,
|
||||||
|
this.props.model,
|
||||||
|
graphModel,
|
||||||
|
(node: any, graph: any) => {
|
||||||
|
return { model: node, graph }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.customLineApp = createApp({
|
||||||
|
render: () => vh(CustomLine, { model: this.props.model })
|
||||||
|
})
|
||||||
|
this.customLineApp?.mount(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected targetId() {
|
||||||
|
return `${this.props.graphModel.flowId}:${this.props.model.id}`
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 组件即将卸载勾子
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (super.componentWillUnmount) {
|
||||||
|
super.componentWillUnmount()
|
||||||
|
}
|
||||||
|
if (isActive()) {
|
||||||
|
disconnect(this.targetId())
|
||||||
|
}
|
||||||
|
this.unmountVueComponent()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 卸载vue
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected unmountVueComponent() {
|
||||||
|
if (this.customLineApp) {
|
||||||
|
this.customLineApp.unmount()
|
||||||
|
this.customLineApp = null
|
||||||
|
}
|
||||||
|
if (this.root) {
|
||||||
|
this.root.innerHTML = ''
|
||||||
|
}
|
||||||
|
return this.root
|
||||||
|
}
|
||||||
|
|
||||||
|
getEdge() {
|
||||||
|
const { model } = this.props
|
||||||
|
const id = model.id
|
||||||
|
const { customWidth = DEFAULT_WIDTH, customHeight = DEFAULT_HEIGHT } = model.getProperties()
|
||||||
|
const { startPoint, endPoint, path, isAnimation, arrowConfig } = model
|
||||||
|
const animationStyle = model.getEdgeAnimationStyle()
|
||||||
|
const {
|
||||||
|
strokeDasharray,
|
||||||
|
stroke,
|
||||||
|
strokeDashoffset,
|
||||||
|
animationName,
|
||||||
|
animationDuration,
|
||||||
|
animationIterationCount,
|
||||||
|
animationTimingFunction,
|
||||||
|
animationDirection
|
||||||
|
} = animationStyle
|
||||||
|
const positionData = {
|
||||||
|
x: (startPoint.x + endPoint.x - customWidth) / 2,
|
||||||
|
y: (startPoint.y + endPoint.y - customHeight) / 2,
|
||||||
|
width: customWidth,
|
||||||
|
height: customHeight
|
||||||
|
}
|
||||||
|
const style = model.getEdgeStyle()
|
||||||
|
const wrapperStyle = {
|
||||||
|
width: customWidth,
|
||||||
|
height: customHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const s = document.getElementById(id)
|
||||||
|
if (s && !this.isMounted) {
|
||||||
|
this.isMounted = true
|
||||||
|
this.renderVueComponent(s)
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
delete style.stroke
|
||||||
|
|
||||||
|
return h('g', {}, [
|
||||||
|
h(
|
||||||
|
'style' as any,
|
||||||
|
{ type: 'text/css' },
|
||||||
|
'.lf-edge{stroke:#afafaf}.lf-edge:hover{stroke: #3370FF;}'
|
||||||
|
),
|
||||||
|
h('path', {
|
||||||
|
d: path,
|
||||||
|
...style,
|
||||||
|
...arrowConfig,
|
||||||
|
...(isAnimation
|
||||||
|
? {
|
||||||
|
strokeDasharray,
|
||||||
|
stroke,
|
||||||
|
style: {
|
||||||
|
strokeDashoffset,
|
||||||
|
animationName,
|
||||||
|
animationDuration,
|
||||||
|
animationIterationCount,
|
||||||
|
animationTimingFunction,
|
||||||
|
animationDirection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}),
|
||||||
|
h(
|
||||||
|
'foreignObject',
|
||||||
|
{
|
||||||
|
...positionData,
|
||||||
|
y: positionData.y + 5,
|
||||||
|
x: positionData.x + 5,
|
||||||
|
style: {}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('div', {
|
||||||
|
id,
|
||||||
|
style: { ...wrapperStyle },
|
||||||
|
className: 'lf-custom-edge-wrapper'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomEdgeModel2 extends BezierEdgeModel {
|
||||||
|
getArrowStyle() {
|
||||||
|
const arrowStyle = super.getArrowStyle()
|
||||||
|
arrowStyle.offset = 1
|
||||||
|
arrowStyle.verticalLength = 0
|
||||||
|
return arrowStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
getEdgeStyle() {
|
||||||
|
const style = super.getEdgeStyle()
|
||||||
|
// svg属性
|
||||||
|
style.strokeWidth = 2
|
||||||
|
style.stroke = '#BBBFC4'
|
||||||
|
style.offset = 0
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 重写此方法,使保存数据是能带上锚点数据。
|
||||||
|
*/
|
||||||
|
getData() {
|
||||||
|
const data: any = super.getData()
|
||||||
|
if (data) {
|
||||||
|
data.sourceAnchorId = this.sourceAnchorId
|
||||||
|
data.targetAnchorId = this.targetAnchorId
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 给边自定义方案,使其支持基于锚点的位置更新边的路径
|
||||||
|
*/
|
||||||
|
updatePathByAnchor() {
|
||||||
|
// TODO
|
||||||
|
const sourceNodeModel = this.graphModel.getNodeModelById(this.sourceNodeId)
|
||||||
|
const sourceAnchor = sourceNodeModel
|
||||||
|
.getDefaultAnchor()
|
||||||
|
.find((anchor: any) => anchor.id === this.sourceAnchorId)
|
||||||
|
|
||||||
|
const targetNodeModel = this.graphModel.getNodeModelById(this.targetNodeId)
|
||||||
|
const targetAnchor = targetNodeModel
|
||||||
|
.getDefaultAnchor()
|
||||||
|
.find((anchor: any) => anchor.id === this.targetAnchorId)
|
||||||
|
if (sourceAnchor && targetAnchor) {
|
||||||
|
const startPoint = {
|
||||||
|
x: sourceAnchor.x,
|
||||||
|
y: sourceAnchor.y
|
||||||
|
}
|
||||||
|
this.updateStartPoint(startPoint)
|
||||||
|
const endPoint = {
|
||||||
|
x: targetAnchor.x,
|
||||||
|
y: targetAnchor.y
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateEndPoint(endPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里需要将原有的pointsList设置为空,才能触发bezier的自动计算control点。
|
||||||
|
this.pointsList = []
|
||||||
|
this.initPoints()
|
||||||
|
}
|
||||||
|
setAttributes(): void {
|
||||||
|
super.setAttributes()
|
||||||
|
this.isHitable = true
|
||||||
|
this.zIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
type: 'app-edge',
|
||||||
|
view: CustomEdge2,
|
||||||
|
model: CustomEdgeModel2
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
import type LogicFlow from '@logicflow/core'
|
||||||
|
import { type GraphModel } from '@logicflow/core'
|
||||||
|
import { MsgSuccess, MsgError, MsgConfirm } from '@/utils/message'
|
||||||
|
import { WorkflowType } from '@/enums/workflow'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
let selected: any | null = null
|
||||||
|
|
||||||
|
function translationNodeData(nodeData: any, distance: any) {
|
||||||
|
nodeData.x += distance
|
||||||
|
nodeData.y += distance
|
||||||
|
if (nodeData.text) {
|
||||||
|
nodeData.text.x += distance
|
||||||
|
nodeData.text.y += distance
|
||||||
|
}
|
||||||
|
return nodeData
|
||||||
|
}
|
||||||
|
|
||||||
|
function translationEdgeData(edgeData: any, distance: any) {
|
||||||
|
if (edgeData.startPoint) {
|
||||||
|
edgeData.startPoint.x += distance
|
||||||
|
edgeData.startPoint.y += distance
|
||||||
|
}
|
||||||
|
if (edgeData.endPoint) {
|
||||||
|
edgeData.endPoint.x += distance
|
||||||
|
edgeData.endPoint.y += distance
|
||||||
|
}
|
||||||
|
if (edgeData.pointsList && edgeData.pointsList.length > 0) {
|
||||||
|
edgeData.pointsList.forEach((point: any) => {
|
||||||
|
point.x += distance
|
||||||
|
point.y += distance
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (edgeData.text) {
|
||||||
|
edgeData.text.x += distance
|
||||||
|
edgeData.text.y += distance
|
||||||
|
}
|
||||||
|
return edgeData
|
||||||
|
}
|
||||||
|
|
||||||
|
const TRANSLATION_DISTANCE = 40
|
||||||
|
let CHILDREN_TRANSLATION_DISTANCE = 40
|
||||||
|
|
||||||
|
export function initDefaultShortcut(lf: LogicFlow, graph: GraphModel) {
|
||||||
|
const { keyboard } = lf
|
||||||
|
const {
|
||||||
|
options: { keyboard: keyboardOptions }
|
||||||
|
} = keyboard
|
||||||
|
const copy_node = () => {
|
||||||
|
CHILDREN_TRANSLATION_DISTANCE = TRANSLATION_DISTANCE
|
||||||
|
if (!keyboardOptions?.enabled) return true
|
||||||
|
if (graph.textEditElement) return true
|
||||||
|
const { guards } = lf.options
|
||||||
|
const elements = graph.getSelectElements(false)
|
||||||
|
const enabledClone = guards && guards.beforeClone ? guards.beforeClone(elements) : true
|
||||||
|
if (!enabledClone || (elements.nodes.length === 0 && elements.edges.length === 0)) {
|
||||||
|
selected = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const base_nodes = elements.nodes.filter(
|
||||||
|
(node: any) => node.type === WorkflowType.Start || node.type === WorkflowType.Base
|
||||||
|
)
|
||||||
|
if (base_nodes.length > 0) {
|
||||||
|
MsgError(base_nodes[0]?.properties?.stepName + t('views.applicationWorkflow.tip.cannotCopy'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selected = elements
|
||||||
|
selected.nodes.forEach((node: any) => translationNodeData(node, TRANSLATION_DISTANCE))
|
||||||
|
selected.edges.forEach((edge: any) => translationEdgeData(edge, TRANSLATION_DISTANCE))
|
||||||
|
MsgSuccess(t('views.applicationWorkflow.tip.copyError'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const paste_node = () => {
|
||||||
|
if (!keyboardOptions?.enabled) return true
|
||||||
|
if (graph.textEditElement) return true
|
||||||
|
if (selected && (selected.nodes || selected.edges)) {
|
||||||
|
lf.clearSelectElements()
|
||||||
|
const addElements = lf.addElements(selected, CHILDREN_TRANSLATION_DISTANCE)
|
||||||
|
if (!addElements) return true
|
||||||
|
addElements.nodes.forEach((node) => lf.selectElementById(node.id, true))
|
||||||
|
addElements.edges.forEach((edge) => lf.selectElementById(edge.id, true))
|
||||||
|
selected.nodes.forEach((node: any) => translationNodeData(node, TRANSLATION_DISTANCE))
|
||||||
|
selected.edges.forEach((edge: any) => translationEdgeData(edge, TRANSLATION_DISTANCE))
|
||||||
|
CHILDREN_TRANSLATION_DISTANCE = CHILDREN_TRANSLATION_DISTANCE + TRANSLATION_DISTANCE
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const delete_node = () => {
|
||||||
|
const elements = graph.getSelectElements(true)
|
||||||
|
lf.clearSelectElements()
|
||||||
|
if (elements.nodes.length == 0 && elements.edges.length == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (elements.edges.length > 0 && elements.nodes.length == 0) {
|
||||||
|
elements.edges.forEach((edge: any) => lf.deleteEdge(edge.id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const nodes = elements.nodes.filter((node) => ['start-node', 'base-node'].includes(node.type))
|
||||||
|
if (nodes.length > 0) {
|
||||||
|
MsgError(`${nodes[0].properties?.stepName}${t('views.applicationWorkflow.delete.deleteMessage')}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
MsgConfirm(t('common.tip'), t('views.applicationWorkflow.delete.confirmTitle'), {
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
confirmButtonClass: 'danger'
|
||||||
|
}).then(() => {
|
||||||
|
if (!keyboardOptions?.enabled) return true
|
||||||
|
if (graph.textEditElement) return true
|
||||||
|
|
||||||
|
elements.edges.forEach((edge: any) => lf.deleteEdge(edge.id))
|
||||||
|
elements.nodes.forEach((node: any) => lf.deleteNode(node.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
graph.eventCenter.on('copy_node', copy_node)
|
||||||
|
// 复制
|
||||||
|
keyboard.on(['cmd + c', 'ctrl + c'], copy_node)
|
||||||
|
// 粘贴
|
||||||
|
keyboard.on(['cmd + v', 'ctrl + v'], paste_node)
|
||||||
|
// undo
|
||||||
|
keyboard.on(['cmd + z', 'ctrl + z'], () => {
|
||||||
|
// if (!keyboardOptions?.enabled) return true
|
||||||
|
// if (graph.textEditElement) return true
|
||||||
|
// lf.undo()
|
||||||
|
// return false
|
||||||
|
})
|
||||||
|
// redo
|
||||||
|
keyboard.on(['cmd + y', 'ctrl + y'], () => {
|
||||||
|
if (!keyboardOptions?.enabled) return true
|
||||||
|
if (graph.textEditElement) return true
|
||||||
|
lf.redo()
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
// delete
|
||||||
|
keyboard.on(['backspace'], delete_node)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { BaseEdgeModel, BaseNodeModel, GraphModel } from '@logicflow/core'
|
||||||
|
import { defineComponent, h, reactive, isVue3, Teleport, markRaw, Fragment } from 'vue-demi'
|
||||||
|
|
||||||
|
let active = false
|
||||||
|
const items = reactive<{ [key: string]: any }>({})
|
||||||
|
|
||||||
|
export function connect(
|
||||||
|
id: string,
|
||||||
|
component: any,
|
||||||
|
container: HTMLDivElement,
|
||||||
|
node: BaseNodeModel | BaseEdgeModel,
|
||||||
|
graph: GraphModel,
|
||||||
|
get_props?: any
|
||||||
|
) {
|
||||||
|
if (!get_props) {
|
||||||
|
get_props = (node: BaseNodeModel | BaseEdgeModel, graph: GraphModel) => {
|
||||||
|
return { nodeModel: node, graph }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (active) {
|
||||||
|
items[id] = markRaw(
|
||||||
|
defineComponent({
|
||||||
|
render: () => h(Teleport, { to: container } as any, [h(component, get_props(node, graph))]),
|
||||||
|
provide: () => ({
|
||||||
|
getNode: () => node,
|
||||||
|
getGraph: () => graph
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disconnect(id: string) {
|
||||||
|
if (active) {
|
||||||
|
delete items[id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isActive() {
|
||||||
|
return active
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTeleport(): any {
|
||||||
|
if (!isVue3) {
|
||||||
|
throw new Error('teleport is only available in Vue3')
|
||||||
|
}
|
||||||
|
active = true
|
||||||
|
|
||||||
|
return defineComponent({
|
||||||
|
props: {
|
||||||
|
flowId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => {
|
||||||
|
const children: Record<string, any>[] = []
|
||||||
|
Object.keys(items).forEach((id) => {
|
||||||
|
// https://github.com/didi/LogicFlow/issues/1768
|
||||||
|
// 多个不同的VueNodeView都会connect注册到items中,因此items存储了可能有多个flowId流程图的数据
|
||||||
|
// 当使用多个LogicFlow时,会创建多个flowId + 同时使用KeepAlive
|
||||||
|
// 每一次items改变,会触发不同flowId持有的setup()执行,由于每次setup()执行就是遍历items,因此存在多次重复渲染元素的问题
|
||||||
|
// 即items[0]会在Page1的setup()执行,items[0]也会在Page2的setup()执行,从而生成两个items[0]
|
||||||
|
|
||||||
|
// 比对当前界面显示的flowId,只更新items[当前页面flowId:nodeId]的数据
|
||||||
|
// 比如items[0]属于Page1的数据,那么Page2无论active=true/false,都无法执行items[0]
|
||||||
|
if (id.startsWith(props.flowId)) {
|
||||||
|
children.push(items[id])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return h(
|
||||||
|
Fragment,
|
||||||
|
{},
|
||||||
|
children.map((item) => h(item))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
import { WorkflowType } from '@/enums/workflow'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
const end_nodes: Array<string> = [
|
||||||
|
WorkflowType.AiChat,
|
||||||
|
WorkflowType.Reply,
|
||||||
|
WorkflowType.FunctionLib,
|
||||||
|
WorkflowType.FunctionLibCustom,
|
||||||
|
WorkflowType.ImageUnderstandNode,
|
||||||
|
WorkflowType.Application,
|
||||||
|
WorkflowType.SpeechToTextNode,
|
||||||
|
WorkflowType.TextToSpeechNode,
|
||||||
|
WorkflowType.ImageGenerateNode,
|
||||||
|
]
|
||||||
|
export class WorkFlowInstance {
|
||||||
|
nodes
|
||||||
|
edges
|
||||||
|
workFlowNodes: Array<any>
|
||||||
|
constructor(workflow: { nodes: Array<any>; edges: Array<any> }) {
|
||||||
|
this.nodes = workflow.nodes
|
||||||
|
this.edges = workflow.edges
|
||||||
|
this.workFlowNodes = []
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 校验开始节点
|
||||||
|
*/
|
||||||
|
private is_valid_start_node() {
|
||||||
|
const start_node_list = this.nodes.filter((item) => item.id === WorkflowType.Start)
|
||||||
|
if (start_node_list.length == 0) {
|
||||||
|
throw t('views.applicationWorkflow.validate.startNodeRequired')
|
||||||
|
} else if (start_node_list.length > 1) {
|
||||||
|
throw t('views.applicationWorkflow.validate.startNodeOnly')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 校验基本信息节点
|
||||||
|
*/
|
||||||
|
private is_valid_base_node() {
|
||||||
|
const start_node_list = this.nodes.filter((item) => item.id === WorkflowType.Base)
|
||||||
|
if (start_node_list.length == 0) {
|
||||||
|
throw t('views.applicationWorkflow.validate.baseNodeRequired')
|
||||||
|
} else if (start_node_list.length > 1) {
|
||||||
|
throw t('views.applicationWorkflow.validate.baseNodeOnly')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 校验节点
|
||||||
|
*/
|
||||||
|
is_valid() {
|
||||||
|
this.is_valid_start_node()
|
||||||
|
this.is_valid_base_node()
|
||||||
|
this.is_valid_work_flow()
|
||||||
|
this.is_valid_nodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取开始节点
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
get_start_node() {
|
||||||
|
const start_node_list = this.nodes.filter((item) => item.id === WorkflowType.Start)
|
||||||
|
return start_node_list[0]
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取基本节点
|
||||||
|
* @returns 基本节点
|
||||||
|
*/
|
||||||
|
get_base_node() {
|
||||||
|
const base_node_list = this.nodes.filter((item) => item.id === WorkflowType.Base)
|
||||||
|
return base_node_list[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验工作流
|
||||||
|
* @param up_node 上一个节点
|
||||||
|
*/
|
||||||
|
private _is_valid_work_flow(up_node?: any) {
|
||||||
|
if (!up_node) {
|
||||||
|
up_node = this.get_start_node()
|
||||||
|
}
|
||||||
|
this.workFlowNodes.push(up_node)
|
||||||
|
this.is_valid_node(up_node)
|
||||||
|
const next_nodes = this.get_next_nodes(up_node)
|
||||||
|
for (const next_node of next_nodes) {
|
||||||
|
this._is_valid_work_flow(next_node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private is_valid_work_flow() {
|
||||||
|
this.workFlowNodes = []
|
||||||
|
this._is_valid_work_flow()
|
||||||
|
const notInWorkFlowNodes = this.nodes
|
||||||
|
.filter((node: any) => node.id !== WorkflowType.Start && node.id !== WorkflowType.Base)
|
||||||
|
.filter((node) => !this.workFlowNodes.includes(node))
|
||||||
|
if (notInWorkFlowNodes.length > 0) {
|
||||||
|
throw `${t('views.applicationWorkflow.validate.notInWorkFlowNode')}:${notInWorkFlowNodes.map((node) => node.properties.stepName).join(',')}`
|
||||||
|
}
|
||||||
|
this.workFlowNodes = []
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取流程下一个节点列表
|
||||||
|
* @param node 节点
|
||||||
|
* @returns 节点列表
|
||||||
|
*/
|
||||||
|
private get_next_nodes(node: any) {
|
||||||
|
const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id)
|
||||||
|
const node_list = edge_list
|
||||||
|
.map((edge) => this.nodes.filter((node) => node.id == edge.targetNodeId))
|
||||||
|
.reduce((x, y) => [...x, ...y], [])
|
||||||
|
if (node_list.length == 0 && !end_nodes.includes(node.type)) {
|
||||||
|
throw t('views.applicationWorkflow.validate.noNextNode')
|
||||||
|
}
|
||||||
|
return node_list
|
||||||
|
}
|
||||||
|
private is_valid_nodes() {
|
||||||
|
for (const node of this.nodes) {
|
||||||
|
if (node.type !== WorkflowType.Base && node.type !== WorkflowType.Start) {
|
||||||
|
if (!this.edges.some((edge) => edge.targetNodeId === node.id)) {
|
||||||
|
throw `${t('views.applicationWorkflow.validate.notInWorkFlowNode')}:${node.properties.stepName}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 校验节点
|
||||||
|
* @param node 节点
|
||||||
|
*/
|
||||||
|
private is_valid_node(node: any) {
|
||||||
|
if (node.properties.status && node.properties.status === 500) {
|
||||||
|
throw `${node.properties.stepName} ${t('views.applicationWorkflow.validate.nodeUnavailable')}`
|
||||||
|
}
|
||||||
|
if (node.type === WorkflowType.Condition) {
|
||||||
|
const branch_list = node.properties.node_data.branch
|
||||||
|
for (const branch of branch_list) {
|
||||||
|
const source_anchor_id = `${node.id}_${branch.id}_right`
|
||||||
|
const edge_list = this.edges.filter((edge) => edge.sourceAnchorId == source_anchor_id)
|
||||||
|
if (edge_list.length == 0) {
|
||||||
|
throw `${node.properties.stepName} ${t('views.applicationWorkflow.validate.needConnect1')}${branch.type}${t('views.applicationWorkflow.validate.needConnect2')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id)
|
||||||
|
if (edge_list.length == 0 && !end_nodes.includes(node.type)) {
|
||||||
|
throw `${node.properties.stepName} ${t('views.applicationWorkflow.validate.cannotEndNode')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.properties.status && node.properties.status !== 200) {
|
||||||
|
throw `${node.properties.stepName} ${t('views.applicationWorkflow.validate.nodeUnavailable')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar class="avatar-gradient" shape="square">
|
||||||
|
<img src="@/assets/icon_robot.svg" style="width: 75%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(item?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
class="mr-8"
|
||||||
|
>
|
||||||
|
<img :src="item?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="item?.name"
|
||||||
|
:name="item?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
class="mr-8"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
const props = defineProps<{
|
||||||
|
item: {
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #FF8800;">
|
||||||
|
<img src="@/assets/icon_hi.svg" style="width: 75%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #14C0FF;">
|
||||||
|
<img src="@/assets/icon_condition.svg" style="width: 75%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" class="avatar-blue">
|
||||||
|
<img src="@/assets/icon_docs.svg" style="width: 65%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #34c724">
|
||||||
|
<img src="@/assets/icon_form.svg" style="width: 75%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="isAppIcon(item?.icon)"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
class="mr-8"
|
||||||
|
>
|
||||||
|
<img :src="item?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar v-else shape="square" style="background: #34c724">
|
||||||
|
<img src="@/assets/icon_function_outlined.svg" style="width: 75%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isAppIcon } from '@/utils/common'
|
||||||
|
const props = defineProps<{
|
||||||
|
item?: {
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #34c724">
|
||||||
|
<img src="@/assets/icon_function_outlined.svg" style="width: 75%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<template>
|
||||||
|
<img src="@/assets/icon_globe_color.svg" style="width: 18px" alt="" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #FF8800;">
|
||||||
|
<img src="@/assets/icon_text-image.svg" style="width: 65%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #14C0FF;">
|
||||||
|
<img src="@/assets/icon_image.svg" style="width: 65%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #34c724">
|
||||||
|
<img src="@/assets/icon_mcp.svg" style="width: 65%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #34C724">
|
||||||
|
<img src="@/assets/icon_setting.svg" style="width: 65%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #FF8800">
|
||||||
|
<img src="@/assets/icon_reply.svg" style="width: 65%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #7F3BF5">
|
||||||
|
<img src="@/assets/icon_reranker.svg" style="width: 65%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" class="avatar-blue">
|
||||||
|
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #ff8800">
|
||||||
|
<img src="@/assets/icon_speech_to_text.svg" style="width: 100%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #D136D1;">
|
||||||
|
<img src="@/assets/icon_start.svg" style="width: 75%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" style="background: #14c0ff">
|
||||||
|
<img src="@/assets/icon_text_to_speech.svg" style="width: 100%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
const icons: any = import.meta.glob('./**.vue', { eager: true })
|
||||||
|
export function iconComponent(name: string) {
|
||||||
|
const url = `./${name}.vue`
|
||||||
|
return icons[url]?.default || null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<AppAvatar shape="square" class="avatar-blue">
|
||||||
|
<img src="@/assets/icon_assigner.svg" style="width: 65%" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
<template>
|
||||||
|
<div className="workflow-app" id="container"></div>
|
||||||
|
<!-- 辅助工具栏 -->
|
||||||
|
<Control class="workflow-control" v-if="lf" :lf="lf"></Control>
|
||||||
|
<TeleportContainer :flow-id="flowId" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import LogicFlow from '@logicflow/core'
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import AppEdge from './common/edge'
|
||||||
|
import Control from './common/NodeControl.vue'
|
||||||
|
import { baseNodes } from '@/workflow/common/data'
|
||||||
|
import '@logicflow/extension/lib/style/index.css'
|
||||||
|
import '@logicflow/core/dist/style/index.css'
|
||||||
|
import { initDefaultShortcut } from '@/workflow/common/shortcut'
|
||||||
|
import Dagre from '@/workflow/plugins/dagre'
|
||||||
|
import { getTeleport } from '@/workflow/common/teleport'
|
||||||
|
const nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })
|
||||||
|
|
||||||
|
defineOptions({ name: 'WorkFlow' })
|
||||||
|
const TeleportContainer = getTeleport()
|
||||||
|
const flowId = ref('')
|
||||||
|
type ShapeItem = {
|
||||||
|
type?: string
|
||||||
|
text?: string
|
||||||
|
icon?: string
|
||||||
|
label?: string
|
||||||
|
className?: string
|
||||||
|
disabled?: boolean
|
||||||
|
properties?: Record<string, any>
|
||||||
|
callback?: (lf: LogicFlow, container?: HTMLElement) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: Object || null
|
||||||
|
})
|
||||||
|
|
||||||
|
const defaultData = {
|
||||||
|
nodes: [...baseNodes]
|
||||||
|
}
|
||||||
|
const graphData = computed({
|
||||||
|
get: () => {
|
||||||
|
if (props.data) {
|
||||||
|
return props.data
|
||||||
|
} else {
|
||||||
|
return defaultData
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const lf = ref()
|
||||||
|
onMounted(() => {
|
||||||
|
renderGraphData()
|
||||||
|
})
|
||||||
|
const render = (data: any) => {
|
||||||
|
lf.value.render(data)
|
||||||
|
}
|
||||||
|
const renderGraphData = (data?: any) => {
|
||||||
|
const container: any = document.querySelector('#container')
|
||||||
|
if (container) {
|
||||||
|
lf.value = new LogicFlow({
|
||||||
|
plugins: [Dagre],
|
||||||
|
textEdit: false,
|
||||||
|
adjustEdge: false,
|
||||||
|
adjustEdgeStartAndEnd: false,
|
||||||
|
background: {
|
||||||
|
backgroundColor: '#f5f6f7'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
size: 10,
|
||||||
|
type: 'dot',
|
||||||
|
config: {
|
||||||
|
color: '#DEE0E3',
|
||||||
|
thickness: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyboard: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
isSilentMode: false,
|
||||||
|
container: container
|
||||||
|
})
|
||||||
|
lf.value.setTheme({
|
||||||
|
bezier: {
|
||||||
|
stroke: '#afafaf',
|
||||||
|
strokeWidth: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
lf.value.on('graph:rendered', () => {
|
||||||
|
flowId.value = lf.value.graphModel.flowId
|
||||||
|
})
|
||||||
|
initDefaultShortcut(lf.value, lf.value.graphModel)
|
||||||
|
lf.value.batchRegister([...Object.keys(nodes).map((key) => nodes[key].default), AppEdge])
|
||||||
|
lf.value.setDefaultEdgeType('app-edge')
|
||||||
|
|
||||||
|
lf.value.render(data ? data : {})
|
||||||
|
|
||||||
|
lf.value.graphModel.eventCenter.on('delete_edge', (id_list: Array<string>) => {
|
||||||
|
id_list.forEach((id: string) => {
|
||||||
|
lf.value.deleteEdge(id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
lf.value.graphModel.eventCenter.on('anchor:drop', (data: any) => {
|
||||||
|
// 清除当前节点下面的子节点的所有缓存
|
||||||
|
data.nodeModel.clear_next_node_field(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
lf.value?.fitView()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const validate = () => {
|
||||||
|
return Promise.all(lf.value.graphModel.nodes.map((element: any) => element?.validate?.()))
|
||||||
|
}
|
||||||
|
const getGraphData = () => {
|
||||||
|
return lf.value.getGraphData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onmousedown = (shapeItem: ShapeItem) => {
|
||||||
|
if (shapeItem.type) {
|
||||||
|
lf.value.dnd.startDrag({
|
||||||
|
type: shapeItem.type,
|
||||||
|
properties: { ...shapeItem.properties }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (shapeItem.callback) {
|
||||||
|
shapeItem.callback(lf.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const addNode = (shapeItem: ShapeItem) => {
|
||||||
|
lf.value.clearSelectElements()
|
||||||
|
const { virtualRectCenterPositionX, virtualRectCenterPositionY } =
|
||||||
|
lf.value.graphModel.getVirtualRectSize()
|
||||||
|
const newNode = lf.value.graphModel.addNode({
|
||||||
|
type: shapeItem.type,
|
||||||
|
properties: shapeItem.properties,
|
||||||
|
x: virtualRectCenterPositionX,
|
||||||
|
y: virtualRectCenterPositionY - lf.value.graphModel.height / 2
|
||||||
|
})
|
||||||
|
newNode.isSelected = true
|
||||||
|
newNode.isHovered = true
|
||||||
|
lf.value.toFront(newNode.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearGraphData = () => {
|
||||||
|
return lf.value.clearData()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
onmousedown,
|
||||||
|
validate,
|
||||||
|
getGraphData,
|
||||||
|
addNode,
|
||||||
|
clearGraphData,
|
||||||
|
renderGraphData,
|
||||||
|
render
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.workflow-app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.workflow-control {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 24px;
|
||||||
|
left: 24px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.lf-drag-able {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import ChatNodeVue from './index.vue'
|
||||||
|
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
|
||||||
|
class ChatNode extends AppNode {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props, ChatNodeVue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
type: 'ai-chat-node',
|
||||||
|
model: AppNodeModel,
|
||||||
|
view: ChatNode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,338 @@
|
||||||
|
<template>
|
||||||
|
<NodeContainer :nodeModel="nodeModel">
|
||||||
|
<h5 class="title-decoration-1 mb-8">{{ $t('views.applicationWorkflow.nodeSetting') }}</h5>
|
||||||
|
<el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
|
||||||
|
<el-form
|
||||||
|
@submit.prevent
|
||||||
|
:model="chat_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
ref="aiChatNodeFormRef"
|
||||||
|
hide-required-asterisk
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.application.applicationForm.form.aiModel.label')"
|
||||||
|
prop="model_id"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: $t('views.application.applicationForm.form.aiModel.placeholder'),
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between w-full">
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
>{{ $t('views.application.applicationForm.form.aiModel.label')
|
||||||
|
}}<span class="danger">*</span></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
:disabled="!chat_data.model_id"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="openAIParamSettingDialog(chat_data.model_id)"
|
||||||
|
@refreshForm="refreshParam"
|
||||||
|
>
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<ModelSelect
|
||||||
|
@change="model_change"
|
||||||
|
@wheel="wheel"
|
||||||
|
:teleported="false"
|
||||||
|
v-model="chat_data.model_id"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.aiModel.placeholder')"
|
||||||
|
:options="modelOptions"
|
||||||
|
@submitModel="getModel"
|
||||||
|
showFooter
|
||||||
|
:model-type="'LLM'"
|
||||||
|
></ModelSelect>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.roleSettings.label')">
|
||||||
|
<MdEditorMagnify
|
||||||
|
:title="$t('views.application.applicationForm.form.roleSettings.label')"
|
||||||
|
v-model="chat_data.system"
|
||||||
|
style="height: 100px"
|
||||||
|
@submitDialog="submitSystemDialog"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.roleSettings.label')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.application.applicationForm.form.prompt.label')"
|
||||||
|
prop="prompt"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: $t('views.application.applicationForm.form.prompt.requiredMessage'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-4">
|
||||||
|
<span
|
||||||
|
>{{ $t('views.application.applicationForm.form.prompt.label')
|
||||||
|
}}<span class="danger">*</span></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
|
||||||
|
<template #content
|
||||||
|
>{{ $t('views.application.applicationForm.form.prompt.tooltip') }}
|
||||||
|
</template>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<MdEditorMagnify
|
||||||
|
@wheel="wheel"
|
||||||
|
:title="$t('views.application.applicationForm.form.prompt.label')"
|
||||||
|
v-model="chat_data.prompt"
|
||||||
|
style="height: 150px"
|
||||||
|
@submitDialog="submitDialog"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.historyRecord.label')">
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<div>{{ $t('views.application.applicationForm.form.historyRecord.label') }}</div>
|
||||||
|
<el-select v-model="chat_data.dialogue_type" type="small" style="width: 100px">
|
||||||
|
<el-option :label="$t('views.applicationWorkflow.node')" value="NODE" />
|
||||||
|
<el-option :label="$t('views.applicationWorkflow.workflow')" value="WORKFLOW" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-input-number
|
||||||
|
v-model="chat_data.dialogue_number"
|
||||||
|
:min="0"
|
||||||
|
:value-on-clear="0"
|
||||||
|
controls-position="right"
|
||||||
|
class="w-full"
|
||||||
|
:step="1"
|
||||||
|
:step-strictly="true"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<div class="flex-between mb-16">
|
||||||
|
<div class="lighter">{{ $t('views.applicationWorkflow.nodes.mcpNode.tool') }}</div>
|
||||||
|
<el-button type="primary" link @click="openMcpServersDialog" @refreshForm="refreshParam">
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form-item @click.prevent>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between w-full">
|
||||||
|
<div>
|
||||||
|
<span>{{
|
||||||
|
$t('views.application.applicationForm.form.reasoningContent.label')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="openReasoningParamSettingDialog"
|
||||||
|
@refreshForm="refreshParam"
|
||||||
|
>
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-switch size="small" v-model="chat_data.model_setting.reasoning_content_enable" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item @click.prevent>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-4">
|
||||||
|
<span>{{
|
||||||
|
$t('views.applicationWorkflow.nodes.aiChatNode.returnContent.label')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
|
||||||
|
<template #content>
|
||||||
|
{{ $t('views.applicationWorkflow.nodes.aiChatNode.returnContent.tooltip') }}
|
||||||
|
</template>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-switch size="small" v-model="chat_data.is_result" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<AIModeParamSettingDialog ref="AIModeParamSettingDialogRef" @refresh="refreshParam" />
|
||||||
|
<ReasoningParamSettingDialog
|
||||||
|
ref="ReasoningParamSettingDialogRef"
|
||||||
|
@refresh="submitReasoningDialog"
|
||||||
|
/>
|
||||||
|
<McpServersDialog ref="mcpServersDialogRef" @refresh="submitMcpServersDialog" />
|
||||||
|
</NodeContainer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { cloneDeep, set, groupBy } from 'lodash'
|
||||||
|
import { app } from '@/main'
|
||||||
|
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
import { isLastNode } from '@/workflow/common/data'
|
||||||
|
import AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import ReasoningParamSettingDialog from '@/views/application/component/ReasoningParamSettingDialog.vue'
|
||||||
|
import McpServersDialog from '@/views/application/component/McpServersDialog.vue'
|
||||||
|
const { model } = useStore()
|
||||||
|
|
||||||
|
const wheel = (e: any) => {
|
||||||
|
if (e.ctrlKey === true) {
|
||||||
|
e.preventDefault()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
e.stopPropagation()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitSystemDialog(val: string) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'system', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitDialog(val: string) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'prompt', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
const model_change = (model_id?: string) => {
|
||||||
|
if (model_id) {
|
||||||
|
AIModeParamSettingDialogRef.value?.reset_default(model_id, id)
|
||||||
|
} else {
|
||||||
|
refreshParam({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = app.config.globalProperties.$route as any
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const defaultPrompt = `${t('views.applicationWorkflow.nodes.aiChatNode.defaultPrompt')}:
|
||||||
|
{{${t('views.applicationWorkflow.nodes.searchDatasetNode.label')}.data}}
|
||||||
|
${t('views.problem.title')}:
|
||||||
|
{{${t('views.applicationWorkflow.nodes.startNode.label')}.question}}`
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
model_id: '',
|
||||||
|
system: '',
|
||||||
|
prompt: defaultPrompt,
|
||||||
|
dialogue_number: 1,
|
||||||
|
is_result: false,
|
||||||
|
temperature: null,
|
||||||
|
max_tokens: null,
|
||||||
|
dialogue_type: 'WORKFLOW',
|
||||||
|
model_setting: {
|
||||||
|
reasoning_content_start: '<think>',
|
||||||
|
reasoning_content_end: '</think>',
|
||||||
|
reasoning_content_enable: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chat_data = computed({
|
||||||
|
get: () => {
|
||||||
|
if (props.nodeModel.properties.node_data) {
|
||||||
|
if (!props.nodeModel.properties.node_data.model_setting) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'model_setting', {
|
||||||
|
reasoning_content_start: '<think>',
|
||||||
|
reasoning_content_end: '</think>',
|
||||||
|
reasoning_content_enable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
} else {
|
||||||
|
set(props.nodeModel.properties, 'node_data', form)
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
set(props.nodeModel.properties, 'node_data', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
|
||||||
|
const aiChatNodeFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const modelOptions = ref<any>(null)
|
||||||
|
const AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()
|
||||||
|
const ReasoningParamSettingDialogRef = ref<InstanceType<typeof ReasoningParamSettingDialog>>()
|
||||||
|
const validate = () => {
|
||||||
|
return aiChatNodeFormRef.value?.validate().catch((err) => {
|
||||||
|
return Promise.reject({ node: props.nodeModel, errMessage: err })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModel() {
|
||||||
|
if (id) {
|
||||||
|
applicationApi.getApplicationModel(id).then((res: any) => {
|
||||||
|
modelOptions.value = groupBy(res?.data, 'provider')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
model.asyncGetModel().then((res: any) => {
|
||||||
|
modelOptions.value = groupBy(res?.data, 'provider')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openAIParamSettingDialog = (modelId: string) => {
|
||||||
|
if (modelId) {
|
||||||
|
AIModeParamSettingDialogRef.value?.open(modelId, id, chat_data.value.model_params_setting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openReasoningParamSettingDialog = () => {
|
||||||
|
ReasoningParamSettingDialogRef.value?.open(chat_data.value.model_setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshParam(data: any) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'model_params_setting', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitReasoningDialog(val: any) {
|
||||||
|
let model_setting = cloneDeep(props.nodeModel.properties.node_data.model_setting)
|
||||||
|
model_setting = {
|
||||||
|
...model_setting,
|
||||||
|
...val
|
||||||
|
}
|
||||||
|
|
||||||
|
set(props.nodeModel.properties.node_data, 'model_setting', model_setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mcpServersDialogRef = ref()
|
||||||
|
function openMcpServersDialog() {
|
||||||
|
const config = {
|
||||||
|
mcp_servers: chat_data.value.mcp_servers,
|
||||||
|
mcp_enable: chat_data.value.mcp_enable
|
||||||
|
}
|
||||||
|
mcpServersDialogRef.value.open(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitMcpServersDialog(config: any) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'mcp_servers', config.mcp_servers)
|
||||||
|
set(props.nodeModel.properties.node_data, 'mcp_enable', config.mcp_enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getModel()
|
||||||
|
if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {
|
||||||
|
if (isLastNode(props.nodeModel)) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'is_result', true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set(props.nodeModel, 'validate', validate)
|
||||||
|
if (!chat_data.value.dialogue_type) {
|
||||||
|
chat_data.value.dialogue_type = 'WORKFLOW'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import ChatNodeVue from './index.vue'
|
||||||
|
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
|
||||||
|
class ChatNode extends AppNode {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props, ChatNodeVue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
type: 'application-node',
|
||||||
|
model: AppNodeModel,
|
||||||
|
view: ChatNode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,317 @@
|
||||||
|
<template>
|
||||||
|
<NodeContainer :nodeModel="nodeModel">
|
||||||
|
<h5 class="title-decoration-1 mb-8">{{ $t('views.applicationWorkflow.nodeSetting') }}</h5>
|
||||||
|
<el-card shadow="never" class="card-never">
|
||||||
|
<el-form
|
||||||
|
@submit.prevent
|
||||||
|
:model="form_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
ref="applicationNodeFormRef"
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.startNode.question')"
|
||||||
|
prop="question_reference_address"
|
||||||
|
:rules="{
|
||||||
|
message: $t(
|
||||||
|
'views.applicationWorkflow.nodes.searchDatasetNode.searchQuestion.requiredMessage'
|
||||||
|
),
|
||||||
|
trigger: 'blur',
|
||||||
|
required: true
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NodeCascader
|
||||||
|
ref="applicationNodeFormRef"
|
||||||
|
:nodeModel="nodeModel"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.applicationWorkflow.nodes.searchDatasetNode.searchQuestion.placeholder')
|
||||||
|
"
|
||||||
|
v-model="form_data.question_reference_address"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="form_data.hasOwnProperty('document_list') || 'document_list' in form_data"
|
||||||
|
:label="$t('views.problem.relateParagraph.selectDocument')"
|
||||||
|
prop="document_list"
|
||||||
|
:rules="{
|
||||||
|
message: $t('views.log.documentPlaceholder'),
|
||||||
|
trigger: 'blur',
|
||||||
|
required: false
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NodeCascader
|
||||||
|
ref="nodeCascaderRef"
|
||||||
|
:nodeModel="nodeModel"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="$t('views.log.documentPlaceholder')"
|
||||||
|
v-model="form_data.document_list"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="form_data.hasOwnProperty('image_list') || 'image_list' in form_data"
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.imageUnderstandNode.image.label')"
|
||||||
|
prop="image_list"
|
||||||
|
:rules="{
|
||||||
|
message: $t(
|
||||||
|
'views.applicationWorkflow.nodes.imageUnderstandNode.image.requiredMessage'
|
||||||
|
),
|
||||||
|
trigger: 'blur',
|
||||||
|
required: false
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NodeCascader
|
||||||
|
ref="nodeCascaderRef"
|
||||||
|
:nodeModel="nodeModel"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.applicationWorkflow.nodes.imageUnderstandNode.image.requiredMessage')
|
||||||
|
"
|
||||||
|
v-model="form_data.image_list"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-if="form_data.hasOwnProperty('audio_list') || 'audio_list' in form_data"
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.speechToTextNode.audio.label')"
|
||||||
|
prop="audio_list"
|
||||||
|
:rules="{
|
||||||
|
message: $t('views.applicationWorkflow.nodes.speechToTextNode.audio.placeholder'),
|
||||||
|
trigger: 'blur',
|
||||||
|
required: false
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NodeCascader
|
||||||
|
ref="nodeCascaderRef"
|
||||||
|
:nodeModel="nodeModel"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="$t('views.applicationWorkflow.nodes.speechToTextNode.audio.placeholder')"
|
||||||
|
v-model="form_data.audio_list"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-for="(field, index) in form_data.api_input_field_list" :key="'api-input-' + index">
|
||||||
|
<el-form-item
|
||||||
|
:label="field.variable"
|
||||||
|
:prop="'api_input_field_list.' + index + '.value'"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: field.is_required,
|
||||||
|
message: `${$t('common.inputPlaceholder')}${field.variable}`,
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<NodeCascader
|
||||||
|
ref="nodeCascaderRef"
|
||||||
|
:nodeModel="nodeModel"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.applicationWorkflow.nodes.searchDatasetNode.searchQuestion.placeholder')
|
||||||
|
"
|
||||||
|
v-model="form_data.api_input_field_list[index].value"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-for="(field, index) in form_data.user_input_field_list" :key="'user-input-' + index">
|
||||||
|
<el-form-item
|
||||||
|
:label="field.label"
|
||||||
|
:prop="'user_input_field_list.' + index + '.value'"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: field.required,
|
||||||
|
message: `${$t('common.inputPlaceholder')}${field.label}`,
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<NodeCascader
|
||||||
|
ref="nodeCascaderRef"
|
||||||
|
:nodeModel="nodeModel"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.applicationWorkflow.nodes.searchDatasetNode.searchQuestion.placeholder')
|
||||||
|
"
|
||||||
|
v-model="form_data.user_input_field_list[index].value"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.aiChatNode.returnContent.label')"
|
||||||
|
@click.prevent
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-4">
|
||||||
|
<span>{{
|
||||||
|
$t('views.applicationWorkflow.nodes.aiChatNode.returnContent.label')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
|
||||||
|
<template #content>
|
||||||
|
{{ $t('views.applicationWorkflow.nodes.aiChatNode.returnContent.tooltip') }}
|
||||||
|
</template>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-switch size="small" v-model="form_data.is_result" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</NodeContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { set, groupBy, create, cloneDeep } from 'lodash'
|
||||||
|
import { app } from '@/main'
|
||||||
|
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||||
|
import { ref, computed, onMounted, onActivated } from 'vue'
|
||||||
|
import NodeCascader from '@/workflow/common/NodeCascader.vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { isWorkFlow } from '@/utils/application'
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
question_reference_address: ['start-node', 'question'],
|
||||||
|
api_input_field_list: [],
|
||||||
|
user_input_field_list: [],
|
||||||
|
document_list: ['start-node', 'document'],
|
||||||
|
image_list: ['start-node', 'image'],
|
||||||
|
audio_list: ['start-node', 'audio']
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = app.config.globalProperties.$route as any
|
||||||
|
|
||||||
|
const applicationNodeFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const form_data = computed({
|
||||||
|
get: () => {
|
||||||
|
if (props.nodeModel.properties.node_data) {
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
} else {
|
||||||
|
set(props.nodeModel.properties, 'node_data', form)
|
||||||
|
}
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
set(props.nodeModel.properties, 'node_data', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleFileUpload(type: string, isEnabled: boolean) {
|
||||||
|
const listKey = `${type}_list`
|
||||||
|
if (isEnabled) {
|
||||||
|
if (!props.nodeModel.properties.node_data[listKey]) {
|
||||||
|
set(props.nodeModel.properties.node_data, listKey, [])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
|
delete props.nodeModel.properties.node_data[listKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const update_field = () => {
|
||||||
|
if (!props.nodeModel.properties.node_data.application_id) {
|
||||||
|
set(props.nodeModel.properties, 'status', 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
applicationApi
|
||||||
|
.getApplicationById(id, props.nodeModel.properties.node_data.application_id)
|
||||||
|
.then((ok) => {
|
||||||
|
const old_api_input_field_list = cloneDeep(
|
||||||
|
props.nodeModel.properties.node_data.api_input_field_list
|
||||||
|
)
|
||||||
|
const old_user_input_field_list = cloneDeep(
|
||||||
|
props.nodeModel.properties.node_data.user_input_field_list
|
||||||
|
)
|
||||||
|
if (isWorkFlow(ok.data.type)) {
|
||||||
|
const nodeData = ok.data.work_flow.nodes[0].properties.node_data
|
||||||
|
const new_api_input_field_list = cloneDeep(
|
||||||
|
ok.data.work_flow.nodes[0].properties.api_input_field_list
|
||||||
|
)
|
||||||
|
const new_user_input_field_list = cloneDeep(
|
||||||
|
ok.data.work_flow.nodes[0].properties.user_input_field_list
|
||||||
|
)
|
||||||
|
|
||||||
|
const merge_api_input_field_list = (new_api_input_field_list || []).map((item: any) => {
|
||||||
|
const find_field = old_api_input_field_list.find(
|
||||||
|
(old_item: any) => old_item.variable == item.variable
|
||||||
|
)
|
||||||
|
if (find_field) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: find_field.value,
|
||||||
|
label:
|
||||||
|
typeof item.label === 'object' && item.label != null ? item.label.label : item.label
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
set(
|
||||||
|
props.nodeModel.properties.node_data,
|
||||||
|
'api_input_field_list',
|
||||||
|
merge_api_input_field_list
|
||||||
|
)
|
||||||
|
const merge_user_input_field_list = (new_user_input_field_list || []).map((item: any) => {
|
||||||
|
const find_field = old_user_input_field_list.find(
|
||||||
|
(old_item: any) => old_item.field == item.field
|
||||||
|
)
|
||||||
|
if (find_field) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: find_field.value,
|
||||||
|
label:
|
||||||
|
typeof item.label === 'object' && item.label != null ? item.label.label : item.label
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
set(
|
||||||
|
props.nodeModel.properties.node_data,
|
||||||
|
'user_input_field_list',
|
||||||
|
merge_user_input_field_list
|
||||||
|
)
|
||||||
|
const fileEnable = nodeData.file_upload_enable
|
||||||
|
const fileUploadSetting = nodeData.file_upload_setting
|
||||||
|
if (fileEnable) {
|
||||||
|
handleFileUpload('document', fileUploadSetting.document)
|
||||||
|
handleFileUpload('image', fileUploadSetting.image)
|
||||||
|
handleFileUpload('audio', fileUploadSetting.audio)
|
||||||
|
} else {
|
||||||
|
;['document_list', 'image_list', 'audio_list'].forEach((list) => {
|
||||||
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
|
delete props.nodeModel.properties.node_data[list]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
set(props.nodeModel.properties, 'status', ok.data.id ? 200 : 500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
set(props.nodeModel.properties, 'status', 500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
return applicationNodeFormRef.value?.validate().catch((err) => {
|
||||||
|
return Promise.reject({ node: props.nodeModel, errMessage: err })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
update_field()
|
||||||
|
set(props.nodeModel, 'validate', validate)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="
|
||||||
|
isEdit
|
||||||
|
? $t('views.template.templateForm.title.editParam')
|
||||||
|
: $t('views.template.templateForm.title.addParam')
|
||||||
|
"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:before-close="close"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
ref="fieldFormRef"
|
||||||
|
:rules="rules"
|
||||||
|
:model="form"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item :label="$t('dynamicsForm.paramForm.field.label')" prop="variable">
|
||||||
|
<el-input
|
||||||
|
v-model="form.variable"
|
||||||
|
:placeholder="$t('dynamicsForm.paramForm.field.placeholder')"
|
||||||
|
maxlength="64"
|
||||||
|
show-word-limit
|
||||||
|
@blur="form.variable = form.variable.trim()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('dynamicsForm.paramForm.required.label')" @click.prevent>
|
||||||
|
<el-switch size="small" v-model="form.is_required"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('dynamicsForm.default.label')"
|
||||||
|
prop="default_value"
|
||||||
|
:rules="{
|
||||||
|
required: form.is_required,
|
||||||
|
message: $t('dynamicsForm.default.placeholder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="form.default_value"
|
||||||
|
:placeholder="$t('dynamicsForm.default.placeholder')"
|
||||||
|
@blur="form.name = form.name.trim()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
|
||||||
|
{{ isEdit ? $t('common.save') : $t('common.add') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const fieldFormRef = ref()
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const isEdit = ref(false)
|
||||||
|
|
||||||
|
const form = ref<any>({
|
||||||
|
name: '',
|
||||||
|
variable: '',
|
||||||
|
type: 'input',
|
||||||
|
is_required: true,
|
||||||
|
assignment_method: 'api_input',
|
||||||
|
optionList: [''],
|
||||||
|
default_value: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
name: [{ required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' }],
|
||||||
|
variable: [
|
||||||
|
{ required: true, message: t('dynamicsForm.paramForm.field.requiredMessage'), trigger: 'blur' },
|
||||||
|
{ pattern: /^[a-zA-Z0-9_]+$/, message: t('dynamicsForm.paramForm.field.requiredMessage2'), trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
form.value = {
|
||||||
|
name: '',
|
||||||
|
variable: '',
|
||||||
|
type: 'input',
|
||||||
|
is_required: true,
|
||||||
|
assignment_method: 'api_input',
|
||||||
|
optionList: [''],
|
||||||
|
default_value: ''
|
||||||
|
}
|
||||||
|
isEdit.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (row: any) => {
|
||||||
|
if (row) {
|
||||||
|
form.value = cloneDeep(row)
|
||||||
|
isEdit.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
isEdit.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
emit('refresh', form.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, close })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex-between mb-16">
|
||||||
|
<h5 class="lighter">{{ $t('views.template.templateForm.title.apiParamPassing') }}</h5>
|
||||||
|
<el-button link type="primary" @click="openAddDialog()">
|
||||||
|
<el-icon class="mr-4">
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
{{ $t('common.add') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
v-if="props.nodeModel.properties.api_input_field_list?.length > 0"
|
||||||
|
:data="props.nodeModel.properties.api_input_field_list"
|
||||||
|
class="mb-16"
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="field"
|
||||||
|
>
|
||||||
|
<el-table-column prop="variable" :label="$t('dynamicsForm.paramForm.field.label')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="ellipsis-1" :title="row.variable">
|
||||||
|
{{ row.variable }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="default_value" :label="$t('dynamicsForm.default.label')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="ellipsis-1" :title="row.default_value">
|
||||||
|
{{ row.default_value }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.required')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div @click.stop>
|
||||||
|
<el-switch disabled size="small" v-model="row.is_required" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.operation')" align="left" width="90">
|
||||||
|
<template #default="{ row, $index }">
|
||||||
|
<span class="mr-4">
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
|
||||||
|
<el-button type="primary" text @click.stop="openAddDialog(row, $index)">
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
|
||||||
|
<el-button type="primary" text @click="deleteField($index)">
|
||||||
|
<el-icon>
|
||||||
|
<Delete />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<ApiFieldFormDialog ref="ApiFieldFormDialogRef" @refresh="refreshFieldList" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { set } from 'lodash'
|
||||||
|
import Sortable from 'sortablejs'
|
||||||
|
import ApiFieldFormDialog from './ApiFieldFormDialog.vue'
|
||||||
|
import { MsgError } from '@/utils/message'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
const tableRef = ref()
|
||||||
|
const currentIndex = ref(null)
|
||||||
|
const ApiFieldFormDialogRef = ref()
|
||||||
|
const inputFieldList = ref<any[]>([])
|
||||||
|
|
||||||
|
function openAddDialog(data?: any, index?: any) {
|
||||||
|
if (typeof index !== 'undefined') {
|
||||||
|
currentIndex.value = index
|
||||||
|
}
|
||||||
|
ApiFieldFormDialogRef.value.open(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteField(index: any) {
|
||||||
|
inputFieldList.value.splice(index, 1)
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')
|
||||||
|
onDragHandle()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFieldList(data: any) {
|
||||||
|
for (let i = 0; i < inputFieldList.value.length; i++) {
|
||||||
|
if (inputFieldList.value[i].variable === data.variable && currentIndex.value !== i) {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.paramErrorMessage') + data.variable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 查看另一个list又没有重复的
|
||||||
|
let arr = props.nodeModel.properties.user_input_field_list
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i].field === data.variable) {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.paramErrorMessage') + data.variable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentIndex.value !== null) {
|
||||||
|
inputFieldList.value.splice(currentIndex.value, 1, data)
|
||||||
|
} else {
|
||||||
|
inputFieldList.value.push(data)
|
||||||
|
}
|
||||||
|
currentIndex.value = null
|
||||||
|
ApiFieldFormDialogRef.value.close()
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')
|
||||||
|
onDragHandle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格排序拖拽
|
||||||
|
function onDragHandle() {
|
||||||
|
if (!tableRef.value) return
|
||||||
|
|
||||||
|
// 获取表格的 tbody DOM 元素
|
||||||
|
const wrapper = tableRef.value.$el as HTMLElement
|
||||||
|
const tbody = wrapper.querySelector('.el-table__body-wrapper tbody')
|
||||||
|
if (!tbody) return
|
||||||
|
// 初始化 Sortable
|
||||||
|
Sortable.create(tbody as HTMLElement, {
|
||||||
|
animation: 150,
|
||||||
|
ghostClass: 'ghost-row',
|
||||||
|
onEnd: (evt) => {
|
||||||
|
if (evt.oldIndex === undefined || evt.newIndex === undefined) return
|
||||||
|
// 更新数据顺序
|
||||||
|
const items = [...inputFieldList.value]
|
||||||
|
const [movedItem] = items.splice(evt.oldIndex, 1)
|
||||||
|
items.splice(evt.newIndex, 0, movedItem)
|
||||||
|
inputFieldList.value = items
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.nodeModel.properties.api_input_field_list) {
|
||||||
|
if (props.nodeModel.properties.input_field_list) {
|
||||||
|
props.nodeModel.properties.input_field_list
|
||||||
|
.filter((item: any) => {
|
||||||
|
return item.assignment_method === 'api_input'
|
||||||
|
})
|
||||||
|
.forEach((item: any) => {
|
||||||
|
inputFieldList.value.push(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inputFieldList.value.push(...props.nodeModel.properties.api_input_field_list)
|
||||||
|
}
|
||||||
|
set(props.nodeModel.properties, 'api_input_field_list', inputFieldList)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
|
|
@ -0,0 +1,288 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.applicationWorkflow.nodes.baseNode.FileUploadSetting.title')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:before-close="close"
|
||||||
|
append-to-body
|
||||||
|
width="800"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
ref="fieldFormRef"
|
||||||
|
:model="form_data"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.baseNode.FileUploadSetting.maxFiles')"
|
||||||
|
>
|
||||||
|
<el-slider
|
||||||
|
v-model="form_data.maxFiles"
|
||||||
|
show-input
|
||||||
|
:show-input-controls="false"
|
||||||
|
:min="1"
|
||||||
|
:max="10"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileLimit')"
|
||||||
|
>
|
||||||
|
<el-slider
|
||||||
|
v-model="form_data.fileLimit"
|
||||||
|
show-input
|
||||||
|
:show-input-controls="false"
|
||||||
|
:min="1"
|
||||||
|
:max="100"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="
|
||||||
|
$t('views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.label')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-card
|
||||||
|
shadow="hover"
|
||||||
|
class="card-checkbox cursor w-full mb-8"
|
||||||
|
:class="form_data.document ? 'active' : ''"
|
||||||
|
style="--el-card-padding: 8px 16px"
|
||||||
|
@click.stop="form_data.document = !form_data.document"
|
||||||
|
>
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<img class="mr-12" src="@/assets/icon_file-doc.svg" alt="" />
|
||||||
|
<div>
|
||||||
|
<p class="line-height-22 mt-4">
|
||||||
|
{{ $t('common.fileUpload.document') }}
|
||||||
|
<el-text class="color-secondary"
|
||||||
|
>{{
|
||||||
|
$t(
|
||||||
|
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.documentText'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</el-text>
|
||||||
|
</p>
|
||||||
|
<p>{{ documentExtensions.join('、') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="form_data.document"
|
||||||
|
@change="form_data.document = !form_data.document"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<el-card
|
||||||
|
shadow="hover"
|
||||||
|
class="card-checkbox cursor w-full mb-8"
|
||||||
|
:class="form_data.image ? 'active' : ''"
|
||||||
|
style="--el-card-padding: 8px 16px"
|
||||||
|
@click.stop="form_data.image = !form_data.image"
|
||||||
|
>
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<img class="mr-12" src="@/assets/icon_file-image.svg" alt="" />
|
||||||
|
<div>
|
||||||
|
<p class="line-height-22 mt-4">
|
||||||
|
{{ $t('common.fileUpload.image') }}
|
||||||
|
<el-text class="color-secondary"
|
||||||
|
>{{
|
||||||
|
$t(
|
||||||
|
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.imageText'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</el-text>
|
||||||
|
</p>
|
||||||
|
<p>{{ imageExtensions.join('、') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-checkbox v-model="form_data.image" @change="form_data.image = !form_data.image" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card
|
||||||
|
shadow="hover"
|
||||||
|
class="card-checkbox cursor w-full mb-8"
|
||||||
|
:class="form_data.audio ? 'active' : ''"
|
||||||
|
style="--el-card-padding: 8px 16px"
|
||||||
|
@click.stop="form_data.audio = !form_data.audio"
|
||||||
|
>
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<img class="mr-12" src="@/assets/icon_file-audio.svg" alt="" />
|
||||||
|
<div>
|
||||||
|
<p class="line-height-22 mt-4">
|
||||||
|
{{ $t('common.fileUpload.audio') }}
|
||||||
|
<el-text class="color-secondary"
|
||||||
|
>{{
|
||||||
|
$t(
|
||||||
|
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.audioText'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</el-text>
|
||||||
|
</p>
|
||||||
|
<p>{{ audioExtensions.join('、') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-checkbox v-model="form_data.audio" @change="form_data.audio = !form_data.audio" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<el-card
|
||||||
|
shadow="hover"
|
||||||
|
class="card-checkbox cursor w-full mb-8"
|
||||||
|
:class="form_data.other ? 'active' : ''"
|
||||||
|
style="--el-card-padding: 8px 16px"
|
||||||
|
@click.stop="form_data.other = !form_data.other"
|
||||||
|
>
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<img class="mr-12" :width="32" src="@/assets/fileType/unknown-icon.svg" alt="" />
|
||||||
|
<div>
|
||||||
|
<p class="line-height-22 mt-4">
|
||||||
|
{{ $t('common.fileUpload.other') }}
|
||||||
|
<el-text class="color-secondary"
|
||||||
|
>{{
|
||||||
|
$t(
|
||||||
|
'views.applicationWorkflow.nodes.baseNode.FileUploadSetting.fileUploadType.otherText'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</el-text>
|
||||||
|
</p>
|
||||||
|
<el-space wrap :size="2" class="mt-4">
|
||||||
|
<el-tag
|
||||||
|
v-for="tag in form_data.otherExtensions"
|
||||||
|
:key="tag"
|
||||||
|
closable
|
||||||
|
:disable-transitions="false"
|
||||||
|
@close="handleClose(tag)"
|
||||||
|
type="info"
|
||||||
|
class="mr-4"
|
||||||
|
effect="plain"
|
||||||
|
style="
|
||||||
|
--el-tag-border-radius: 4px;
|
||||||
|
--el-tag-border-color: var(--el-border-color);
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</el-tag>
|
||||||
|
<el-input
|
||||||
|
v-if="inputVisible"
|
||||||
|
ref="InputRef"
|
||||||
|
v-model="inputValue"
|
||||||
|
size="small"
|
||||||
|
@keyup.enter="handleInputConfirm"
|
||||||
|
@blur="handleInputConfirm"
|
||||||
|
/>
|
||||||
|
<el-button v-else class="button-new-tag" size="small" @click.stop="showInput">
|
||||||
|
+ {{ $t('common.fileUpload.addExtensions') }}
|
||||||
|
</el-button>
|
||||||
|
</el-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-checkbox v-model="form_data.other" @change="form_data.other = !form_data.other" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="close"> {{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit()" :loading="loading">
|
||||||
|
{{ $t('common.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { nextTick, ref } from 'vue'
|
||||||
|
import type { InputInstance } from 'element-plus'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { MsgWarning } from '@/utils/message'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const inputVisible = ref(false)
|
||||||
|
const inputValue = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const fieldFormRef = ref()
|
||||||
|
const InputRef = ref<InputInstance>()
|
||||||
|
|
||||||
|
const documentExtensions = ['TXT', 'MD', 'DOCX', 'HTML', 'CSV', 'XLSX', 'XLS', 'PDF']
|
||||||
|
const imageExtensions = ['JPG', 'JPEG', 'PNG', 'GIF']
|
||||||
|
const audioExtensions = ['MP3', 'WAV', 'OGG', 'ACC', 'M4A']
|
||||||
|
|
||||||
|
const form_data = ref({
|
||||||
|
maxFiles: 3,
|
||||||
|
fileLimit: 50,
|
||||||
|
document: true,
|
||||||
|
image: false,
|
||||||
|
audio: false,
|
||||||
|
video: false,
|
||||||
|
other: false,
|
||||||
|
otherExtensions: ['PPT', 'DOC']
|
||||||
|
})
|
||||||
|
|
||||||
|
function open(data: any) {
|
||||||
|
dialogVisible.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
form_data.value = { ...form_data.value, ...data }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = (tag: string) => {
|
||||||
|
form_data.value.otherExtensions = form_data.value.otherExtensions.filter((item) => item !== tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showInput = () => {
|
||||||
|
inputVisible.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
InputRef.value!.input!.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleInputConfirm = () => {
|
||||||
|
if (inputValue.value) {
|
||||||
|
inputValue.value = inputValue.value.toUpperCase()
|
||||||
|
if (
|
||||||
|
form_data.value.otherExtensions.includes(inputValue.value) ||
|
||||||
|
documentExtensions.includes(inputValue.value) ||
|
||||||
|
imageExtensions.includes(inputValue.value) ||
|
||||||
|
audioExtensions.includes(inputValue.value)
|
||||||
|
) {
|
||||||
|
inputVisible.value = false
|
||||||
|
inputValue.value = ''
|
||||||
|
MsgWarning(t('common.fileUpload.existingExtensionsTip'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form_data.value.otherExtensions.push(inputValue.value)
|
||||||
|
}
|
||||||
|
inputVisible.value = false
|
||||||
|
inputValue.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
const formEl = fieldFormRef.value
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate().then(() => {
|
||||||
|
const formattedData = cloneDeep(form_data.value)
|
||||||
|
emit('refresh', formattedData)
|
||||||
|
// emit('refresh', form_data.value)
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refreshFileUploadConfig')
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="
|
||||||
|
isEdit
|
||||||
|
? $t('views.template.templateForm.title.editParam')
|
||||||
|
: $t('views.template.templateForm.title.addParam')
|
||||||
|
"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:before-close="close"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<DynamicsFormConstructor
|
||||||
|
v-model="currentRow"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
:input_type_list="inputTypeList"
|
||||||
|
ref="DynamicsFormConstructorRef"
|
||||||
|
></DynamicsFormConstructor>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="close"> {{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit()" :loading="loading">
|
||||||
|
{{ isEdit ? $t('common.save') : $t('common.add') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'
|
||||||
|
import type { FormField } from '@/components/dynamics-form/type'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const DynamicsFormConstructorRef = ref()
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const isEdit = ref(false)
|
||||||
|
const currentItem = ref<FormField | any>()
|
||||||
|
const check_field = (field_list: Array<string>, obj: any) => {
|
||||||
|
return field_list.every((field) => _.get(obj, field, undefined) !== undefined)
|
||||||
|
}
|
||||||
|
const currentRow = computed(() => {
|
||||||
|
if (currentItem.value) {
|
||||||
|
const row = currentItem.value
|
||||||
|
switch (row.type) {
|
||||||
|
case 'input':
|
||||||
|
if (check_field(['field', 'input_type', 'label', 'required', 'attrs'], currentItem.value)) {
|
||||||
|
return currentItem.value
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
attrs: row.attrs || { maxlength: 200, minlength: 0 },
|
||||||
|
field: row.field || row.variable,
|
||||||
|
input_type: 'TextInput',
|
||||||
|
label: row.label || row.name,
|
||||||
|
default_value: row.default_value,
|
||||||
|
required: row.required != undefined ? row.required : row.is_required
|
||||||
|
}
|
||||||
|
case 'select':
|
||||||
|
if (
|
||||||
|
check_field(
|
||||||
|
['field', 'input_type', 'label', 'required', 'option_list'],
|
||||||
|
currentItem.value
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return currentItem.value
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
attrs: row.attrs || {},
|
||||||
|
field: row.field || row.variable,
|
||||||
|
input_type: 'SingleSelect',
|
||||||
|
label: row.label || row.name,
|
||||||
|
default_value: row.default_value,
|
||||||
|
required: row.required != undefined ? row.required : row.is_required,
|
||||||
|
option_list: row.option_list
|
||||||
|
? row.option_list
|
||||||
|
: row.optionList.map((o: any) => {
|
||||||
|
return { key: o, value: o }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'date':
|
||||||
|
if (
|
||||||
|
check_field(
|
||||||
|
[
|
||||||
|
'field',
|
||||||
|
'input_type',
|
||||||
|
'label',
|
||||||
|
'required',
|
||||||
|
'attrs.format',
|
||||||
|
'attrs.value-format',
|
||||||
|
'attrs.type'
|
||||||
|
],
|
||||||
|
currentItem.value
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return currentItem.value
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
field: row.field || row.variable,
|
||||||
|
input_type: 'DatePicker',
|
||||||
|
label: row.label || row.name,
|
||||||
|
default_value: row.default_value,
|
||||||
|
required: row.required != undefined ? row.required : row.is_required,
|
||||||
|
attrs: {
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
type: 'datetime'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return currentItem.value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { input_type: 'TextInput', required: false, attrs: { maxlength: 200, minlength: 0 }, show_default_value: true }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const currentIndex = ref(null)
|
||||||
|
const inputTypeList = ref([
|
||||||
|
{ label: t('dynamicsForm.input_type_list.TextInput'), value: 'TextInputConstructor' },
|
||||||
|
{ label: t('dynamicsForm.input_type_list.PasswordInput'), value: 'PasswordInputConstructor' },
|
||||||
|
{ label: t('dynamicsForm.input_type_list.SingleSelect'), value: 'SingleSelectConstructor' },
|
||||||
|
{ label: t('dynamicsForm.input_type_list.MultiSelect'), value: 'MultiSelectConstructor' },
|
||||||
|
{ label: t('dynamicsForm.input_type_list.RadioCard'), value: 'RadioCardConstructor' },
|
||||||
|
{ label: t('dynamicsForm.input_type_list.DatePicker'), value: 'DatePickerConstructor' },
|
||||||
|
{ label: t('dynamicsForm.input_type_list.SwitchInput'), value: 'SwitchInputConstructor' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
|
||||||
|
const open = (row: any, index: any) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
isEdit.value = true
|
||||||
|
currentItem.value = cloneDeep(row)
|
||||||
|
currentIndex.value = index
|
||||||
|
} else {
|
||||||
|
currentItem.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
isEdit.value = false
|
||||||
|
currentIndex.value = null
|
||||||
|
currentItem.value = null as any
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
const formEl = DynamicsFormConstructorRef.value
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate().then(() => {
|
||||||
|
emit('refresh', formEl?.getData(), currentIndex.value)
|
||||||
|
isEdit.value = false
|
||||||
|
currentItem.value = null as any
|
||||||
|
currentIndex.value = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, close })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex-between mb-16">
|
||||||
|
<h5 class="break-all ellipsis lighter" style="max-width:80%" :title="inputFieldConfig.title">
|
||||||
|
{{ inputFieldConfig.title }}
|
||||||
|
</h5>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" link @click="openChangeTitleDialog">
|
||||||
|
<el-icon>
|
||||||
|
<Setting />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
<span class="ml-4">
|
||||||
|
<el-button link type="primary" @click="openAddDialog()">
|
||||||
|
<el-icon class="mr-4">
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
{{ $t('common.add') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
v-if="props.nodeModel.properties.user_input_field_list?.length > 0"
|
||||||
|
:data="props.nodeModel.properties.user_input_field_list"
|
||||||
|
class="mb-16"
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="field"
|
||||||
|
>
|
||||||
|
<el-table-column prop="field" :label="$t('dynamicsForm.paramForm.field.label')" width="95">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :title="row.field" class="ellipsis-1">{{ row.field }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="label" :label="$t('dynamicsForm.paramForm.name.label')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.label && row.label.input_type === 'TooltipLabel'">
|
||||||
|
<span :title="row.label.label" class="ellipsis-1">
|
||||||
|
{{ row.label.label }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<span :title="row.label" class="ellipsis-1">
|
||||||
|
{{ row.label }}
|
||||||
|
</span></span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('dynamicsForm.paramForm.input_type.label')" width="95">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag type="info" class="info-tag" v-if="row.input_type === 'TextInput'">{{
|
||||||
|
$t('dynamicsForm.input_type_list.TextInput')
|
||||||
|
}}</el-tag>
|
||||||
|
<el-tag type="info" class="info-tag" v-if="row.input_type === 'PasswordInput'">{{
|
||||||
|
$t('dynamicsForm.input_type_list.PasswordInput')
|
||||||
|
}}</el-tag>
|
||||||
|
<el-tag type="info" class="info-tag" v-if="row.input_type === 'Slider'">{{
|
||||||
|
$t('dynamicsForm.input_type_list.Slider')
|
||||||
|
}}</el-tag>
|
||||||
|
<el-tag type="info" class="info-tag" v-if="row.input_type === 'SwitchInput'">{{
|
||||||
|
$t('dynamicsForm.input_type_list.SwitchInput')
|
||||||
|
}}</el-tag>
|
||||||
|
<el-tag type="info" class="info-tag" v-if="row.input_type === 'SingleSelect'">{{
|
||||||
|
$t('dynamicsForm.input_type_list.SingleSelect')
|
||||||
|
}}</el-tag>
|
||||||
|
<el-tag type="info" class="info-tag" v-if="row.input_type === 'MultiSelect'">{{
|
||||||
|
$t('dynamicsForm.input_type_list.MultiSelect')
|
||||||
|
}}</el-tag>
|
||||||
|
<el-tag type="info" class="info-tag" v-if="row.input_type === 'RadioCard'">{{
|
||||||
|
$t('dynamicsForm.input_type_list.RadioCard')
|
||||||
|
}}</el-tag>
|
||||||
|
<el-tag type="info" class="info-tag" v-if="row.input_type === 'DatePicker'">{{
|
||||||
|
$t('dynamicsForm.input_type_list.DatePicker')
|
||||||
|
}}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="default_value" :label="$t('dynamicsForm.default.label')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :title="row.default_value" class="ellipsis-1">{{ getDefaultValue(row) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.required')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div @click.stop>
|
||||||
|
<el-switch disabled size="small" v-model="row.required" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.operation')" align="left" width="90">
|
||||||
|
<template #default="{ row, $index }">
|
||||||
|
<span class="mr-4">
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
|
||||||
|
<el-button type="primary" text @click.stop="openAddDialog(row, $index)">
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
|
||||||
|
<el-button type="primary" text @click="deleteField($index)">
|
||||||
|
<el-icon>
|
||||||
|
<Delete />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<UserFieldFormDialog ref="UserFieldFormDialogRef" @refresh="refreshFieldList" />
|
||||||
|
<UserInputTitleDialog ref="UserInputTitleDialogRef" @refresh="refreshFieldTitle" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { set } from 'lodash'
|
||||||
|
import Sortable from 'sortablejs'
|
||||||
|
import UserFieldFormDialog from './UserFieldFormDialog.vue'
|
||||||
|
import { MsgError } from '@/utils/message'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import UserInputTitleDialog from '@/workflow/nodes/base-node/component/UserInputTitleDialog.vue'
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
|
||||||
|
const tableRef = ref()
|
||||||
|
const UserFieldFormDialogRef = ref()
|
||||||
|
const UserInputTitleDialogRef = ref()
|
||||||
|
const inputFieldList = ref<any[]>([])
|
||||||
|
const inputFieldConfig = ref({ title: t('chat.userInput') })
|
||||||
|
|
||||||
|
function openAddDialog(data?: any, index?: any) {
|
||||||
|
UserFieldFormDialogRef.value.open(data, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openChangeTitleDialog() {
|
||||||
|
UserInputTitleDialogRef.value.open(inputFieldConfig.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteField(index: any) {
|
||||||
|
inputFieldList.value.splice(index, 1)
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')
|
||||||
|
onDragHandle()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFieldList(data: any, index: any) {
|
||||||
|
for (let i = 0; i < inputFieldList.value.length; i++) {
|
||||||
|
if (inputFieldList.value[i].field === data.field && index !== i) {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.paramErrorMessage') + data.field)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 查看另一个list又没有重复的
|
||||||
|
let arr = props.nodeModel.properties.api_input_field_list
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i].variable === data.field) {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.paramErrorMessage') + data.field)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index !== null) {
|
||||||
|
inputFieldList.value.splice(index, 1, data)
|
||||||
|
} else {
|
||||||
|
inputFieldList.value.push(data)
|
||||||
|
}
|
||||||
|
UserFieldFormDialogRef.value.close()
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')
|
||||||
|
onDragHandle()
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFieldTitle(data: any) {
|
||||||
|
inputFieldConfig.value = data
|
||||||
|
UserInputTitleDialogRef.value.close()
|
||||||
|
|
||||||
|
// console.log('inputFieldConfig', inputFieldConfig.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultValue = (row: any) => {
|
||||||
|
if (row.input_type === 'PasswordInput') {
|
||||||
|
return '******'
|
||||||
|
}
|
||||||
|
if (row.default_value) {
|
||||||
|
const default_value = row.option_list
|
||||||
|
?.filter((v: any) => row.default_value.indexOf(v.value) > -1)
|
||||||
|
.map((v: any) => v.label)
|
||||||
|
.join(',')
|
||||||
|
if (default_value) {
|
||||||
|
return default_value
|
||||||
|
}
|
||||||
|
return row.default_value
|
||||||
|
}
|
||||||
|
if (row.default_value !== undefined) {
|
||||||
|
return row.default_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragHandle() {
|
||||||
|
if (!tableRef.value) return
|
||||||
|
|
||||||
|
// 获取表格的 tbody DOM 元素
|
||||||
|
const wrapper = tableRef.value.$el as HTMLElement
|
||||||
|
const tbody = wrapper.querySelector('.el-table__body-wrapper tbody')
|
||||||
|
if (!tbody) return
|
||||||
|
// 初始化 Sortable
|
||||||
|
Sortable.create(tbody as HTMLElement, {
|
||||||
|
animation: 150,
|
||||||
|
ghostClass: 'ghost-row',
|
||||||
|
onEnd: (evt) => {
|
||||||
|
if (evt.oldIndex === undefined || evt.newIndex === undefined) return
|
||||||
|
// 更新数据顺序
|
||||||
|
const items = [...inputFieldList.value]
|
||||||
|
const [movedItem] = items.splice(evt.oldIndex, 1)
|
||||||
|
items.splice(evt.newIndex, 0, movedItem)
|
||||||
|
inputFieldList.value = items
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.nodeModel.properties.user_input_field_list) {
|
||||||
|
if (props.nodeModel.properties.input_field_list) {
|
||||||
|
props.nodeModel.properties.input_field_list
|
||||||
|
.filter((item: any) => {
|
||||||
|
return item.assignment_method === 'user_input'
|
||||||
|
})
|
||||||
|
.forEach((item: any) => {
|
||||||
|
inputFieldList.value.push(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inputFieldList.value.push(...props.nodeModel.properties.user_input_field_list)
|
||||||
|
}
|
||||||
|
// 兼容旧数据
|
||||||
|
inputFieldList.value.forEach((item, index) => {
|
||||||
|
item.label = item.label || item.name
|
||||||
|
item.field = item.field || item.variable
|
||||||
|
item.required = item.required || item.is_required
|
||||||
|
switch (item.type) {
|
||||||
|
case 'input':
|
||||||
|
item.input_type = 'TextInput'
|
||||||
|
break
|
||||||
|
case 'select':
|
||||||
|
item.input_type = 'SingleSelect'
|
||||||
|
break
|
||||||
|
case 'date':
|
||||||
|
item.input_type = 'DatePicker'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
set(props.nodeModel.properties, 'user_input_field_list', inputFieldList)
|
||||||
|
if (props.nodeModel.properties.user_input_config) {
|
||||||
|
inputFieldConfig.value = props.nodeModel.properties.user_input_config
|
||||||
|
}
|
||||||
|
set(props.nodeModel.properties, 'user_input_config', inputFieldConfig)
|
||||||
|
onDragHandle()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('common.setting')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:before-close="close"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
ref="fieldFormRef"
|
||||||
|
:rules="rules"
|
||||||
|
:model="form"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item :label="$t('common.title')" prop="title">
|
||||||
|
<el-input
|
||||||
|
v-model="form.title"
|
||||||
|
maxlength="64"
|
||||||
|
show-word-limit
|
||||||
|
@blur="form.title = form.title.trim()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const fieldFormRef = ref()
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
|
||||||
|
const form = ref<any>({
|
||||||
|
title: t('chat.userInput')
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
title: [
|
||||||
|
{ required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
|
||||||
|
const open = (row: any) => {
|
||||||
|
if (row) {
|
||||||
|
form.value = cloneDeep(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
emit('refresh', form.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, close })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import BaseNodeVue from './index.vue'
|
||||||
|
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
|
||||||
|
|
||||||
|
class BaseNode extends AppNode {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props, BaseNodeVue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseModel extends AppNodeModel {
|
||||||
|
constructor(data: any, graphModel: any) {
|
||||||
|
super(data, graphModel)
|
||||||
|
}
|
||||||
|
get_width() {
|
||||||
|
return 600
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
type: 'base-node',
|
||||||
|
model: BaseModel,
|
||||||
|
view: BaseNode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,349 @@
|
||||||
|
<template>
|
||||||
|
<NodeContainer :nodeModel="nodeModel">
|
||||||
|
<el-form
|
||||||
|
@submit.prevent
|
||||||
|
:model="form_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
class="mb-24"
|
||||||
|
label-width="auto"
|
||||||
|
ref="baseNodeFormRef"
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.baseNode.appName.label')"
|
||||||
|
prop="name"
|
||||||
|
:rules="{
|
||||||
|
message: t('views.application.applicationForm.form.appName.requiredMessage'),
|
||||||
|
trigger: 'blur',
|
||||||
|
required: true
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="form_data.name"
|
||||||
|
maxlength="64"
|
||||||
|
:placeholder="t('views.application.applicationForm.form.appName.placeholder')"
|
||||||
|
show-word-limit
|
||||||
|
@blur="form_data.name = form_data.name?.trim()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('views.applicationWorkflow.nodes.baseNode.appDescription.label')">
|
||||||
|
<el-input
|
||||||
|
v-model="form_data.desc"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.appDescription.placeholder')"
|
||||||
|
:rows="3"
|
||||||
|
type="textarea"
|
||||||
|
maxlength="256"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('views.application.applicationForm.form.prologue')">
|
||||||
|
<MdEditorMagnify
|
||||||
|
@wheel="wheel"
|
||||||
|
:title="$t('views.application.applicationForm.form.prologue')"
|
||||||
|
v-model="form_data.prologue"
|
||||||
|
style="height: 150px"
|
||||||
|
@submitDialog="submitDialog"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<span class="mr-4">{{
|
||||||
|
$t('views.applicationWorkflow.nodes.baseNode.fileUpload.label')
|
||||||
|
}}</span>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="$t('views.applicationWorkflow.nodes.baseNode.fileUpload.tooltip')"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button
|
||||||
|
v-if="form_data.file_upload_enable"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="openFileUploadSettingDialog"
|
||||||
|
class="mr-4"
|
||||||
|
>
|
||||||
|
<el-icon class="mr-4">
|
||||||
|
<Setting />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-switch
|
||||||
|
size="small"
|
||||||
|
v-model="form_data.file_upload_enable"
|
||||||
|
@change="switchFileUpload"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-form-item>
|
||||||
|
<UserInputFieldTable ref="UserInputFieldTableFef" :node-model="nodeModel" />
|
||||||
|
<ApiInputFieldTable ref="ApiInputFieldTableFef" :node-model="nodeModel" />
|
||||||
|
<el-form-item>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span class="mr-4">{{
|
||||||
|
$t('views.application.applicationForm.form.voiceInput.label')
|
||||||
|
}}</span>
|
||||||
|
<div class="flex">
|
||||||
|
<el-checkbox v-if="form_data.stt_model_enable" v-model="form_data.stt_autosend">{{
|
||||||
|
$t('views.application.applicationForm.form.voiceInput.autoSend')
|
||||||
|
}}</el-checkbox>
|
||||||
|
<el-switch
|
||||||
|
class="ml-8"
|
||||||
|
size="small"
|
||||||
|
v-model="form_data.stt_model_enable"
|
||||||
|
@change="sttModelEnableChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<ModelSelect
|
||||||
|
@wheel="wheel"
|
||||||
|
v-show="form_data.stt_model_enable"
|
||||||
|
v-model="form_data.stt_model_id"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.voiceInput.placeholder')"
|
||||||
|
:options="sttModelOptions"
|
||||||
|
showFooter
|
||||||
|
:model-type="'STT'"
|
||||||
|
></ModelSelect>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<span class="mr-4">{{
|
||||||
|
$t('views.application.applicationForm.form.voicePlay.label')
|
||||||
|
}}</span>
|
||||||
|
<div class="flex">
|
||||||
|
<el-checkbox v-if="form_data.tts_model_enable" v-model="form_data.tts_autoplay">{{
|
||||||
|
$t('views.application.applicationForm.form.voicePlay.autoPlay')
|
||||||
|
}}</el-checkbox>
|
||||||
|
<el-switch
|
||||||
|
class="ml-8"
|
||||||
|
size="small"
|
||||||
|
v-model="form_data.tts_model_enable"
|
||||||
|
@change="ttsModelEnableChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="w-full">
|
||||||
|
<el-radio-group v-model="form_data.tts_type" v-show="form_data.tts_model_enable">
|
||||||
|
<el-radio
|
||||||
|
:label="$t('views.application.applicationForm.form.voicePlay.browser')"
|
||||||
|
value="BROWSER"
|
||||||
|
/>
|
||||||
|
<el-radio
|
||||||
|
:label="$t('views.application.applicationForm.form.voicePlay.tts')"
|
||||||
|
value="TTS"
|
||||||
|
/>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
<div class="flex-between w-full">
|
||||||
|
<ModelSelect
|
||||||
|
@wheel="wheel"
|
||||||
|
v-if="form_data.tts_type === 'TTS' && form_data.tts_model_enable"
|
||||||
|
v-model="form_data.tts_model_id"
|
||||||
|
:placeholder="$t('views.application.applicationForm.form.voicePlay.placeholder')"
|
||||||
|
:options="ttsModelOptions"
|
||||||
|
@change="ttsModelChange()"
|
||||||
|
showFooter
|
||||||
|
:model-type="'TTS'"
|
||||||
|
></ModelSelect>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
v-if="form_data.tts_type === 'TTS' && form_data.tts_model_enable"
|
||||||
|
@click="openTTSParamSettingDialog"
|
||||||
|
:disabled="!form_data.tts_model_id"
|
||||||
|
class="ml-8"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<el-icon><Operation /></el-icon>
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<TTSModeParamSettingDialog ref="TTSModeParamSettingDialogRef" @refresh="refreshTTSForm" />
|
||||||
|
<FileUploadSettingDialog
|
||||||
|
ref="FileUploadSettingDialogRef"
|
||||||
|
:node-model="nodeModel"
|
||||||
|
@refresh="refreshFileUploadForm"
|
||||||
|
/>
|
||||||
|
</NodeContainer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { app } from '@/main'
|
||||||
|
import { groupBy, set } from 'lodash'
|
||||||
|
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import applicationApi from '@/api/application'
|
||||||
|
import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import TTSModeParamSettingDialog from '@/views/application/component/TTSModeParamSettingDialog.vue'
|
||||||
|
import ApiInputFieldTable from './component/ApiInputFieldTable.vue'
|
||||||
|
import UserInputFieldTable from './component/UserInputFieldTable.vue'
|
||||||
|
import FileUploadSettingDialog from '@/workflow/nodes/base-node/component/FileUploadSettingDialog.vue'
|
||||||
|
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = app.config.globalProperties.$route as any
|
||||||
|
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
|
||||||
|
const sttModelOptions = ref<any>(null)
|
||||||
|
const ttsModelOptions = ref<any>(null)
|
||||||
|
const TTSModeParamSettingDialogRef = ref<InstanceType<typeof TTSModeParamSettingDialog>>()
|
||||||
|
const UserInputFieldTableFef = ref()
|
||||||
|
const ApiInputFieldTableFef = ref()
|
||||||
|
const FileUploadSettingDialogRef = ref<InstanceType<typeof FileUploadSettingDialog>>()
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
name: '',
|
||||||
|
desc: '',
|
||||||
|
prologue: t('views.application.applicationForm.form.defaultPrologue')
|
||||||
|
}
|
||||||
|
|
||||||
|
const wheel = (e: any) => {
|
||||||
|
if (e.ctrlKey === true) {
|
||||||
|
e.preventDefault()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
e.stopPropagation()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitDialog(val: string) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'prologue', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
const form_data = computed({
|
||||||
|
get: () => {
|
||||||
|
if (props.nodeModel.properties.node_data) {
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
} else {
|
||||||
|
set(props.nodeModel.properties, 'node_data', form)
|
||||||
|
}
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
set(props.nodeModel.properties, 'node_data', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const baseNodeFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
if (
|
||||||
|
form_data.value.tts_model_enable &&
|
||||||
|
!form_data.value.tts_model_id &&
|
||||||
|
form_data.value.tts_type === 'TTS'
|
||||||
|
) {
|
||||||
|
return Promise.reject({
|
||||||
|
node: props.nodeModel,
|
||||||
|
errMessage: t('views.application.applicationForm.form.voicePlay.requiredMessage')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (form_data.value.stt_model_enable && !form_data.value.stt_model_id) {
|
||||||
|
return Promise.reject({
|
||||||
|
node: props.nodeModel,
|
||||||
|
errMessage: t('views.application.applicationForm.form.voiceInput.requiredMessage')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return baseNodeFormRef.value?.validate().catch((err) => {
|
||||||
|
return Promise.reject({ node: props.nodeModel, errMessage: err })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSTTModel() {
|
||||||
|
applicationApi.getApplicationSTTModel(id).then((res: any) => {
|
||||||
|
sttModelOptions.value = groupBy(res?.data, 'provider')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTTSModel() {
|
||||||
|
applicationApi.getApplicationTTSModel(id).then((res: any) => {
|
||||||
|
ttsModelOptions.value = groupBy(res?.data, 'provider')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ttsModelChange() {
|
||||||
|
if (form_data.value.tts_model_id) {
|
||||||
|
TTSModeParamSettingDialogRef.value?.reset_default(form_data.value.tts_model_id, id)
|
||||||
|
} else {
|
||||||
|
refreshTTSForm({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ttsModelEnableChange() {
|
||||||
|
if (!form_data.value.tts_model_enable) {
|
||||||
|
form_data.value.tts_model_id = ''
|
||||||
|
form_data.value.tts_type = 'BROWSER'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sttModelEnableChange() {
|
||||||
|
if (!form_data.value.stt_model_enable) {
|
||||||
|
form_data.value.stt_model_id = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openTTSParamSettingDialog = () => {
|
||||||
|
const model_id = form_data.value.tts_model_id
|
||||||
|
if (!model_id) {
|
||||||
|
MsgSuccess(t('views.application.applicationForm.form.voicePlay.requiredMessage'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TTSModeParamSettingDialogRef.value?.open(model_id, id, form_data.value.tts_model_params_setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshTTSForm = (data: any) => {
|
||||||
|
form_data.value.tts_model_params_setting = data
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchFileUpload = () => {
|
||||||
|
const default_upload_setting = {
|
||||||
|
maxFiles: 3,
|
||||||
|
fileLimit: 50,
|
||||||
|
document: true,
|
||||||
|
image: false,
|
||||||
|
audio: false,
|
||||||
|
video: false,
|
||||||
|
other: false,
|
||||||
|
otherExtensions: ['ppt', 'doc']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form_data.value.file_upload_enable) {
|
||||||
|
form_data.value.file_upload_setting =
|
||||||
|
form_data.value.file_upload_setting || default_upload_setting
|
||||||
|
}
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refreshFileUploadConfig')
|
||||||
|
}
|
||||||
|
const openFileUploadSettingDialog = () => {
|
||||||
|
FileUploadSettingDialogRef.value?.open(form_data.value.file_upload_setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshFileUploadForm = (data: any) => {
|
||||||
|
form_data.value.file_upload_setting = data
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
set(props.nodeModel, 'validate', validate)
|
||||||
|
if (!props.nodeModel.properties.node_data.tts_type) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'tts_type', 'BROWSER')
|
||||||
|
}
|
||||||
|
getTTSModel()
|
||||||
|
getSTTModel()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import ConditioNodeVue from './index.vue'
|
||||||
|
import { cloneDeep, set } from 'lodash'
|
||||||
|
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
|
||||||
|
class ConditioNode extends AppNode {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props, ConditioNodeVue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const get_up_index_height = (condition_list: Array<any>, index: number) => {
|
||||||
|
return condition_list
|
||||||
|
.filter((item, i) => i < index)
|
||||||
|
.map((item) => item.height + 8)
|
||||||
|
.reduce((x, y) => x + y, 0)
|
||||||
|
}
|
||||||
|
class ConditionModel extends AppNodeModel {
|
||||||
|
refreshBranch() {
|
||||||
|
// 更新节点连接边的path
|
||||||
|
this.incoming.edges.forEach((edge: any) => {
|
||||||
|
// 调用自定义的更新方案
|
||||||
|
edge.updatePathByAnchor()
|
||||||
|
})
|
||||||
|
this.outgoing.edges.forEach((edge: any) => {
|
||||||
|
edge.updatePathByAnchor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getDefaultAnchor() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
properties: { branch_condition_list }
|
||||||
|
} = this
|
||||||
|
if (this.height === undefined) {
|
||||||
|
this.height = 200
|
||||||
|
}
|
||||||
|
const showNode = this.properties.showNode === undefined ? true : this.properties.showNode
|
||||||
|
const anchors: any = []
|
||||||
|
anchors.push({
|
||||||
|
x: x - width / 2 + 10,
|
||||||
|
y: showNode ? y : y - 15,
|
||||||
|
id: `${id}_left`,
|
||||||
|
edgeAddable: false,
|
||||||
|
type: 'left'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (branch_condition_list) {
|
||||||
|
for (let index = 0; index < branch_condition_list.length; index++) {
|
||||||
|
const element = branch_condition_list[index]
|
||||||
|
const h = get_up_index_height(branch_condition_list, index)
|
||||||
|
anchors.push({
|
||||||
|
x: x + width / 2 - 10,
|
||||||
|
y: showNode ? y - height / 2 + 75 + h + element.height / 2 : y - 15,
|
||||||
|
id: `${id}_${element.id}_right`,
|
||||||
|
type: 'right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
type: 'condition-node',
|
||||||
|
model: ConditionModel,
|
||||||
|
view: ConditioNode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,370 @@
|
||||||
|
<template>
|
||||||
|
<NodeContainer :nodeModel="nodeModel">
|
||||||
|
<el-form
|
||||||
|
:model="form_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
ref="ConditionNodeFormRef"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<VueDraggable
|
||||||
|
ref="el"
|
||||||
|
v-bind:modelValue="form_data.branch"
|
||||||
|
:disabled="form_data.branch === 2"
|
||||||
|
:filter="'.no-drag'"
|
||||||
|
handle=".handle"
|
||||||
|
:animation="150"
|
||||||
|
ghostClass="ghost"
|
||||||
|
@end="onEnd"
|
||||||
|
>
|
||||||
|
<template v-for="(item, index) in form_data.branch" :key="item.id">
|
||||||
|
<el-card
|
||||||
|
v-resize="(wh: any) => resizeCondition(wh, item, index)"
|
||||||
|
shadow="never"
|
||||||
|
class="drag-card card-never mb-8"
|
||||||
|
:class="{
|
||||||
|
'no-drag': index === form_data.branch.length - 1 || form_data.branch.length === 2
|
||||||
|
}"
|
||||||
|
style="--el-card-padding: 12px"
|
||||||
|
>
|
||||||
|
<div class="handle flex-between lighter">
|
||||||
|
<span class="flex align-center">
|
||||||
|
<img src="@/assets/sort.svg" alt="" height="15" class="handle-img mr-4" />
|
||||||
|
{{ item.type }}
|
||||||
|
</span>
|
||||||
|
<div class="info" v-if="item.conditions.length > 1">
|
||||||
|
<span>{{
|
||||||
|
$t('views.applicationWorkflow.nodes.conditionNode.conditions.info')
|
||||||
|
}}</span>
|
||||||
|
<el-select
|
||||||
|
:teleported="false"
|
||||||
|
v-model="item.condition"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px; margin: 0 8px"
|
||||||
|
>
|
||||||
|
<el-option :label="$t('views.applicationWorkflow.condition.AND')" value="and" />
|
||||||
|
<el-option :label="$t('views.applicationWorkflow.condition.OR')" value="or" />
|
||||||
|
</el-select>
|
||||||
|
<span>{{
|
||||||
|
$t('views.applicationWorkflow.nodes.conditionNode.conditions.label')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="index !== form_data.branch.length - 1" class="mt-8">
|
||||||
|
<template v-for="(condition, cIndex) in item.conditions" :key="cIndex">
|
||||||
|
<el-row :gutter="8">
|
||||||
|
<el-col :span="11">
|
||||||
|
<el-form-item
|
||||||
|
:prop="'branch.' + index + '.conditions.' + cIndex + '.field'"
|
||||||
|
:rules="{
|
||||||
|
type: 'array',
|
||||||
|
required: true,
|
||||||
|
message: $t('views.applicationWorkflow.variable.placeholder'),
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NodeCascader
|
||||||
|
ref="nodeCascaderRef"
|
||||||
|
:nodeModel="nodeModel"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="$t('views.applicationWorkflow.variable.placeholder')"
|
||||||
|
v-model="condition.field"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item
|
||||||
|
:prop="'branch.' + index + '.conditions.' + cIndex + '.compare'"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: $t(
|
||||||
|
'views.applicationWorkflow.nodes.conditionNode.conditions.requiredMessage'
|
||||||
|
),
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
@wheel="wheel"
|
||||||
|
:teleported="false"
|
||||||
|
v-model="condition.compare"
|
||||||
|
:placeholder="
|
||||||
|
$t(
|
||||||
|
'views.applicationWorkflow.nodes.conditionNode.conditions.requiredMessage'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
clearable
|
||||||
|
@change="changeCondition($event, index, cIndex)"
|
||||||
|
>
|
||||||
|
<template v-for="(item, index) in compareList" :key="index">
|
||||||
|
<el-option :label="item.label" :value="item.value" />
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item
|
||||||
|
v-if="
|
||||||
|
!['is_null', 'is_not_null', 'is_true', 'is_not_true'].includes(
|
||||||
|
condition.compare
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:prop="'branch.' + index + '.conditions.' + cIndex + '.value'"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: $t('views.applicationWorkflow.nodes.conditionNode.valueMessage'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="condition.value"
|
||||||
|
:placeholder="
|
||||||
|
$t('views.applicationWorkflow.nodes.conditionNode.valueMessage')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1">
|
||||||
|
<el-button
|
||||||
|
:disabled="index === 0 && cIndex === 0"
|
||||||
|
link
|
||||||
|
type="info"
|
||||||
|
class="mt-4"
|
||||||
|
@click="deleteCondition(index, cIndex)"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="addCondition(index)"
|
||||||
|
v-if="index !== form_data.branch.length - 1"
|
||||||
|
>
|
||||||
|
<el-icon class="mr-4"><Plus /></el-icon>
|
||||||
|
{{ $t('views.applicationWorkflow.nodes.conditionNode.addCondition') }}
|
||||||
|
</el-button>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
</VueDraggable>
|
||||||
|
<el-button link type="primary" @click="addBranch">
|
||||||
|
<el-icon class="mr-4"><Plus /></el-icon>
|
||||||
|
{{ $t('views.applicationWorkflow.nodes.conditionNode.addBranch') }}
|
||||||
|
</el-button>
|
||||||
|
</el-form>
|
||||||
|
</NodeContainer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { cloneDeep, set } from 'lodash'
|
||||||
|
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||||
|
import NodeCascader from '@/workflow/common/NodeCascader.vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
|
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||||
|
import { randomId } from '@/utils/utils'
|
||||||
|
import { compareList } from '@/workflow/common/data'
|
||||||
|
import { VueDraggable } from 'vue-draggable-plus'
|
||||||
|
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
branch: [
|
||||||
|
{
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
field: [],
|
||||||
|
compare: '',
|
||||||
|
value: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
id: randomId(),
|
||||||
|
type: 'IF',
|
||||||
|
condition: 'and'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conditions: [],
|
||||||
|
id: randomId(),
|
||||||
|
type: 'ELSE',
|
||||||
|
condition: 'and'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const wheel = (e: any) => {
|
||||||
|
if (e.ctrlKey === true) {
|
||||||
|
e.preventDefault()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
e.stopPropagation()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const resizeCondition = (wh: any, row: any, index: number) => {
|
||||||
|
const branch_condition_list = cloneDeep(
|
||||||
|
props.nodeModel.properties.branch_condition_list
|
||||||
|
? props.nodeModel.properties.branch_condition_list
|
||||||
|
: []
|
||||||
|
)
|
||||||
|
const new_branch_condition_list = branch_condition_list.map((item: any) => {
|
||||||
|
if (item.id === row.id) {
|
||||||
|
return { ...item, height: wh.height, index: index }
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
set(props.nodeModel.properties, 'branch_condition_list', new_branch_condition_list)
|
||||||
|
refreshBranchAnchor(props.nodeModel.properties.node_data.branch, true)
|
||||||
|
}
|
||||||
|
const form_data = computed({
|
||||||
|
get: () => {
|
||||||
|
if (props.nodeModel.properties.node_data) {
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
} else {
|
||||||
|
set(props.nodeModel.properties, 'node_data', form)
|
||||||
|
refreshBranchAnchor(form.branch, true)
|
||||||
|
}
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
set(props.nodeModel.properties, 'node_data', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ConditionNodeFormRef = ref<FormInstance>()
|
||||||
|
const nodeCascaderRef = ref()
|
||||||
|
const validate = () => {
|
||||||
|
const v_list = [
|
||||||
|
ConditionNodeFormRef.value?.validate(),
|
||||||
|
...nodeCascaderRef.value.map((item: any) => item.validate())
|
||||||
|
]
|
||||||
|
return Promise.all(v_list).catch((err) => {
|
||||||
|
return Promise.reject({ node: props.nodeModel, errMessage: err })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEnd(event?: any) {
|
||||||
|
const { oldIndex, newIndex } = event
|
||||||
|
if (oldIndex === undefined || newIndex === undefined) return
|
||||||
|
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
|
||||||
|
if (oldIndex === list.length - 1 || newIndex === list.length - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newInstance = { ...list[oldIndex], type: list[newIndex].type, id: list[newIndex].id }
|
||||||
|
const oldInstance = { ...list[newIndex], type: list[oldIndex].type, id: list[oldIndex].id }
|
||||||
|
list[newIndex] = newInstance
|
||||||
|
list[oldIndex] = oldInstance
|
||||||
|
set(props.nodeModel.properties.node_data, 'branch', list)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBranch() {
|
||||||
|
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
|
||||||
|
const obj = {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
field: [],
|
||||||
|
compare: '',
|
||||||
|
value: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
type: 'ELSE IF ' + (list.length - 1),
|
||||||
|
id: randomId(),
|
||||||
|
condition: 'and'
|
||||||
|
}
|
||||||
|
list.splice(list.length - 1, 0, obj)
|
||||||
|
refreshBranchAnchor(list, true)
|
||||||
|
set(props.nodeModel.properties.node_data, 'branch', list)
|
||||||
|
}
|
||||||
|
function refreshBranchAnchor(list: Array<any>, is_add: boolean) {
|
||||||
|
const branch_condition_list = cloneDeep(
|
||||||
|
props.nodeModel.properties.branch_condition_list
|
||||||
|
? props.nodeModel.properties.branch_condition_list
|
||||||
|
: []
|
||||||
|
)
|
||||||
|
const new_branch_condition_list = list
|
||||||
|
.map((item, index) => {
|
||||||
|
const find = branch_condition_list.find((b: any) => b.id === item.id)
|
||||||
|
if (find) {
|
||||||
|
return { index: index, height: find.height, id: item.id }
|
||||||
|
} else {
|
||||||
|
if (is_add) {
|
||||||
|
return { index: index, height: 12, id: item.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item) => item)
|
||||||
|
|
||||||
|
set(props.nodeModel.properties, 'branch_condition_list', new_branch_condition_list)
|
||||||
|
props.nodeModel.refreshBranch()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCondition(index: number) {
|
||||||
|
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
|
||||||
|
list[index]['conditions'].push({
|
||||||
|
field: [],
|
||||||
|
compare: '',
|
||||||
|
value: ''
|
||||||
|
})
|
||||||
|
set(props.nodeModel.properties.node_data, 'branch', list)
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCondition(index: number, cIndex: number) {
|
||||||
|
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
|
||||||
|
list[index]['conditions'].splice(cIndex, 1)
|
||||||
|
if (list[index]['conditions'].length === 0) {
|
||||||
|
const delete_edge = list.splice(index, 1)
|
||||||
|
const delete_target_anchor_id_list = delete_edge.map(
|
||||||
|
(item: any) => props.nodeModel.id + '_' + item.id + '_right'
|
||||||
|
)
|
||||||
|
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit(
|
||||||
|
'delete_edge',
|
||||||
|
props.nodeModel.outgoing.edges
|
||||||
|
.filter((item: any) => delete_target_anchor_id_list.includes(item.sourceAnchorId))
|
||||||
|
.map((item: any) => item.id)
|
||||||
|
)
|
||||||
|
refreshBranchAnchor(list, false)
|
||||||
|
|
||||||
|
list.forEach((item: any, index: number) => {
|
||||||
|
if (item.type === 'ELSE IF ' + (index + 1)) {
|
||||||
|
item.type = 'ELSE IF ' + index
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
set(props.nodeModel.properties.node_data, 'branch', list)
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeCondition(val: string, index: number, cIndex: number) {
|
||||||
|
if (['is_null', 'is_not_null', 'is_true', 'is_not_true'].includes(val)) {
|
||||||
|
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
|
||||||
|
list[index]['conditions'][cIndex].value = 1
|
||||||
|
set(props.nodeModel.properties.node_data, 'branch', list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
set(props.nodeModel, 'validate', validate)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.drag-card.no-drag {
|
||||||
|
.handle {
|
||||||
|
.handle-img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drag-card:not(.no-drag) {
|
||||||
|
.handle {
|
||||||
|
.handle-img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.handle-img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import DocumentExtractNodeVue from './index.vue'
|
||||||
|
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
|
||||||
|
class RerankerNode extends AppNode {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props, DocumentExtractNodeVue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
type: 'document-extract-node',
|
||||||
|
model: AppNodeModel,
|
||||||
|
view: RerankerNode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
<template>
|
||||||
|
<NodeContainer :nodeModel="nodeModel">
|
||||||
|
<h5 class="title-decoration-1 mb-8">{{ $t('views.applicationWorkflow.nodeSetting') }}</h5>
|
||||||
|
<el-card shadow="never" class="card-never">
|
||||||
|
<el-form
|
||||||
|
@submit.prevent
|
||||||
|
:model="form_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
ref="DatasetNodeFormRef"
|
||||||
|
>
|
||||||
|
<el-form-item :label="$t('views.problem.relateParagraph.selectDocument')" :rules="{
|
||||||
|
type: 'array',
|
||||||
|
required: true,
|
||||||
|
message: $t('views.log.documentPlaceholder'),
|
||||||
|
trigger: 'change'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NodeCascader
|
||||||
|
ref="nodeCascaderRef"
|
||||||
|
:nodeModel="nodeModel"
|
||||||
|
class="w-full"
|
||||||
|
:placeholder="$t('views.log.documentPlaceholder')"
|
||||||
|
v-model="form_data.document_list"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</NodeContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { set } from 'lodash'
|
||||||
|
import NodeCascader from '@/workflow/common/NodeCascader.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
document_list: ["start-node", "document"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const form_data = computed({
|
||||||
|
get: () => {
|
||||||
|
if (props.nodeModel.properties.node_data) {
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
} else {
|
||||||
|
set(props.nodeModel.properties, 'node_data', form)
|
||||||
|
}
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
set(props.nodeModel.properties, 'node_data', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import FormNodeVue from './index.vue'
|
||||||
|
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
|
||||||
|
class FormNode extends AppNode {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props, FormNodeVue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
type: 'form-node',
|
||||||
|
model: AppNodeModel,
|
||||||
|
view: FormNode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
<template>
|
||||||
|
<NodeContainer :nodeModel="nodeModel">
|
||||||
|
<h5 class="title-decoration-1 mb-8">{{ $t('views.applicationWorkflow.nodeSetting') }}</h5>
|
||||||
|
<el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
|
||||||
|
<el-form
|
||||||
|
@submit.prevent
|
||||||
|
:model="form_data"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
ref="formNodeFormRef"
|
||||||
|
hide-required-asterisk
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.formNode.formContent.label')"
|
||||||
|
prop="form_content_format"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: $t('views.applicationWorkflow.nodes.formNode.formContent.requiredMessage'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex align-center">
|
||||||
|
<div class="mr-4">
|
||||||
|
<span
|
||||||
|
>{{ $t('views.applicationWorkflow.nodes.formNode.formContent.label')
|
||||||
|
}}<span class="danger">*</span></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<el-tooltip effect="dark" placement="right" popper-class="max-w-200">
|
||||||
|
<template #content>
|
||||||
|
{{
|
||||||
|
$t('views.applicationWorkflow.nodes.formNode.formContent.tooltip', {
|
||||||
|
form: '{ form }'
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<MdEditorMagnify
|
||||||
|
:title="$t('views.applicationWorkflow.nodes.formNode.formContent.label')"
|
||||||
|
v-model="form_data.form_content_format"
|
||||||
|
style="height: 150px"
|
||||||
|
@submitDialog="submitDialog"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('views.applicationWorkflow.nodes.formNode.formSetting')"
|
||||||
|
@click.prevent
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-between">
|
||||||
|
<h5 class="lighter">
|
||||||
|
{{ $t('views.applicationWorkflow.nodes.formNode.formSetting') }}
|
||||||
|
</h5>
|
||||||
|
<el-button link type="primary" @click="openAddFormCollect()">
|
||||||
|
<el-icon class="mr-4">
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
{{ $t('common.add') }}
|
||||||
|
</el-button>
|
||||||
|
</div></template
|
||||||
|
>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
class="border"
|
||||||
|
v-if="form_data.form_field_list.length > 0"
|
||||||
|
:data="form_data.form_field_list"
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="field"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
prop="field"
|
||||||
|
:label="$t('dynamicsForm.paramForm.field.label')"
|
||||||
|
width="95"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :title="row.field" class="ellipsis-1">{{ row.field }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="label" :label="$t('dynamicsForm.paramForm.name.label')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.label && row.label.input_type === 'TooltipLabel'">
|
||||||
|
<span :title="row.label.label" class="ellipsis-1">
|
||||||
|
{{ row.label.label }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<span :title="row.label" class="ellipsis-1">
|
||||||
|
{{ row.label }}
|
||||||
|
</span></span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="$t('dynamicsForm.paramForm.input_type.label')" width="110px">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag type="info" class="info-tag">{{
|
||||||
|
input_type_list.find((item) => item.value === row.input_type)?.label
|
||||||
|
}}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="default_value" :label="$t('dynamicsForm.default.label')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :title="row.default_value" class="ellipsis-1">{{
|
||||||
|
getDefaultValue(row)
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.required')" width="85">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div @click.stop>
|
||||||
|
<el-switch disabled size="small" v-model="row.required" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.operation')" align="left" width="90">
|
||||||
|
<template #default="{ row, $index }">
|
||||||
|
<span class="mr-4">
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
|
||||||
|
<el-button type="primary" text @click.stop="openEditFormCollect(row, $index)">
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
|
||||||
|
<el-button type="primary" text @click="deleteField(row)">
|
||||||
|
<el-icon>
|
||||||
|
<Delete />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<AddFormCollect ref="addFormCollectRef" :addFormField="addFormField"></AddFormCollect>
|
||||||
|
<EditFormCollect ref="editFormCollectRef" :editFormField="editFormField"></EditFormCollect>
|
||||||
|
</NodeContainer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NodeContainer from '@/workflow/common/NodeContainer.vue'
|
||||||
|
import AddFormCollect from '@/workflow/common/AddFormCollect.vue'
|
||||||
|
import EditFormCollect from '@/workflow/common/EditFormCollect.vue'
|
||||||
|
import { type FormInstance } from 'element-plus'
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { input_type_list } from '@/components/dynamics-form/constructor/data'
|
||||||
|
import { MsgError } from '@/utils/message'
|
||||||
|
import { set, cloneDeep } from 'lodash'
|
||||||
|
import Sortable from 'sortablejs'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
const props = defineProps<{ nodeModel: any }>()
|
||||||
|
const formNodeFormRef = ref<FormInstance>()
|
||||||
|
const tableRef = ref()
|
||||||
|
const editFormField = (form_field_data: any, field_index: number) => {
|
||||||
|
const _value = form_data.value.form_field_list.map((item: any, index: number) => {
|
||||||
|
if (field_index === index) {
|
||||||
|
return cloneDeep(form_field_data)
|
||||||
|
}
|
||||||
|
return cloneDeep(item)
|
||||||
|
})
|
||||||
|
form_data.value.form_field_list = _value
|
||||||
|
sync_form_field_list()
|
||||||
|
}
|
||||||
|
const addFormField = (form_field_data: any) => {
|
||||||
|
if (form_data.value.form_field_list.some((field: any) => field.field === form_field_data.field)) {
|
||||||
|
MsgError(t('views.applicationWorkflow.tip.paramErrorMessage') + form_field_data.field)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form_data.value.form_field_list = cloneDeep([...form_data.value.form_field_list, form_field_data])
|
||||||
|
sync_form_field_list()
|
||||||
|
}
|
||||||
|
const sync_form_field_list = () => {
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
label: t('views.applicationWorkflow.nodes.formNode.formAllContent'),
|
||||||
|
value: 'form_data'
|
||||||
|
},
|
||||||
|
...form_data.value.form_field_list.map((item: any) => ({
|
||||||
|
value: item.field,
|
||||||
|
label: typeof item.label == 'string' ? item.label : item.label.label
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
set(props.nodeModel.properties.config, 'fields', fields)
|
||||||
|
props.nodeModel.clear_next_node_field(false)
|
||||||
|
onDragHandle()
|
||||||
|
}
|
||||||
|
const addFormCollectRef = ref<InstanceType<typeof AddFormCollect>>()
|
||||||
|
const editFormCollectRef = ref<InstanceType<typeof EditFormCollect>>()
|
||||||
|
const openAddFormCollect = () => {
|
||||||
|
addFormCollectRef.value?.open()
|
||||||
|
}
|
||||||
|
const openEditFormCollect = (form_field_data: any, index: number) => {
|
||||||
|
editFormCollectRef.value?.open(cloneDeep(form_field_data), index)
|
||||||
|
}
|
||||||
|
const deleteField = (form_field_data: any) => {
|
||||||
|
form_data.value.form_field_list = form_data.value.form_field_list.filter(
|
||||||
|
(field: any) => field.field !== form_field_data.field
|
||||||
|
)
|
||||||
|
sync_form_field_list()
|
||||||
|
}
|
||||||
|
const form = ref<any>({
|
||||||
|
is_result: true,
|
||||||
|
form_content_format: `${t('views.applicationWorkflow.nodes.formNode.form_content_format1')}
|
||||||
|
{{form}}
|
||||||
|
${t('views.applicationWorkflow.nodes.formNode.form_content_format2')}`,
|
||||||
|
form_field_list: []
|
||||||
|
})
|
||||||
|
const form_data = computed({
|
||||||
|
get: () => {
|
||||||
|
if (props.nodeModel.properties.node_data) {
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
} else {
|
||||||
|
set(props.nodeModel.properties, 'node_data', form.value)
|
||||||
|
}
|
||||||
|
return props.nodeModel.properties.node_data
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
set(props.nodeModel.properties, 'node_data', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getDefaultValue = (row: any) => {
|
||||||
|
if (row.default_value) {
|
||||||
|
const default_value = row.option_list
|
||||||
|
?.filter((v: any) => row.default_value.indexOf(v.value) > -1)
|
||||||
|
.map((v: any) => v.label)
|
||||||
|
.join(',')
|
||||||
|
if (default_value) {
|
||||||
|
return default_value
|
||||||
|
}
|
||||||
|
return row.default_value
|
||||||
|
}
|
||||||
|
if (row.default_value !== undefined) {
|
||||||
|
return row.default_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
return formNodeFormRef.value?.validate()
|
||||||
|
}
|
||||||
|
function submitDialog(val: string) {
|
||||||
|
set(props.nodeModel.properties.node_data, 'form_content_format', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格排序拖拽
|
||||||
|
function onDragHandle() {
|
||||||
|
if (!tableRef.value) return
|
||||||
|
|
||||||
|
// 获取表格的 tbody DOM 元素
|
||||||
|
const wrapper = tableRef.value.$el as HTMLElement
|
||||||
|
const tbody = wrapper.querySelector('.el-table__body-wrapper tbody')
|
||||||
|
if (!tbody) return
|
||||||
|
// 初始化 Sortable
|
||||||
|
Sortable.create(tbody as HTMLElement, {
|
||||||
|
animation: 150,
|
||||||
|
ghostClass: 'ghost-row',
|
||||||
|
onEnd: (evt) => {
|
||||||
|
if (evt.oldIndex === undefined || evt.newIndex === undefined) return
|
||||||
|
// 更新数据顺序
|
||||||
|
const items = [...form_data.value.form_field_list]
|
||||||
|
const [movedItem] = items.splice(evt.oldIndex, 1)
|
||||||
|
items.splice(evt.newIndex, 0, movedItem)
|
||||||
|
form_data.value.form_field_list = items
|
||||||
|
sync_form_field_list()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
set(props.nodeModel, 'validate', validate)
|
||||||
|
sync_form_field_list()
|
||||||
|
props.nodeModel.graphModel.eventCenter.emit('refresh_incoming_node_field')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import FunctionLibNodeVue from './index.vue'
|
||||||
|
import { AppNode, AppNodeModel } from '@/workflow/common/app-node'
|
||||||
|
class FunctionLibNode extends AppNode {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props, FunctionLibNodeVue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
type: 'function-lib-node',
|
||||||
|
model: AppNodeModel,
|
||||||
|
view: FunctionLibNode
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue