index.vue 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288
  1. <template>
  2. <div class="flex h-full w-full flex-col">
  3. <toBackCom
  4. title="应用中心"
  5. :rou="{
  6. name: 'd446bfb3-4605-477f-a0f4-b7a0a1aa78fe',
  7. }"
  8. />
  9. <div class="bm-main-box">
  10. <div class="grid h-full w-full grid-cols-3 gap-4">
  11. <div
  12. class="col-span-2 flex flex-col gap-2 overflow-hidden rounded-lg bg-[#F6F8FC] p-4"
  13. >
  14. <div
  15. class="flex h-14 w-full items-center gap-2.5 rounded-lg bg-[#ffffff] px-6"
  16. >
  17. <div class="text-2xl font-bold text-[#303133]">编排</div>
  18. <div class="text-[#576275]" v-if="state.autoSaveTimestamp">
  19. 自动保存{{ state.autoSaveTimestamp }}
  20. </div>
  21. <div class="mx-auto" />
  22. <div
  23. class="flex items-center text-[var(--czr-error-color)]"
  24. v-if="canPublishCpt"
  25. >
  26. <SvgIcon
  27. name="czr_tip"
  28. color="var(--czr-error-color)"
  29. class="mr-1"
  30. />
  31. 有未发布的修改
  32. </div>
  33. <CzrButton
  34. v-if="AppStore.hasPermission($route.name, 'publish')"
  35. type="primary"
  36. title="发布"
  37. @click="onPublish"
  38. />
  39. <el-popover
  40. :show-arrow="false"
  41. width="26rem"
  42. placement="bottom-end"
  43. :popper-style="{
  44. padding: 0,
  45. }"
  46. >
  47. <template #reference>
  48. <div
  49. class="__hover flex h-8 w-8 items-center justify-center rounded-sm border-1 border-[var(--czr-main-color)]"
  50. >
  51. <SvgIcon name="history" :active="true" size="16" />
  52. </div>
  53. </template>
  54. <div>
  55. <div
  56. class="flex h-11 w-full items-center bg-[url('@/assets/images/global/head-bg-1.png')] bg-[length:100%_100%] bg-no-repeat px-4 text-base font-bold text-[#303133]"
  57. >
  58. 历史版本
  59. </div>
  60. <div class="h-[68vh] overflow-y-auto px-4 py-2">
  61. <template v-for="(item, index) in state.history">
  62. <div class="flex">
  63. <div
  64. class="relative mr-3 flex flex-col items-center justify-center"
  65. >
  66. <div
  67. class="h-[50%] w-0.5"
  68. :class="`${index > 0 ? 'bg-[#DEE9FF]' : ''}`"
  69. />
  70. <div
  71. class="h-[50%] w-0.5"
  72. :class="`${index < state.history.length - 1 ? 'bg-[#DEE9FF]' : ''}`"
  73. />
  74. <div
  75. class="absolute flex size-2 items-center justify-center rounded bg-[var(--czr-main-color)]"
  76. >
  77. <div class="size-1 rounded bg-[#DEE9FF]" />
  78. </div>
  79. </div>
  80. <div
  81. class="my-2 flex flex-1 items-center justify-between rounded-lg bg-[#F4F8FF] px-4 py-3"
  82. >
  83. <div
  84. class="text-base font-bold"
  85. :class="{
  86. 'text-[var(--czr-success-color)]':
  87. item.type === 1 || item.type === 3,
  88. 'text-[var(--czr-warning-color)]': item.type === 2,
  89. 'text-[#2E3238]': item.type === 0,
  90. }"
  91. >
  92. {{
  93. DictionaryStore.getStaticDict(
  94. 'appPublishType',
  95. item.type,
  96. )
  97. }}
  98. </div>
  99. <div class="text-[#909399]">{{ item.time }}</div>
  100. </div>
  101. </div>
  102. </template>
  103. </div>
  104. </div>
  105. </el-popover>
  106. </div>
  107. <CzrForm class="form" ref="ref_form">
  108. <div class="grid h-full flex-1 grid-cols-2 gap-4">
  109. <div class="col-span-1 flex flex-col rounded-lg bg-[#ffffff]">
  110. <div
  111. class="flex h-12 w-full items-center gap-2.5 bg-[url('@/assets/images/global/head-bg-1.png')] bg-[length:100%_100%] bg-no-repeat px-6"
  112. >
  113. <div class="mr-auto font-bold text-[#303133]">提示词</div>
  114. <CzrButton
  115. v-if="AppStore.hasPermission($route.name, 'template')"
  116. type="normal"
  117. title="保存为模板"
  118. @click="onAddTipsTemplate"
  119. />
  120. <CzrButton
  121. type="normal"
  122. title="使用模板"
  123. @click="onTipsTemplate"
  124. />
  125. </div>
  126. <div class="flex-1 p-4">
  127. <textarea
  128. class="h-full w-full"
  129. style="resize: none; line-height: 1.4"
  130. ref="ref_tips"
  131. v-model="state.form.tips"
  132. ></textarea>
  133. </div>
  134. </div>
  135. <div
  136. class="col-span-1 overflow-y-auto rounded-lg bg-[#ffffff] p-4"
  137. >
  138. <template v-if="state.detail.type === 0">
  139. <div class="__czr-title_2">模型选择</div>
  140. <div class="mt-4">
  141. <CzrFormColumn
  142. class="__czr-table-form-column"
  143. required
  144. label="模型选择"
  145. :span="24"
  146. v-model:param="state.form.modelId"
  147. link="select"
  148. :options="DictionaryStore.models.list"
  149. placeholder="点击选择模型"
  150. @click.capture.stop="onModel"
  151. default-error-msg="请选择模型"
  152. :clearable="false"
  153. />
  154. </div>
  155. </template>
  156. <div class="__czr-title_2 mt-4">
  157. 组件
  158. <template v-if="state.form.components.length > 0">
  159. ({{ state.form.components.length }})
  160. </template>
  161. <!-- <CzrButton type="normal" title="新增" class="ml-auto" />-->
  162. </div>
  163. <template v-if="state.form.components.length > 0"> </template>
  164. <template v-else>
  165. <div
  166. class="mt-2 flex h-8 items-center justify-center rounded-sm bg-[#F6F8FC] text-xs text-[#A7ADB9]"
  167. >
  168. 暂未添加组件
  169. </div>
  170. </template>
  171. <template v-if="state.detail.type === 0">
  172. <div class="__czr-title_2 mt-4">
  173. 知识库
  174. <template v-if="state.form.datasetIds.length > 0">
  175. ({{ state.form.datasetIds.length }})
  176. </template>
  177. <div
  178. class="__hover ml-4 text-sm font-normal text-[var(--czr-main-color)]"
  179. @click="onRecall"
  180. >
  181. 知识库设置
  182. </div>
  183. <CzrButton
  184. type="normal"
  185. title="新增"
  186. class="ml-auto"
  187. @click="onAddKnowledge"
  188. />
  189. </div>
  190. <template v-if="state.form.datasetIds.length > 0">
  191. <div
  192. class="mt-2 flex max-h-42 flex-col gap-2 overflow-y-auto pr-2"
  193. >
  194. <template v-for="(id, index) in state.form.datasetIds">
  195. <div class="flex items-center gap-1.5">
  196. <div
  197. class="flex flex-1 items-center overflow-hidden rounded-sm bg-[#F6F8FC] p-2.5"
  198. >
  199. <img
  200. src="@/assets/images/knowledge/knowledge-item-icon.png"
  201. class="mr-4 size-6"
  202. />
  203. <div
  204. class="flex-1 text-sm font-bold text-[#2E3238]"
  205. v-title
  206. >
  207. {{ DictionaryStore.knowledges.map.get(id) }}
  208. </div>
  209. </div>
  210. <el-tooltip content="删除" placement="top">
  211. <SvgIcon
  212. class="__hover"
  213. name="czr_del"
  214. color="var(--czr-error-color)"
  215. @click="state.form.datasetIds.splice(index, 1)"
  216. />
  217. </el-tooltip>
  218. </div>
  219. </template>
  220. </div>
  221. </template>
  222. <template v-else>
  223. <div
  224. class="mt-2 flex h-8 items-center justify-center rounded-sm bg-[#F6F8FC] text-xs text-[#A7ADB9]"
  225. >
  226. 暂未添加知识库
  227. </div>
  228. </template>
  229. </template>
  230. <template v-if="state.detail.type === 1">
  231. <div class="__czr-title_2 mt-4">
  232. 工作流
  233. <CzrButton
  234. type="normal"
  235. :title="state.form.workflowId ? '选择' : '新增'"
  236. class="ml-auto"
  237. @click="onAddWorkflow"
  238. />
  239. </div>
  240. <template v-if="state.form.workflowId">
  241. <div
  242. class="mt-2 flex max-h-42 flex-col gap-2 overflow-y-auto pr-2"
  243. >
  244. <div class="flex items-center gap-1.5">
  245. <div
  246. class="flex flex-1 items-center overflow-hidden rounded-sm bg-[#F6F8FC] p-2.5"
  247. >
  248. <img
  249. src="@/assets/images/workflow/workflow-default-icon.png"
  250. class="mr-4 size-15"
  251. />
  252. <div
  253. class="flex flex-1 flex-col gap-2 overflow-hidden"
  254. >
  255. <div
  256. class="text-sm font-bold text-[#2E3238]"
  257. v-title
  258. >
  259. {{
  260. DictionaryStore.workflows.map.get(
  261. state.form.workflowId,
  262. )
  263. }}
  264. </div>
  265. <div
  266. class="text-sm text-[#6F7889]"
  267. v-title="{ lines: 2 }"
  268. >
  269. {{
  270. DictionaryStore.workflows.objMap.get(
  271. state.form.workflowId,
  272. )?.description
  273. }}
  274. </div>
  275. </div>
  276. </div>
  277. <el-tooltip content="查看" placement="top">
  278. <SvgIcon
  279. class="__hover"
  280. name="view"
  281. :active="true"
  282. @click="onWorkflowView(state.form.workflowId)"
  283. />
  284. </el-tooltip>
  285. <el-tooltip content="删除" placement="top">
  286. <SvgIcon
  287. class="__hover"
  288. name="czr_del"
  289. color="var(--czr-error-color)"
  290. @click="state.form.workflowId = ''"
  291. />
  292. </el-tooltip>
  293. </div>
  294. </div>
  295. </template>
  296. <template v-else>
  297. <div
  298. class="mt-2 flex h-8 items-center justify-center rounded-sm bg-[#F6F8FC] text-xs text-[#A7ADB9]"
  299. >
  300. 暂未添加工作流
  301. </div>
  302. </template>
  303. </template>
  304. <div class="__czr-title_2 mt-4">开场白</div>
  305. <div class="mt-4">
  306. <CzrFormColumn
  307. class="__czr-table-form-column"
  308. label-width="0px"
  309. :span="24"
  310. v-model:param="state.form.prologue"
  311. link="rich"
  312. :height="300"
  313. default-error-msg="请输入开场白"
  314. />
  315. </div>
  316. <div class="__czr-title_2 mt-4">
  317. 开场白引导问题
  318. <template v-if="state.prologueQuestionsArr.length > 0">
  319. ({{ state.prologueQuestionsArr.length }})
  320. </template>
  321. <div class="ml-auto">
  322. <el-radio-group
  323. v-model="state.form.prologueNum"
  324. size="small"
  325. >
  326. <el-radio-button :value="3">显示前3条</el-radio-button>
  327. <el-radio-button :value="0">显示全部</el-radio-button>
  328. </el-radio-group>
  329. </div>
  330. </div>
  331. <div class="mt-2 flex flex-col">
  332. <div
  333. class="drag-body col relative flex max-h-42 flex-col gap-2 overflow-y-auto pr-2"
  334. ref="ref_prologueBody"
  335. v-if="state.dragRefresh"
  336. >
  337. <template
  338. v-for="(item, index) in state.prologueQuestionsArr"
  339. >
  340. <div
  341. class="flex items-center gap-2"
  342. style="-webkit-user-drag: element"
  343. >
  344. <SvgIcon
  345. name="czr_drag"
  346. color="#999999"
  347. class="drag-icon cursor-move"
  348. />
  349. <div
  350. class="__hover flex h-7 flex-1 items-center rounded-sm bg-[#F6F8FC] px-2.5 text-sm text-[#A7ADB9]"
  351. @click="onPrologue(item)"
  352. >
  353. <template v-if="item.__edit">
  354. <CzrFormColumn
  355. ref="ref_prologue"
  356. class="transparent-input"
  357. label-width="0px"
  358. :span="24"
  359. v-model:param="item.__value"
  360. :transparent="true"
  361. :clearable="false"
  362. maxlength="40"
  363. show-word-limit
  364. @keyup.enter.stop="onEditPrologue(item, index)"
  365. @blur="onEditPrologue(item, index)"
  366. />
  367. </template>
  368. <template v-else> {{ item.value }} </template>
  369. </div>
  370. <el-tooltip content="删除" placement="top">
  371. <SvgIcon
  372. class="__hover"
  373. name="czr_del"
  374. color="var(--czr-error-color)"
  375. @click="
  376. (state.form.prologueQuestions.splice(index, 1),
  377. state.prologueQuestionsArr.splice(index, 1))
  378. "
  379. />
  380. </el-tooltip>
  381. </div>
  382. </template>
  383. </div>
  384. <div class="mt-2 flex items-center gap-2">
  385. <div
  386. class="__hover flex h-7 flex-1 items-center rounded-sm bg-[#F6F8FC] px-2.5 text-sm text-[#A7ADB9]"
  387. @click="onPrologue(null)"
  388. >
  389. <template v-if="state.prologuesAdd.__edit">
  390. <CzrFormColumn
  391. ref="ref_prologue"
  392. class="transparent-input"
  393. label-width="0px"
  394. :span="24"
  395. v-model:param="state.prologuesAdd.value"
  396. :transparent="true"
  397. :clearable="false"
  398. maxlength="40"
  399. show-word-limit
  400. @keyup.enter.stop="onAddPrologue"
  401. @blur="state.prologuesAdd.__edit = false"
  402. />
  403. </template>
  404. <template v-else> 点击输入问题,enter以新增 </template>
  405. </div>
  406. </div>
  407. </div>
  408. <div class="__czr-title_2 mt-4">用户输入方式</div>
  409. <div class="mt-4">
  410. <CzrFormColumn
  411. class="__czr-table-form-column"
  412. label="输入方式"
  413. :span="24"
  414. v-model:param="state.form.userInputMethod"
  415. link="select"
  416. :options="[
  417. { value: 0, label: '文字/语音输入' },
  418. { value: 1, label: '语音通话' },
  419. ]"
  420. :clearable="false"
  421. />
  422. <div
  423. class="mt-4 flex items-center"
  424. v-if="state.form.userInputMethod == 1"
  425. >
  426. <div class="flex-1">
  427. <CzrFormColumn
  428. class="__czr-table-form-column"
  429. required
  430. label="语音包"
  431. :span="24"
  432. v-model:param="state.form.voicePackage"
  433. link="select"
  434. :options="[
  435. { value: 'Echo', label: 'Echo' },
  436. { value: 'Alloy', label: 'Alloy' },
  437. { value: 'Fable', label: 'Fable' },
  438. { value: 'Onyx', label: 'Onyx' },
  439. { value: 'Nova', label: 'Nova' },
  440. { value: 'Shimmer', label: 'Shimmer' },
  441. ]"
  442. :clearable="false"
  443. >
  444. <template #row="{ row }">
  445. <div class="flex h-full items-center">
  446. {{ row.label }}
  447. <div
  448. class="__hover ml-auto text-[var(--czr-main-color)]"
  449. @click.stop="onTryVoice(row.value)"
  450. >
  451. 试听
  452. </div>
  453. </div>
  454. </template>
  455. </CzrFormColumn>
  456. </div>
  457. <div
  458. class="__hover ml-4 text-sm text-[var(--czr-main-color)]"
  459. @click="onTryVoice(state.form.voicePackage)"
  460. >
  461. 试听
  462. </div>
  463. </div>
  464. </div>
  465. <div class="__czr-title_2 mt-4">
  466. 问题建议
  467. <div class="ml-auto">
  468. <el-checkbox-group
  469. class="advise"
  470. v-model="state.form.advise.types"
  471. size="small"
  472. >
  473. <el-checkbox :value="AdviseType.Open"> 开启 </el-checkbox>
  474. <template
  475. v-if="state.form.advise.types.includes(AdviseType.Open)"
  476. >
  477. <el-checkbox :value="AdviseType.Tips">
  478. 自定义提示词
  479. </el-checkbox>
  480. <el-checkbox :value="AdviseType.Knowledge">
  481. 仅从知识库建议
  482. </el-checkbox>
  483. </template>
  484. </el-checkbox-group>
  485. </div>
  486. </div>
  487. <template
  488. v-if="state.form.advise.types.includes(AdviseType.Open)"
  489. >
  490. <div class="mt-4">
  491. <CzrFormColumn
  492. class="__czr-table-form-column"
  493. required
  494. label="模型选择"
  495. :span="24"
  496. v-model:param="state.form.advise.modelId"
  497. link="select"
  498. :options="DictionaryStore.models.list"
  499. placeholder="点击选择模型"
  500. @click.capture.stop="onAdviseModel"
  501. default-error-msg="请选择问题建议模型"
  502. :clearable="false"
  503. />
  504. <div
  505. class="mt-2 flex h-10 items-center gap-1 rounded-sm bg-[#FFFAEA] px-3.5 text-sm text-[#6F7889]"
  506. >
  507. <SvgIcon
  508. name="czr_tip"
  509. color="var(--czr-warning-color)"
  510. />
  511. 应用每次回复后,根据对话内容提出最多3条问题建议
  512. </div>
  513. <template
  514. v-if="state.form.advise.types.includes(AdviseType.Tips)"
  515. >
  516. <div class="mt-2">
  517. <CzrFormColumn
  518. class="__czr-table-form-column"
  519. label-width="0px"
  520. :span="24"
  521. v-model:param="state.form.advise.tips"
  522. type="textarea"
  523. :rows="4"
  524. placeholder="问题应该与你最后一轮的回复紧密相关,可以引发进一步的讨论。&#10;问题不要与上文已经提问或者回答过的内容重复。&#10;每句话只包含一个问题,但也可以不是问句而是一句指令。&#10;推荐你有能力回答的问题。"
  525. />
  526. </div>
  527. </template>
  528. </div>
  529. <template
  530. v-if="
  531. state.form.advise.types.includes(AdviseType.Knowledge)
  532. "
  533. >
  534. <div class="__czr-title_2 mt-4">
  535. 问题建议知识库
  536. <template v-if="state.form.advise.datasetIds.length > 0">
  537. ({{ state.form.advise.datasetIds.length }})
  538. </template>
  539. <CzrButton
  540. type="normal"
  541. title="新增"
  542. class="ml-auto"
  543. @click="onAddAdviseKnowledge"
  544. />
  545. </div>
  546. <template v-if="state.form.advise.datasetIds.length > 0">
  547. <div
  548. class="mt-2 flex max-h-42 flex-col gap-2 overflow-y-auto pr-2"
  549. >
  550. <template
  551. v-for="(id, index) in state.form.advise.datasetIds"
  552. >
  553. <div class="flex items-center gap-1.5">
  554. <div
  555. class="flex h-10 flex-1 items-center overflow-hidden rounded-sm bg-[#F6F8FC] px-2.5"
  556. >
  557. <img
  558. src="@/assets/images/knowledge/knowledge-item-icon.png"
  559. class="mr-4 size-6"
  560. />
  561. <div
  562. class="flex-1 text-sm font-bold text-[#2E3238]"
  563. v-title
  564. >
  565. {{ DictionaryStore.knowledges.map.get(id) }}
  566. </div>
  567. </div>
  568. <el-tooltip content="删除" placement="top">
  569. <SvgIcon
  570. class="__hover"
  571. name="czr_del"
  572. color="var(--czr-error-color)"
  573. @click="
  574. state.form.advise.datasetIds.splice(index, 1)
  575. "
  576. />
  577. </el-tooltip>
  578. </div>
  579. </template>
  580. </div>
  581. </template>
  582. <template v-else>
  583. <div
  584. class="mt-2 flex h-8 items-center justify-center rounded-sm bg-[#F6F8FC] text-xs text-[#A7ADB9]"
  585. >
  586. 暂未添加问题建议知识库
  587. </div>
  588. </template>
  589. </template>
  590. </template>
  591. </div>
  592. </div>
  593. </CzrForm>
  594. </div>
  595. <div
  596. class="col-span-1 flex flex-col gap-2 overflow-hidden rounded-lg bg-[#F6F8FC] p-4"
  597. >
  598. <div
  599. class="flex h-14 w-full items-center gap-2.5 rounded-lg bg-[#ffffff] px-6"
  600. >
  601. <div class="text-2xl font-bold text-[#303133]">预览</div>
  602. <div
  603. class="flex items-center gap-2 text-sm text-[var(--czr-error-color)]"
  604. v-if="!state.canModelApply"
  605. >
  606. <SvgIcon name="czr_tip" color="var(--czr-error-color)" />
  607. 编排中有申请中的模型,预览暂不可用
  608. </div>
  609. <el-tooltip content="重新开始" :raw-content="true" placement="top">
  610. <SvgIcon
  611. class="__hover ml-auto"
  612. name="refresh"
  613. :active="true"
  614. @click="onRestart"
  615. />
  616. </el-tooltip>
  617. </div>
  618. <div class="relative flex-1 overflow-hidden rounded-lg">
  619. <chat :ID="state.ID" :test="true" ref="ref_chat" />
  620. <div
  621. v-if="!state.canDebug"
  622. class="absolute inset-0 flex h-full w-full flex-col rounded-lg bg-white/50 p-20 backdrop-blur-sm"
  623. >
  624. <div class="text-2xl font-bold text-[#1d2939]">编排已改变</div>
  625. <div class="mt-6 text-[#667085]">
  626. 修改编排将重置调试区域,确定吗?
  627. </div>
  628. <div class="mt-6 flex gap-2">
  629. <CzrButton
  630. type="primary"
  631. title="重新开始"
  632. icon="refresh"
  633. size="18"
  634. @click="onRestart"
  635. />
  636. <CzrButton
  637. type="normal"
  638. title="取消"
  639. @click="state.canDebug = true"
  640. />
  641. </div>
  642. </div>
  643. </div>
  644. </div>
  645. </div>
  646. </div>
  647. <recallConfig
  648. ref="ref_recalConfig"
  649. v-model:show="state.recallConfig.show"
  650. :transfer="state.recallConfig.transfer"
  651. @refresh="getRecallConfig"
  652. />
  653. <knowledgeSelect
  654. v-model:show="state.knowledgeSelect.show"
  655. :transfer="state.knowledgeSelect.transfer"
  656. @refresh="getKnowledge"
  657. />
  658. <modelSelect
  659. v-model:show="state.modelSelect.show"
  660. :transfer="state.modelSelect.transfer"
  661. @refresh="getModel"
  662. />
  663. <workflowSelect
  664. v-model:show="state.workflowSelect.show"
  665. :transfer="state.workflowSelect.transfer"
  666. @refresh="getWorkflow"
  667. />
  668. <templateSelect
  669. v-model:show="state.templateSelect.show"
  670. @insert="(val) => ((state.form.tips = val), ref_tips.focus())"
  671. />
  672. <templateDetail
  673. v-model:show="state.templateDetail.show"
  674. :transfer="state.templateDetail.transfer"
  675. />
  676. <CzrDialog
  677. :show="state.publish.show"
  678. title="发布"
  679. @onClose="() => (state.publish.show = false)"
  680. @onSubmit="onPublishSubmit"
  681. width="42.5rem"
  682. height="auto"
  683. >
  684. <div class="bm-form">
  685. <CzrForm ref="ref_formPublish">
  686. <CzrFormColumn
  687. required
  688. label="发布渠道"
  689. :span="24"
  690. v-model:param="state.publish.form.type"
  691. link="select"
  692. :options="[
  693. { label: '个人空间', value: 0 },
  694. { label: '部门空间(需审批)', value: 1 },
  695. ]"
  696. :clearable="false"
  697. />
  698. <CzrFormColumn
  699. v-if="state.publish.form.type == 1"
  700. label="申请留言"
  701. :span="24"
  702. v-model:param="state.publish.form.remark"
  703. type="textarea"
  704. :rows="10"
  705. />
  706. </CzrForm>
  707. </div>
  708. </CzrDialog>
  709. </div>
  710. </template>
  711. <script setup lang="ts">
  712. import {
  713. computed,
  714. getCurrentInstance,
  715. inject,
  716. nextTick,
  717. onMounted,
  718. reactive,
  719. ref,
  720. watch,
  721. } from 'vue'
  722. import { useRoute, useRouter } from 'vue-router'
  723. import { ElLoading, ElMessage } from 'element-plus'
  724. import { useAppStore, useDialogStore, useDictionaryStore } from '@/stores'
  725. import { Search } from '@element-plus/icons-vue'
  726. import knowledgeSelect from './knowledge-select.vue'
  727. import modelSelect from './model-select.vue'
  728. import { isValue, YMDHms } from '@/utils/czr-util'
  729. import Sortable from 'sortablejs'
  730. import { debounce } from 'lodash'
  731. import chat from '@/views/chat/index.vue'
  732. import templateSelect from './template-select.vue'
  733. import templateDetail from './template-detail.vue'
  734. import workflowSelect from './workflow-select.vue'
  735. import CzrForm from '@/components/czr-ui/CzrForm.vue'
  736. import CzrDialog from '@/components/czr-ui/CzrDialog.vue'
  737. import toBackCom from '@/views/manage/components/to-back.vue'
  738. import {
  739. appHistory,
  740. appTemporarily,
  741. appModelConfigSave,
  742. appPublish,
  743. appValidatePublish,
  744. appModelConfigDetail,
  745. } from '@/api/modules/app/make'
  746. import { appDetail } from '@/api/modules/app'
  747. import { appTextToAudio } from '@/api/modules/app/chat'
  748. import recallConfig from '@/views/manage/knowledge/recall-config.vue'
  749. const DictionaryStore = useDictionaryStore()
  750. const AppStore = useAppStore()
  751. const DialogStore = useDialogStore()
  752. const route = useRoute()
  753. const router = useRouter()
  754. const emit = defineEmits([])
  755. const props = defineProps({})
  756. const { proxy }: any = getCurrentInstance()
  757. enum AdviseType {
  758. Open = 1,
  759. Tips = 2,
  760. Knowledge = 3,
  761. }
  762. const state: any = reactive({
  763. ID: route.params.id,
  764. isInit: false,
  765. autoSaveTimestamp: '',
  766. form: {
  767. tips: '',
  768. modelId: '',
  769. components: [],
  770. datasetIds: [],
  771. datasetConfigs: null,
  772. workflowId: '',
  773. prologue: '',
  774. prologueQuestions: [],
  775. prologueNum: 3,
  776. userInputMethod: 1,
  777. voicePackage: '',
  778. advise: {
  779. types: [],
  780. modelId: '',
  781. tips: '',
  782. datasetIds: [],
  783. },
  784. },
  785. prologueQuestionsArr: [],
  786. detail: {},
  787. config: {},
  788. knowledgeSelect: {
  789. show: false,
  790. transfer: {},
  791. },
  792. modelSelect: {
  793. show: false,
  794. transfer: {},
  795. },
  796. workflowSelect: {
  797. show: false,
  798. transfer: {},
  799. },
  800. templateSelect: {
  801. show: false,
  802. },
  803. templateDetail: {
  804. show: false,
  805. transfer: {},
  806. },
  807. publish: {
  808. show: false,
  809. form: {},
  810. },
  811. prologuesAdd: {
  812. value: '',
  813. },
  814. dragRefresh: true,
  815. canDebug: true,
  816. canModelApply: true,
  817. history: [],
  818. recallConfig: {
  819. show: false,
  820. transfer: {},
  821. },
  822. })
  823. const ref_form = ref()
  824. const ref_formPublish = ref()
  825. const ref_tips = ref()
  826. const ref_prologue = ref()
  827. const ref_prologueBody = ref()
  828. const ref_recalConfig = ref()
  829. const ref_chat = ref()
  830. const canPublishCpt = computed(() => {
  831. if (state.config.createTime === state.config.updateTime) {
  832. return false
  833. }
  834. if (state.history.length === 0) {
  835. return false
  836. } else {
  837. if (
  838. new Date(state.autoSaveTimestamp).getTime() <
  839. new Date(state.history[0]?.time).getTime()
  840. ) {
  841. return false
  842. }
  843. }
  844. return true
  845. })
  846. watch(
  847. () => state.form,
  848. (n) => {
  849. if (state.isInit) {
  850. console.log('触发自动保存')
  851. autoSave(n)
  852. }
  853. },
  854. { deep: true },
  855. )
  856. const autoSave = debounce((v) => {
  857. const loading = ElLoading.service({
  858. text: '自动保存中……',
  859. background: 'rgba(0, 0,0, 0.3)',
  860. })
  861. const params: any = {
  862. appId: state.ID,
  863. prePrompt: state.form.tips,
  864. openingStatement: state.form.prologue,
  865. showAll: state.form.prologueNum,
  866. suggestedQuestions: state.form.prologueQuestions,
  867. userInputMethod: state.form.userInputMethod,
  868. voice: state.form.voicePackage,
  869. qsType: state.form.advise.types,
  870. qsModelId: state.form.advise.modelId,
  871. qsPrePrompt: state.form.advise.tips,
  872. qsDatasetIds: state.form.advise.datasetIds,
  873. }
  874. if (state.detail.type === 0) {
  875. params.modelId = state.form.modelId
  876. params.datasetIds = state.form.datasetIds
  877. console.log(state.form.datasetConfigs)
  878. if (!state.form.datasetConfigs) {
  879. state.form.datasetConfigs = ref_recalConfig.value.getData()
  880. }
  881. params.datasetConfigs = state.form.datasetConfigs
  882. } else {
  883. params.workflowId = state.form.workflowId
  884. }
  885. appModelConfigSave(params)
  886. .then(({ data }: any) => {
  887. state.canDebug = false
  888. verifyPublish(false)
  889. state.autoSaveTimestamp = data.updateTime
  890. })
  891. .catch(() => {})
  892. .finally(() => {
  893. loading.close()
  894. })
  895. }, 5000)
  896. const initDetail = () => {
  897. state.isInit = false
  898. if (state.ID) {
  899. appDetail(state.ID)
  900. .then(({ data: appData }: any) => {
  901. state.detail = appData
  902. document.title = state.detail.name
  903. initHistory()
  904. appModelConfigDetail(state.ID, 0)
  905. .then(({ data: configData }: any) => {
  906. verifyPublish(false).finally(() => {
  907. if (state.canModelApply) {
  908. onRestart()
  909. }
  910. })
  911. if (configData.createTime !== configData.updateTime) {
  912. state.autoSaveTimestamp = configData.updateTime
  913. }
  914. state.config = JSON.parse(JSON.stringify(configData))
  915. state.form.tips = configData.prePrompt || ''
  916. state.form.prologue = configData.openingStatement || ''
  917. state.form.prologueNum = Number(configData.showAll) || 3
  918. state.form.prologueQuestions = configData.suggestedQuestions || []
  919. state.prologueQuestionsArr = state.form.prologueQuestions.map(
  920. (v) => ({
  921. value: v,
  922. }),
  923. )
  924. state.form.userInputMethod = Number(configData.userInputMethod) || 0
  925. state.form.voicePackage = configData.voice || ''
  926. state.form.advise.types =
  927. configData.qsType?.map((v) => Number(v)) || []
  928. state.form.advise.modelId = configData.qsModelId || ''
  929. state.form.advise.tips = configData.qsPrePrompt || ''
  930. state.form.advise.datasetIds = configData.qsDatasetIds || []
  931. if (state.detail.type === 0) {
  932. state.form.modelId = configData.modelId || ''
  933. state.form.datasetIds = configData.datasetIds || []
  934. state.form.datasetConfigs = configData.datasetConfigs
  935. } else {
  936. state.form.workflowId = configData.workflowId
  937. }
  938. setTimeout(() => {
  939. state.isInit = true
  940. }, 100)
  941. })
  942. .catch(() => {
  943. ElMessage.error('详情获取异常!')
  944. router.push({ name: 'd446bfb3-4605-477f-a0f4-b7a0a1aa78fe' })
  945. })
  946. .finally(() => {
  947. state.loading = false
  948. })
  949. })
  950. .catch(() => {
  951. ElMessage.error('详情获取异常!')
  952. router.push({ name: 'd446bfb3-4605-477f-a0f4-b7a0a1aa78fe' })
  953. })
  954. .finally(() => {
  955. state.loading = false
  956. })
  957. initDrag()
  958. } else {
  959. router.push({ name: 'd446bfb3-4605-477f-a0f4-b7a0a1aa78fe' })
  960. }
  961. }
  962. const initHistory = () => {
  963. appHistory(state.ID).then(({ data }: any) => {
  964. state.history = data || []
  965. })
  966. }
  967. const initDrag = () => {
  968. nextTick(() => {
  969. const tbody =
  970. ref_prologueBody.value.parentElement.querySelector('.drag-body')
  971. new Sortable(tbody, {
  972. animation: 150,
  973. handle: '.drag-icon', // 设置可拖拽行的类名(el-table自带的类名)
  974. onEnd: ({ newIndex, oldIndex }: any) => {
  975. const d = [...state.prologueQuestionsArr]
  976. const targetRow = d[oldIndex]
  977. d.splice(oldIndex, 1)
  978. d.splice(newIndex, 0, targetRow)
  979. state.prologueQuestionsArr = [...d]
  980. state.dragRefresh = false
  981. setTimeout(() => {
  982. state.dragRefresh = true
  983. initDrag()
  984. }, 0)
  985. },
  986. })
  987. })
  988. }
  989. const onAddKnowledge = () => {
  990. state.knowledgeSelect.transfer = {
  991. ids: state.form.datasetIds,
  992. type: 'knowledge',
  993. }
  994. state.knowledgeSelect.show = true
  995. }
  996. const onAddAdviseKnowledge = () => {
  997. state.knowledgeSelect.transfer = {
  998. ids: state.form.advise.datasetIds,
  999. type: 'advise',
  1000. }
  1001. state.knowledgeSelect.show = true
  1002. }
  1003. const onPrologue = (row) => {
  1004. if (row) {
  1005. row.__value = row.value + ''
  1006. row.__edit = true
  1007. } else {
  1008. state.prologuesAdd.__edit = true
  1009. }
  1010. const t = setInterval(() => {
  1011. const r = ref_prologue.value
  1012. if (r) {
  1013. if (r.length) {
  1014. r[0].focus()
  1015. } else {
  1016. r.focus()
  1017. }
  1018. clearInterval(t)
  1019. }
  1020. }, 100)
  1021. }
  1022. const onAddPrologue = () => {
  1023. if (state.prologuesAdd.value.trim()) {
  1024. state.form.prologueQuestions.push(state.prologuesAdd.value + '')
  1025. state.prologueQuestionsArr.push({ value: state.prologuesAdd.value + '' })
  1026. state.prologuesAdd.value = ''
  1027. }
  1028. }
  1029. const onEditPrologue = (row, index) => {
  1030. if (isValue(row.__value.trim())) {
  1031. row.value = row.__value + ''
  1032. state.form.prologueQuestions[index] = row.__value + ''
  1033. }
  1034. row.__edit = false
  1035. }
  1036. const getKnowledge = (arr) => {
  1037. switch (state.knowledgeSelect.transfer.type) {
  1038. case 'knowledge':
  1039. {
  1040. state.form.datasetIds.push(...arr.map((v) => v.id))
  1041. }
  1042. break
  1043. case 'advise':
  1044. {
  1045. state.form.advise.datasetIds.push(...arr.map((v) => v.id))
  1046. }
  1047. break
  1048. }
  1049. }
  1050. const getModel = (val) => {
  1051. switch (state.modelSelect.transfer.type) {
  1052. case 'model':
  1053. {
  1054. state.form.modelId = val.id
  1055. }
  1056. break
  1057. case 'advise':
  1058. {
  1059. state.form.advise.modelId = val.id
  1060. }
  1061. break
  1062. }
  1063. }
  1064. const onModel = () => {
  1065. state.modelSelect.transfer = {
  1066. type: 'model',
  1067. id: state.form.modelId,
  1068. }
  1069. state.modelSelect.show = true
  1070. }
  1071. const onAdviseModel = () => {
  1072. state.modelSelect.transfer = {
  1073. type: 'advise',
  1074. id: state.form.advise.modelId,
  1075. }
  1076. state.modelSelect.show = true
  1077. }
  1078. const onPublish = () => {
  1079. ref_form.value
  1080. .submit()
  1081. .then(() => {
  1082. if (state.detail.type == 1) {
  1083. if (!state.form.workflowId) {
  1084. ElMessage.warning('请添加工作流!')
  1085. return
  1086. }
  1087. }
  1088. if (
  1089. state.form.advise.types.includes(AdviseType.Open) &&
  1090. state.form.advise.types.includes(AdviseType.Knowledge)
  1091. ) {
  1092. if (state.form.advise.datasetIds.length === 0) {
  1093. ElMessage.warning('请添加问题建议知识库!')
  1094. return
  1095. }
  1096. }
  1097. verifyPublish(true).then(() => {
  1098. state.publish.form = {}
  1099. state.publish.show = true
  1100. })
  1101. })
  1102. .catch((e) => {
  1103. ElMessage({
  1104. message: e[0].message,
  1105. grouping: true,
  1106. type: 'warning',
  1107. })
  1108. })
  1109. }
  1110. const onPublishSubmit = () => {
  1111. ref_formPublish.value
  1112. .submit()
  1113. .then(() => {
  1114. DialogStore.confirm({
  1115. content: `请确认是否发布?`,
  1116. onSubmit: () => {
  1117. verifyPublish(true).then(() => {
  1118. appPublish({
  1119. appId: state.ID,
  1120. ...state.publish.form,
  1121. }).then(() => {
  1122. initHistory()
  1123. state.publish.show = false
  1124. ElMessage.success(
  1125. state.publish.form.type == 0 ? '发布成功!' : '提交发布成功!',
  1126. )
  1127. })
  1128. })
  1129. },
  1130. })
  1131. })
  1132. .catch((e) => {
  1133. ElMessage({
  1134. message: e[0].message,
  1135. grouping: true,
  1136. type: 'warning',
  1137. })
  1138. })
  1139. }
  1140. const onAddTipsTemplate = () => {
  1141. state.templateDetail.transfer = {
  1142. mode: 'add',
  1143. tips: state.form.tips + '',
  1144. }
  1145. state.templateDetail.show = true
  1146. }
  1147. const onTipsTemplate = () => {
  1148. state.templateSelect.show = true
  1149. }
  1150. const onAddWorkflow = () => {
  1151. state.workflowSelect.transfer = {
  1152. id: state.form.workflowId,
  1153. }
  1154. state.workflowSelect.show = true
  1155. }
  1156. const getWorkflow = (val) => {
  1157. state.form.workflowId = val.id
  1158. }
  1159. const onTryVoice = (type) => {
  1160. const loading = ElLoading.service({
  1161. text: '语音包加载中……',
  1162. background: 'rgba(0, 0,0, 0.3)',
  1163. })
  1164. appTextToAudio({
  1165. voice: type,
  1166. text: `您好,欢迎使用${(import.meta as any).env.VITE_TITLE}。`,
  1167. })
  1168. .then((bolb: any) => {
  1169. const url = URL.createObjectURL(new Blob([bolb], { type: 'audio/mp3' }))
  1170. const audio = new Audio(url)
  1171. audio.play()
  1172. // 播放结束后释放内存
  1173. audio.onended = function () {
  1174. URL.revokeObjectURL(url)
  1175. }
  1176. })
  1177. .catch(() => {})
  1178. .finally(() => {
  1179. loading.close()
  1180. })
  1181. }
  1182. const onWorkflowView = (id) => {
  1183. const routerUrl = router.resolve({
  1184. name: '37d34d52-78c7-4272-8715-fdc88f599c4f',
  1185. params: {
  1186. id,
  1187. },
  1188. })
  1189. window.open(routerUrl.href, '_blank')
  1190. }
  1191. const verifyPublish = (showDialog) => {
  1192. return new Promise((resolve, reject) => {
  1193. appValidatePublish(state.ID)
  1194. .then(({ data }: any) => {
  1195. if (data.status) {
  1196. resolve(null)
  1197. state.canModelApply = true
  1198. } else {
  1199. if (data.type == 1) {
  1200. state.canModelApply = false
  1201. if (showDialog) {
  1202. DialogStore.confirm({
  1203. title: '发布失败',
  1204. content: `编排中有申请中的模型,暂不可发布!`,
  1205. props: {
  1206. showSubmit: false,
  1207. showCancel: false,
  1208. },
  1209. })
  1210. }
  1211. } else if (data.type == 2) {
  1212. state.canModelApply = true
  1213. if (showDialog) {
  1214. DialogStore.confirm({
  1215. title: '发布失败',
  1216. content: `该应用有发布中的申请,无法提交发布!<br/>申请提交时间:${data.time}`,
  1217. onSubmit: () => {},
  1218. props: {
  1219. submitText: '撤销上一次的申请,重新发布',
  1220. cancelText: '等待审批',
  1221. },
  1222. })
  1223. }
  1224. }
  1225. reject(data)
  1226. }
  1227. })
  1228. .finally(() => {})
  1229. })
  1230. }
  1231. const onRestart = () => {
  1232. state.canDebug = true
  1233. ref_chat.value?.init()
  1234. }
  1235. const onRecall = () => {
  1236. state.recallConfig.transfer = {
  1237. config: null,
  1238. }
  1239. state.recallConfig.show = true
  1240. }
  1241. const getRecallConfig = (config) => {
  1242. state.form.datasetConfigs = { ...config }
  1243. }
  1244. onMounted(() => {
  1245. initDictionary()
  1246. initDetail()
  1247. })
  1248. const initDictionary = () => {
  1249. DictionaryStore.initWorkflows()
  1250. DictionaryStore.initModels()
  1251. DictionaryStore.initKnowledges(AppStore.tenantInfo?.id)
  1252. }
  1253. </script>
  1254. <style lang="scss" scoped>
  1255. :deep(.form) {
  1256. flex: 1;
  1257. overflow: hidden;
  1258. .el-form {
  1259. width: 100%;
  1260. height: 100%;
  1261. .el-row {
  1262. width: 100%;
  1263. height: 100%;
  1264. .transparent-input {
  1265. .el-input__wrapper {
  1266. padding: 0;
  1267. }
  1268. }
  1269. .advise {
  1270. .el-checkbox {
  1271. margin-right: 10px;
  1272. .el-checkbox__label {
  1273. padding-left: 4px;
  1274. }
  1275. }
  1276. }
  1277. }
  1278. }
  1279. }
  1280. .__czr-title_2 {
  1281. height: 2rem;
  1282. }
  1283. </style>