DetailModal.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. <template>
  2. <el-dialog
  3. :model-value="modelValue"
  4. :title="t('pages.agent.agentDetail')"
  5. width="760px"
  6. destroy-on-close
  7. @update:model-value="emit('update:modelValue', $event)"
  8. >
  9. <div v-if="detailItem" class="detail-modal">
  10. <div class="hero">
  11. <div class="hero__avatar">{{ detailItem.avatar?.trim() || '🤖' }}</div>
  12. <div class="hero__content">
  13. <div class="hero__title">
  14. {{ detailItem.name || t('pages.agent.unnamedAgent') }}
  15. </div>
  16. <div class="hero__meta">
  17. <el-tag effect="plain">{{ formatModeLabel(detailItem.mode) }}</el-tag>
  18. <el-tag type="info" effect="plain">{{ formatTypeLabel(detailItem.type) }}</el-tag>
  19. <el-tag
  20. v-if="detailItem.is_builtin"
  21. type="success"
  22. effect="plain"
  23. >
  24. Built-in
  25. </el-tag>
  26. </div>
  27. <div class="hero__desc">
  28. {{ detailItem.description || t('pages.agent.noDescription') }}
  29. </div>
  30. </div>
  31. </div>
  32. <div class="section">
  33. <div class="section__title">基础信息</div>
  34. <div class="info-grid">
  35. <div v-for="item in basicItems" :key="item.label" class="info-card">
  36. <div class="info-card__label">{{ item.label }}</div>
  37. <div class="info-card__value">{{ item.value }}</div>
  38. </div>
  39. </div>
  40. </div>
  41. <div class="section">
  42. <div class="section__title">配置摘要</div>
  43. <div class="info-grid">
  44. <div v-for="item in configItems" :key="item.label" class="info-card">
  45. <div class="info-card__label">{{ item.label }}</div>
  46. <div class="info-card__value">{{ item.value }}</div>
  47. </div>
  48. </div>
  49. </div>
  50. <div v-if="promptItems.length" class="section">
  51. <div class="section__title">提示词</div>
  52. <div class="prompt-list">
  53. <div v-for="item in promptItems" :key="item.label" class="prompt-card">
  54. <div class="prompt-card__head">{{ item.label }}</div>
  55. <pre class="prompt-card__body">{{ item.value }}</pre>
  56. </div>
  57. </div>
  58. </div>
  59. </div>
  60. </el-dialog>
  61. </template>
  62. <script setup lang="ts">
  63. import { computed } from 'vue'
  64. import { useI18n } from '@/composables/useI18n'
  65. import type { AgentItem } from '../type'
  66. const props = defineProps<{
  67. modelValue: boolean
  68. detailItem: AgentItem | null
  69. modelNameMap: Record<string, string>
  70. }>()
  71. const emit = defineEmits<{
  72. 'update:modelValue': [value: boolean]
  73. }>()
  74. const { t } = useI18n()
  75. function formatModeLabel(mode?: string) {
  76. if (mode === 'quick-answer') return t('pages.agent.modeQuickAnswer')
  77. if (mode === 'smart-reasoning') return t('pages.agent.modeSmartReasoning')
  78. if (mode === 'agent') return 'Agent'
  79. return t('pages.agent.modeUnset')
  80. }
  81. function formatTypeLabel(type?: string) {
  82. if (type === 'knowledgeqa') return 'Knowledge'
  83. if (type === 'agent') return 'Agent'
  84. return type || '-'
  85. }
  86. function formatBoolean(value?: boolean) {
  87. return value ? '开启' : '关闭'
  88. }
  89. function formatModelName(id?: string) {
  90. if (!id) return '-'
  91. return props.modelNameMap[id] || id
  92. }
  93. const basicItems = computed(() => {
  94. const item = props.detailItem
  95. if (!item) return []
  96. return [
  97. { label: '模型', value: formatModelName(item.config?.model_config?.model_id) },
  98. { label: '重排模型', value: formatModelName(item.config?.model_config?.rerank_model_id) },
  99. { label: 'VLM 模型', value: formatModelName(item.config?.img_vlm_config?.vlm_model_id) },
  100. { label: 'ASR 模型', value: formatModelName(item.config?.img_vlm_config?.asr_model_id) },
  101. { label: '创建时间', value: item.creationTime || '-' },
  102. { label: '更新时间', value: item.updateTime || '-' }
  103. ]
  104. })
  105. const configItems = computed(() => {
  106. const item = props.detailItem
  107. if (!item) return []
  108. return [
  109. { label: '温度', value: `${item.config?.model_config?.temperature ?? '-'}` },
  110. {
  111. label: '最大 Token',
  112. value: `${item.config?.model_config?.max_completion_tokens ?? '-'}`
  113. },
  114. { label: '思考模式', value: formatBoolean(item.config?.model_config?.thinking) },
  115. {
  116. label: '多轮对话',
  117. value: formatBoolean(item.config?.multiple_config?.multi_turn_enabled)
  118. },
  119. {
  120. label: '知识库数量',
  121. value: `${item.config?.kb_config?.knowledge_bases?.length ?? 0}`
  122. },
  123. {
  124. label: '工具数量',
  125. value: `${item.config?.setting_config?.allowed_tools?.length ?? 0}`
  126. }
  127. ]
  128. })
  129. const promptItems = computed(() => {
  130. const item = props.detailItem
  131. if (!item) return []
  132. return [
  133. {
  134. label: '系统提示词',
  135. value: item.config?.basic_config?.system_prompt || ''
  136. },
  137. {
  138. label: '上下文模板',
  139. value: item.config?.basic_config?.context_template || ''
  140. },
  141. {
  142. label: '改写系统提示词',
  143. value: item.config?.advanced_config?.rewrite_prompt_system || ''
  144. },
  145. {
  146. label: '改写用户提示词',
  147. value: item.config?.advanced_config?.rewrite_prompt_user || ''
  148. },
  149. {
  150. label: '兜底提示词',
  151. value: item.config?.advanced_config?.fallback_prompt || ''
  152. }
  153. ].filter((entry) => entry.value.trim())
  154. })
  155. </script>
  156. <style scoped lang="less">
  157. .detail-modal {
  158. display: flex;
  159. flex-direction: column;
  160. gap: 20px;
  161. }
  162. .hero {
  163. display: flex;
  164. gap: 16px;
  165. padding: 18px;
  166. border: 1px solid var(--border-light);
  167. border-radius: 18px;
  168. background: var(--bg-base);
  169. }
  170. .hero__avatar {
  171. width: 72px;
  172. height: 72px;
  173. flex-shrink: 0;
  174. border-radius: 20px;
  175. display: grid;
  176. place-items: center;
  177. font-size: 34px;
  178. background: var(--bg-overlay);
  179. }
  180. .hero__content {
  181. min-width: 0;
  182. display: flex;
  183. flex-direction: column;
  184. gap: 10px;
  185. }
  186. .hero__title {
  187. font-size: 22px;
  188. font-weight: 700;
  189. color: var(--text-strong);
  190. word-break: break-word;
  191. }
  192. .hero__meta {
  193. display: flex;
  194. flex-wrap: wrap;
  195. gap: 8px;
  196. }
  197. .hero__desc {
  198. color: var(--text-secondary);
  199. line-height: 1.7;
  200. white-space: pre-wrap;
  201. word-break: break-word;
  202. }
  203. .section {
  204. display: flex;
  205. flex-direction: column;
  206. gap: 12px;
  207. }
  208. .section__title {
  209. font-size: 15px;
  210. font-weight: 700;
  211. color: var(--text-strong);
  212. }
  213. .info-grid {
  214. display: grid;
  215. grid-template-columns: repeat(2, minmax(0, 1fr));
  216. gap: 12px;
  217. }
  218. .info-card {
  219. padding: 14px 16px;
  220. border: 1px solid var(--border-light);
  221. border-radius: 14px;
  222. background: var(--bg-base);
  223. }
  224. .info-card__label {
  225. font-size: 12px;
  226. color: var(--text-tertiary);
  227. }
  228. .info-card__value {
  229. margin-top: 6px;
  230. font-size: 14px;
  231. line-height: 1.6;
  232. color: var(--text-primary);
  233. word-break: break-word;
  234. }
  235. .prompt-list {
  236. display: flex;
  237. flex-direction: column;
  238. gap: 12px;
  239. }
  240. .prompt-card {
  241. border: 1px solid var(--border-light);
  242. border-radius: 14px;
  243. background: var(--bg-base);
  244. overflow: hidden;
  245. }
  246. .prompt-card__head {
  247. padding: 12px 14px;
  248. border-bottom: 1px solid var(--border-light);
  249. font-size: 13px;
  250. font-weight: 600;
  251. color: var(--text-secondary);
  252. background: var(--bg-overlay);
  253. }
  254. .prompt-card__body {
  255. margin: 0;
  256. padding: 14px;
  257. max-height: 280px;
  258. overflow: auto;
  259. white-space: pre-wrap;
  260. word-break: break-word;
  261. font-size: 13px;
  262. line-height: 1.7;
  263. color: var(--text-primary);
  264. font-family: Monaco, Consolas, 'Courier New', monospace;
  265. }
  266. @media (max-width: 768px) {
  267. .hero {
  268. flex-direction: column;
  269. }
  270. .info-grid {
  271. grid-template-columns: 1fr;
  272. }
  273. }
  274. </style>