index.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <script lang="ts" setup>
  2. import { computed, nextTick, ref, watch } from 'vue'
  3. import InputTab from '@/features/RunWorkflow/components/InputTab.vue'
  4. import Chat from './Chat.vue'
  5. import ResizableDrawer from '@/components/ResizableDrawer/index.vue'
  6. import { useI18n } from '@/composables/useI18n'
  7. import { Icon, IconButton } from '@repo/ui'
  8. import type { IWorkflow, IWorkflowNode } from '@repo/workflow'
  9. import type { StartVariable } from '@/nodes/src/start'
  10. const props = withDefaults(
  11. defineProps<{
  12. visible: boolean
  13. workflow: IWorkflow
  14. startNode: IWorkflowNode | null
  15. visibleVariables: StartVariable[]
  16. inputValues: Record<string, any>
  17. jsonDrafts: Record<string, string>
  18. validationErrors: Record<string, string>
  19. baseParams: Record<string, any>
  20. isRunning: boolean
  21. }>(),
  22. {
  23. visible: false,
  24. startNode: null,
  25. baseParams: () => ({})
  26. }
  27. )
  28. const emit = defineEmits<{
  29. 'update:visible': [value: boolean]
  30. 'validate-send': [done: (params: Record<string, any> | false) => void]
  31. 'run-started': [nodeId: string]
  32. cancel: []
  33. }>()
  34. const { t } = useI18n()
  35. const showForm = ref(false)
  36. const chatContainerRef = ref<InstanceType<typeof Chat>>()
  37. const hasVisibleForm = computed(() => props.visibleVariables.length > 0)
  38. const closeDrawer = () => {
  39. handleDrawerUpdate(false)
  40. }
  41. const handleValidateSend = () =>
  42. new Promise<Record<string, any> | false>((resolve) => {
  43. emit('validate-send', resolve)
  44. })
  45. const handleDrawerUpdate = (value: boolean) => {
  46. if (!value) {
  47. handleCancel()
  48. }
  49. emit('update:visible', value)
  50. }
  51. const handleFirstSend = () => {
  52. if (hasVisibleForm.value) {
  53. showForm.value = false
  54. }
  55. }
  56. const handleResetConversation = () => {
  57. chatContainerRef.value?.resetConversation?.()
  58. if (hasVisibleForm.value) {
  59. showForm.value = true
  60. }
  61. }
  62. const handleCancel = () => {
  63. emit('cancel')
  64. }
  65. watch(
  66. () => [props.visible, hasVisibleForm.value],
  67. ([visible, hasForm]) => {
  68. if (!visible) return
  69. showForm.value = !!hasForm
  70. },
  71. { immediate: true }
  72. )
  73. defineExpose({
  74. scrollToBottom: () => {
  75. nextTick(() => {
  76. chatContainerRef.value?.scrollToBottom?.()
  77. })
  78. }
  79. })
  80. </script>
  81. <template>
  82. <div class="chat-drawer">
  83. <ResizableDrawer
  84. :visible="visible"
  85. :default-width="520"
  86. :min-width="420"
  87. :max-width-ratio="0.82"
  88. :z-index="1002"
  89. class="chat-drawer__panel"
  90. >
  91. <header class="chat-drawer__header">
  92. <h4>{{ t('pages.runWorkflow.chatDialogTitle') }}</h4>
  93. <span class="flex items-center gap-16px">
  94. <!-- 重置按钮 -->
  95. <Icon
  96. icon="lucide:refresh-ccw"
  97. height="18"
  98. width="18"
  99. class="cursor-pointer"
  100. @click="handleResetConversation"
  101. />
  102. <!-- 展示/隐藏表单 -->
  103. <IconButton
  104. v-if="hasVisibleForm"
  105. icon="lucide:sliders-horizontal"
  106. :type="showForm ? 'primary' : 'default'"
  107. :icon-color="showForm ? '#fff' : undefined"
  108. height="18"
  109. width="18"
  110. @click="showForm = !showForm"
  111. size="small"
  112. square
  113. />
  114. <!-- 关闭 -->
  115. <Icon
  116. icon="lucide:x"
  117. height="18"
  118. width="18"
  119. class="cursor-pointer"
  120. @click="closeDrawer"
  121. />
  122. </span>
  123. </header>
  124. <div class="chat-drawer__body">
  125. <div v-if="hasVisibleForm && showForm" class="chat-drawer__form">
  126. <el-card>
  127. <InputTab
  128. :start-node="startNode"
  129. :visible-variables="visibleVariables"
  130. :input-values="inputValues"
  131. :json-drafts="jsonDrafts"
  132. :validation-errors="validationErrors"
  133. :is-running="isRunning"
  134. :show-action-bar="false"
  135. :paneStyle="{ height: 'auto' }"
  136. />
  137. </el-card>
  138. </div>
  139. <div class="chat-drawer__chat">
  140. <Chat
  141. ref="chatContainerRef"
  142. :node="startNode || undefined"
  143. :workflow-id="workflow.id"
  144. :base-params="baseParams"
  145. :validate-before-send="handleValidateSend"
  146. :on-first-send="handleFirstSend"
  147. show-workflow-trace
  148. @run-started="emit('run-started', $event)"
  149. />
  150. </div>
  151. </div>
  152. </ResizableDrawer>
  153. </div>
  154. </template>
  155. <style lang="less" scoped>
  156. .chat-drawer {
  157. z-index: 1002;
  158. }
  159. .chat-drawer__header {
  160. height: 66px;
  161. padding: 16px 16px 0;
  162. display: flex;
  163. align-items: flex-start;
  164. justify-content: space-between;
  165. color: var(--text-primary);
  166. h4 {
  167. margin: 0;
  168. font-size: 15px;
  169. font-weight: 600;
  170. }
  171. }
  172. .chat-drawer__body {
  173. flex: 1;
  174. min-height: 0;
  175. display: flex;
  176. flex-direction: column;
  177. gap: 12px;
  178. overflow: hidden;
  179. border-radius: 8px;
  180. overflow-y: auto;
  181. }
  182. .chat-drawer__form {
  183. padding: 12px;
  184. flex-shrink: 0;
  185. max-height: 60%;
  186. overflow-y: auto;
  187. }
  188. .chat-drawer__chat {
  189. flex: 1;
  190. min-height: 0;
  191. }
  192. </style>