modeldetailed.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. <template>
  2. <div class="model-detail-root">
  3. <!-- 面包屑导航 -->
  4. <el-breadcrumb separator="/" class="model-breadcrumb">
  5. <el-breadcrumb-item>市场模型</el-breadcrumb-item>
  6. <el-breadcrumb-item>模型详情</el-breadcrumb-item>
  7. </el-breadcrumb>
  8. <div class="model-top-right-btns">
  9. <el-button type="plain" size="small" @click="returnModel" class="custom-button">
  10. <el-icon ><ArrowLeft /></el-icon>
  11. 返回列表
  12. </el-button>
  13. <el-button type="plain" size="small" class="custom-button">
  14. <el-icon><Refresh /></el-icon>
  15. 刷新
  16. </el-button>
  17. <el-button type="primary" size="small" class="custom-button" @click = "handleModelAdd">
  18. <el-icon><Edit /></el-icon>
  19. 编辑
  20. </el-button>
  21. <el-button type="danger" v-if="model.status===1" @click="handleToggle(false)" size="small" class="custom-button">
  22. <el-icon><SwitchButton /></el-icon>
  23. 禁用
  24. </el-button>
  25. <el-button type="success" v-if="model.status===0" @click="handleToggle(true)" size="small" class="custom-button">
  26. <el-icon><SwitchButton /></el-icon>
  27. 启用
  28. </el-button>
  29. </div>
  30. <!-- 顶部信息区(单卡片,左右分布) -->
  31. <el-card class="model-top-card" shadow="never">
  32. <div class="model-top-flex">
  33. <!-- 左侧基本信息 -->
  34. <div class="model-top-left">
  35. <div class="model-top-title">基本信息</div>
  36. <div class="model-top-row">
  37. <span class="model-top-label">模型名称:</span>
  38. <span class="model-top-value">{{model?.modelName}}</span>
  39. </div>
  40. <div class="model-top-row">
  41. <span class="model-top-label">模型类型:</span>
  42. <span class="model-top-value">{{model?.modelType}}</span>
  43. </div>
  44. <div class="model-top-row">
  45. <span class="model-top-label">模型版本:</span>
  46. <span class="model-top-value">{{model?.modelVersion}}</span>
  47. </div>
  48. <div class="model-top-row">
  49. <span class="model-top-label">状态:</span>
  50. <el-tag type="success" size="small" v-if="model?.status===1">启用</el-tag>
  51. <el-tag type="warning" size="small" v-else-if="model?.status === 0">测试中</el-tag>
  52. </div>
  53. <div class="model-top-row">
  54. <span class="model-top-label">API地址:</span>
  55. <a class="model-top-link" :href="model.baseUrl" target="_blank">{{model?.baseUrl}}</a>
  56. </div>
  57. <div class="model-top-row">
  58. <span class="model-top-label">描述:</span>
  59. <span class="model-top-value">{{model?.description}}</span>
  60. </div>
  61. </div>
  62. <!-- 右侧服务信息和按钮 -->
  63. <div class="model-top-right">
  64. <div class="model-top-right-info">
  65. <div style="display: flex"><span class="model-top-label">服务商:</span>&nbsp&nbspOpenAI</div>
  66. <div style="display: flex"><span class="model-top-label">优先级:</span>&nbsp&nbsp{{model?.priority}}</div>
  67. <div style="display: flex"><span class="model-top-label">创建时间:</span>&nbsp&nbsp{{model?.createTime}}</div>
  68. <div style="display: flex"><span class="model-top-label">更新时间:</span>&nbsp&nbsp{{model?.updateTime}}</div>
  69. </div>
  70. </div>
  71. </div>
  72. </el-card>
  73. <!-- 参数配置 -->
  74. <el-card class="model-section-card">
  75. <div class="model-section-title">
  76. <span>参数配置</span>
  77. <el-button type="primary" size="small" class="model-btn-add">+ 添加参数</el-button>
  78. </div>
  79. <el-table :data="model.parameters" class="model-param-table" border header-cell-class-name="model-th" cell-class-name="model-td">
  80. <el-table-column prop="paramName" label="参数名称" align="center" min-width="120" />
  81. <el-table-column prop="paramType" label="参数类型" align="center" min-width="120" />
  82. <el-table-column prop="defaultValue" label="默认值" align="center" min-width="100" />
  83. <el-table-column prop="isRequired" label="是否必需" align="center" min-width="100" />
  84. <el-table-column prop="description" label="描述" min-width="200" />
  85. <el-table-column label="操作" align="center" min-width="100">
  86. <template #default="scope">
  87. <el-button @click="handleParamEdit(scope.$index, scope.row)" type="text" size="small">编辑</el-button>
  88. <el-button @click="handleParamDelete(scope.$index, scope.row)" type="text" size="small">删除</el-button>
  89. </template>
  90. </el-table-column>
  91. </el-table>
  92. </el-card>
  93. <!-- 抽屉 -->
  94. <el-drawer
  95. v-model="drawerVisible"
  96. direction="rtl"
  97. size="40%"
  98. class="drawer-style"
  99. >
  100. <template #header="{close,titleId,titleClass}">
  101. <h2 :id="titleId" style="color:#000000">编辑参数</h2>
  102. </template>
  103. <div >
  104. <h4>模型</h4>
  105. <el-card class="model-card" style=" background:#F5F5F5;">
  106. GPT-4 (OpenAI)<br/>
  107. <span style="color: #777777">版本: gpt-4-1106-preview</span>
  108. </el-card>
  109. <h4>快速添加参数模板</h4>
  110. <el-row :gutter="20">
  111. <el-col :span="12">
  112. <el-card class="center-content" style="height: 150px;background: #FAFAFA" >
  113. <div>OpenAI Chat模板</div>
  114. <div style="color: #777777">6个参数</div>
  115. </el-card>
  116. </el-col>
  117. <el-col :span="12">
  118. <el-card class="center-content"style="background: #FAFAFA">
  119. <div>OpenAI Embedding模板</div>
  120. <div style="color: #777777">三个参数</div>
  121. </el-card>
  122. </el-col>
  123. </el-row>
  124. <h4>参数列表</h4>
  125. <div v-for="(item, index) in tempModel.parameters" :key="index" class="parameter-card">
  126. <el-card shadow="never" style="background-color: #FAFAFA" >
  127. <div class="card-content">
  128. <div class="param-name">{{ item.paramName }} <span style="color: #777777">{{item.paramType}}</span></div>
  129. <div class="actions" style="display: flex">
  130. <el-button @click="handleModelParamAdd(index, item)" type="text" size="small" ></el-button>
  131. <el-button @click="handleModelParamDelete(index, item)" type="text" size="small" style="margin-left:auto" ><el-icon color="red"><DeleteFilled /></el-icon></el-button>
  132. </div>
  133. </div>
  134. </el-card>
  135. </div>
  136. <el-card style="display: flex;justify-content: center; align-items: center; flex-direction: column;" :body-style="{padding:'1px'}">
  137. <el-button @click="addParameter" type="plain" >添加参数</el-button>
  138. </el-card>
  139. <div style="text-align: right; margin-top: 20px;">
  140. <el-button @click="drawerVisible = false">取消</el-button>
  141. <el-button @click="saveEdit" type="primary">保存</el-button>
  142. </div>
  143. </div>
  144. </el-drawer>
  145. <!-- 调用统计 -->
  146. <el-row :gutter="20" class="model-stat-row">
  147. <el-col :span="6" v-for="(item, idx) in statList" :key="item.label">
  148. <el-card class="model-stat-card" shadow="never">
  149. <div class="model-stat-label">{{ item.label }}</div>
  150. <div class="model-stat-value">{{ item.value }}</div>
  151. <div :class="['model-stat-trend', item.trend > 0 ? 'up' : 'down']">
  152. <span v-if="item.trend > 0">↑ {{ item.trend }}% 较上月</span>
  153. <span v-else>↓ {{ Math.abs(item.trend) }}% 较上月</span>
  154. </div>
  155. </el-card>
  156. </el-col>
  157. <div class="model-stat-select">
  158. <el-select v-model="statRange" size="small" style="width: 80px">
  159. <el-option label="本月" value="month" />
  160. <el-option label="本年" value="year" />
  161. </el-select>
  162. </div>
  163. </el-row>
  164. <!-- 调用趋势 -->
  165. <el-card class="model-section-card model-chart-section">
  166. <div class="model-chart-title">调用趋势</div>
  167. <div id="callTrendChart" style="width: 100%; height: 320px;"></div>
  168. <div class="model-chart-footer">调用趋势曲线数据</div>
  169. </el-card>
  170. </div>
  171. </template>
  172. <script setup lang="ts">
  173. import { ref, onMounted } from 'vue'
  174. import * as echarts from 'echarts'
  175. import { ArrowLeft, Refresh } from '@element-plus/icons-vue';
  176. import { useRoute,useRouter } from 'vue-router';
  177. import { ModelConfigVo} from '@/api/modelConfig/types'
  178. import {toggleStatus} from '@/api/modelConfig/index.js'
  179. const route = useRoute()
  180. const router = useRouter()
  181. const model = ref<ModelConfigVo>(route.query.model)
  182. model.value = {
  183. id:0,
  184. modelName: "GPT-4",
  185. modelType:"大语言模型(LLM)",
  186. modelVersion:"gpt-4-1106-preview",
  187. baseUrl:"https://api.openai.com/v1/chat/completions",
  188. apiKey: "",
  189. status:0,
  190. priority:10,
  191. description:"GPT-4是OpenAI最先进的大型语言模型,在各种专业和学术基准上表现最佳。相比GPT-3.5,GPT-4在创造性写作、编码和解决复杂问题方面表现出显著提升,能更好地遵循用户意图,理解和生成更长的内容。",
  192. createTime:"2025-05-15 10:30:15",
  193. updateTime:"2025-06-20 14:45:30",
  194. parameters: [
  195. { paramName: 'temperature', paramType: 'NUMBER', defaultValue: '0.7', isRequired: '否', description: '控制生成文本的随机性,值越小结果越随机' },
  196. { paramName: 'max_tokens', paramType: 'NUMBER', defaultValue: '1024', isRequired: '否', description: '生成文本的最大令牌数' },
  197. { paramName: 'top_p', paramType: 'NUMBER', defaultValue: '0.95', isRequired: '否', description: '核采样阈值,控制模型选择词的概率分布' },
  198. { paramName: 'frequency_penalty', paramType: 'NUMBER', defaultValue: '0', isRequired: '否', description: '减少重复词组的出现概率' },
  199. { paramName: 'presence_penalty', paramType: 'NUMBER', defaultValue: '0', isRequired: '否', description: '增加新话题出现的概率' },
  200. { paramName: 'stream', paramType: 'BOOLEAN', defaultValue: 'false', isRequired: '否', description: '是否启用流式输出' }
  201. ]
  202. }
  203. //临时抽屉数据
  204. const tempModel = ref<ModelConfigVo>(model)
  205. // 统计卡片数据
  206. const statList = ref([
  207. { label: '调用次数', value: '156,783', trend: 12.5 },
  208. { label: '平均响应时间', value: '1.68s', trend: -0.2 },
  209. { label: '成功率', value: '99.87%', trend: 0.05 },
  210. { label: 'Token消耗', value: '5.6M', trend: 18.3 }
  211. ])
  212. // 其他响应式数据
  213. const statRange = ref('month')
  214. const drawerVisible = ref(false)
  215. const currentRowIndex = ref(-1)
  216. // 生命周期钩子
  217. onMounted(() => {
  218. initCallTrendChart()
  219. })
  220. // 方法
  221. const handleToggle = async (value:boolean) =>{
  222. const res = await toggleStatus(value, model.id);
  223. if(res.code === 0){
  224. model.value.status = model.value.status === 1 ? 0 : 1;
  225. }
  226. }
  227. const handleModelAdd = () => {
  228. drawerVisible.value = true
  229. }
  230. const handleModelParamDelete = (index,item) =>{
  231. tempModel.parameters.splice(index,1)
  232. }
  233. const handleParamDelete = (index) => {
  234. model.parameters.value.splice(index, 1)
  235. }
  236. const saveEdit = () => {
  237. tableData.value[currentRowIndex.value] = { ...currentRow.value }
  238. drawerVisible.value = false
  239. }
  240. const returnModel = () => {
  241. router.push('/modelMarket')
  242. }
  243. const initCallTrendChart = () => {
  244. const callTrendChart = echarts.init(document.getElementById('callTrendChart'))
  245. const option = {
  246. tooltip: {},
  247. xAxis: {
  248. type: 'category',
  249. data: ['1月', '2月', '3月', '4月', '5月', '6月'],
  250. axisLine: { lineStyle: { color: '#e5e6eb' } },
  251. axisLabel: { color: '#888' },
  252. },
  253. yAxis: {
  254. type: 'value',
  255. axisLine: { lineStyle: { color: '#e5e6eb' } },
  256. splitLine: { lineStyle: { color: '#f0f0f0' } },
  257. axisLabel: { color: '#888' },
  258. },
  259. series: [
  260. {
  261. data: [120, 200, 150, 80, 70, 110],
  262. type: 'bar',
  263. name: '调用次数',
  264. itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] },
  265. barWidth: 24,
  266. },
  267. ],
  268. grid: { left: 40, right: 20, top: 30, bottom: 30 },
  269. }
  270. callTrendChart.setOption(option)
  271. }
  272. </script>
  273. <style scoped>
  274. .actions{
  275. display: flex;
  276. }
  277. .drawer-style{
  278. header.el-drawer__header{
  279. margin-bottom: 0 !important;
  280. }
  281. }
  282. :deep(header.el-drawer__header){
  283. margin-bottom: 0 !important;
  284. }
  285. /* 确保图标靠右对齐 */
  286. .parameter-card {
  287. margin-bottom: 0;
  288. }
  289. .card-content {
  290. display: flex;
  291. justify-content: space-between;
  292. width: 100%;
  293. }
  294. .param-name {
  295. font-size: 14px;
  296. color: #606266;
  297. }
  298. .center-content {
  299. display: flex;
  300. justify-content: center; /* 水平居中 */
  301. align-items: center; /* 垂直居中 */
  302. height: 100%; /* 确保容器有足够的高度 */
  303. }
  304. .model-detail-root {
  305. background: #f7f8fa;
  306. min-height: 100vh;
  307. padding: 0 24px 24px 24px;
  308. }
  309. .model-breadcrumb {
  310. margin: 18px 0 8px 0;
  311. }
  312. .model-top-card {
  313. border-radius: 10px;
  314. border: 1px solid #e5e6eb;
  315. background: #fff;
  316. box-shadow: none;
  317. padding: 0 32px 0 32px;
  318. margin-bottom: 24px;
  319. }
  320. .model-top-flex {
  321. display: flex;
  322. flex-direction: row;
  323. justify-content: space-between;
  324. align-items: flex-start;
  325. min-height: 160px;
  326. padding: 24px 0 18px 0;
  327. }
  328. .model-top-left {
  329. flex: 1.5;
  330. display: flex;
  331. flex-direction: column;
  332. justify-content: flex-start;
  333. }
  334. .model-top-title {
  335. font-size: 16px;
  336. font-weight: 600;
  337. margin-bottom: 12px;
  338. }
  339. .model-top-row {
  340. display: flex;
  341. align-items: center;
  342. font-size: 13px;
  343. margin-bottom: 4px;
  344. gap: 8px;
  345. }
  346. .model-top-label {
  347. color: #888;
  348. min-width: 80px;
  349. margin-right: 2px;
  350. }
  351. .model-top-value {
  352. color: #222;
  353. margin-right: 24px;
  354. word-break: break-all;
  355. }
  356. .model-top-link {
  357. color: #409eff;
  358. text-decoration: underline;
  359. }
  360. .model-top-right {
  361. flex: 1;
  362. display: flex;
  363. flex-direction: row;
  364. align-items: flex-start;
  365. justify-content: space-between;
  366. min-width: 360px;
  367. margin-left: 32px;
  368. margin-right: 32px;
  369. gap: 32px;
  370. }
  371. .model-top-right-info {
  372. color: #222;
  373. font-size: 13px;
  374. margin-bottom: 0;
  375. text-align: left;
  376. display: flex;
  377. flex-direction: column;
  378. align-items: flex-start;
  379. justify-content: center;
  380. flex: 1;
  381. min-width: 180px;
  382. }
  383. .model-top-right-btns {
  384. display: flex;
  385. flex-direction: row;
  386. gap:0.1px;
  387. margin-top: 0;
  388. align-items: flex-start;
  389. justify-content: flex-end;
  390. }
  391. .model-section-card {
  392. border-radius: 10px;
  393. border: 1px solid #e5e6eb;
  394. background: #fff;
  395. box-shadow: none;
  396. margin-top: 24px;
  397. padding: 0 0 18px 0;
  398. }
  399. .model-section-title {
  400. display: flex;
  401. justify-content: space-between;
  402. align-items: center;
  403. padding: 0 24px;
  404. font-size: 15px;
  405. font-weight: 500;
  406. height: 48px;
  407. background: #fff;
  408. border-bottom: 1px solid #f0f0f0;
  409. }
  410. .model-btn-add {
  411. min-width: 90px;
  412. margin-top: 0;
  413. align-self: flex-start;
  414. }
  415. .model-param-table {
  416. margin: 0 24px 0 24px;
  417. font-size: 13px;
  418. --el-table-border-color: #e5e6eb;
  419. --el-table-header-bg-color: #f7f8fa;
  420. --el-table-header-text-color: #888;
  421. --el-table-row-hover-bg-color: #f5f7fa;
  422. }
  423. .model-th {
  424. background: #f7f8fa !important;
  425. color: #888 !important;
  426. font-weight: 500 !important;
  427. height: 38px !important;
  428. }
  429. .model-td {
  430. height: 38px !important;
  431. padding: 0 8px !important;
  432. }
  433. .model-stat-row {
  434. margin-top: 24px;
  435. margin-bottom: 0;
  436. position: relative;
  437. }
  438. .model-stat-card {
  439. border-radius: 10px;
  440. border: 1px solid #e5e6eb;
  441. background: #fff;
  442. box-shadow: none;
  443. min-height: 100px;
  444. display: flex;
  445. flex-direction: column;
  446. justify-content: center;
  447. align-items: flex-start;
  448. padding: 18px 0 12px 24px;
  449. }
  450. .model-stat-label {
  451. color: #888;
  452. font-size: 13px;
  453. margin-bottom: 2px;
  454. }
  455. .model-stat-value {
  456. font-size: 22px;
  457. font-weight: 600;
  458. color: #222;
  459. margin: 2px 0 2px 0;
  460. }
  461. .model-stat-trend {
  462. font-size: 12px;
  463. margin-top: 2px;
  464. }
  465. .model-stat-trend.up {
  466. color: #67c23a;
  467. }
  468. .model-stat-trend.down {
  469. color: #f56c6c;
  470. }
  471. .model-stat-select {
  472. position: absolute;
  473. right: 24px;
  474. top: 0;
  475. }
  476. .model-chart-section {
  477. margin-top: 24px;
  478. padding: 0 0 18px 0;
  479. }
  480. .model-chart-title {
  481. font-size: 15px;
  482. font-weight: 500;
  483. color: #222;
  484. margin-bottom: 8px;
  485. padding-left: 24px;
  486. padding-top: 18px;
  487. }
  488. .model-chart-footer {
  489. color: #bfbfbf;
  490. font-size: 12px;
  491. text-align: right;
  492. margin-top: 8px;
  493. padding-right: 24px;
  494. }
  495. </style>