已修改16个文件
已复制1个文件
已添加7个文件
已重命名1个文件
| | |
| | | }, |
| | | |
| | | // 获得模型列表 |
| | | getModelSimpleList: async (type?: number) => { |
| | | getModelSimpleList: async (type?: string) => { |
| | | return await request.get({ |
| | | url: `/ai/model/simple-list`, |
| | | params: { |
对比新文件 |
| | |
| | | import request from '@/config/axios' |
| | | |
| | | // 大模型调度建议 VO |
| | | export interface ScheduleSuggestVO { |
| | | id: number // id |
| | | modelId: number // 模型id |
| | | conversationId: number // 会话id |
| | | messageId: number // 消息id |
| | | content: string // 调度建议 |
| | | status: number // 状态(0-未处理 1-已采纳 2-已忽略) |
| | | createTime: Date // 创建时间 |
| | | } |
| | | |
| | | // 大模型调度建议 API |
| | | export const ScheduleSuggestApi = { |
| | | // 查询大模型调度建议分页 |
| | | getScheduleSuggestPage: async (params: any) => { |
| | | return await request.get({ url: `/ai/schedule-suggest/page`, params }) |
| | | }, |
| | | |
| | | // 查询大模型调度建议详情 |
| | | getScheduleSuggest: async (id: number) => { |
| | | return await request.get({ url: `/ai/schedule-suggest/get?id=` + id }) |
| | | }, |
| | | |
| | | // 查询大模型调度建议详情 |
| | | getTopScheduleSuggests: async (top: number) => { |
| | | return await request.get({ url: `/ai/schedule-suggest/simple-list?top=` + top }) |
| | | }, |
| | | |
| | | // 新增大模型调度建议 |
| | | createScheduleSuggest: async (data: ScheduleSuggestVO) => { |
| | | return await request.post({ url: `/ai/schedule-suggest/create`, data }) |
| | | }, |
| | | |
| | | // 修改大模型调度建议 |
| | | updateScheduleSuggest: async (data: ScheduleSuggestVO) => { |
| | | return await request.put({ url: `/ai/schedule-suggest/update`, data }) |
| | | }, |
| | | |
| | | // 采纳忽略取消采纳 |
| | | operateScheduleSuggest: async (data: ScheduleSuggestVO) => { |
| | | return await request.put({ url: `/ai/schedule-suggest/operate-suggest`, data}) |
| | | }, |
| | | |
| | | // 删除大模型调度建议 |
| | | deleteScheduleSuggest: async (id: number) => { |
| | | return await request.delete({ url: `/ai/schedule-suggest/delete?id=` + id }) |
| | | }, |
| | | |
| | | // 导出大模型调度建议 Excel |
| | | exportScheduleSuggest: async (params) => { |
| | | return await request.download({ url: `/ai/schedule-suggest/export-excel`, params }) |
| | | }, |
| | | } |
| | |
| | | return request.get({ url: '/model/mpk/file/list', params}) |
| | | } |
| | | |
| | | export const aiList = (params) => { |
| | | return request.get({ url: '/ai/question-template/modelList', params}) |
| | | } |
| | | |
| | | export const publish = (params) => { |
| | | return request.post({ url: '/model/mpk/file/publish', data: params}) |
| | | } |
文件名从 src/components/Dialog/src/DialogHistory.vue 修改 |
| | |
| | | <script lang="ts" setup> |
| | | import { propTypes } from '@/utils/propTypes' |
| | | import { isNumber } from '@/utils/is' |
| | | defineOptions({ name: 'DialogHistory' }) |
| | | defineOptions({ name: 'DialogAi' }) |
| | | |
| | | const slots = useSlots() |
| | | |
| | |
| | | padding: 0; |
| | | margin-right: 0 !important; |
| | | background: |
| | | url("@/assets/ai/zhuanlu/common_title.png") left no-repeat, |
| | | url("@/assets/ai/zhuanlu/history_title.png") left no-repeat, |
| | | linear-gradient(to bottom, #0a1633dd, #0a1633dd); /* 叠加深色遮罩 */ |
| | | div { |
| | | color: #73C4FF; |
copy from src/components/Dialog/src/DialogHistory.vue
copy to src/components/Dialog/src/DialogSuggest.vue
文件从 src/components/Dialog/src/DialogHistory.vue 复制 |
| | |
| | | <script lang="ts" setup> |
| | | import { propTypes } from '@/utils/propTypes' |
| | | import { isNumber } from '@/utils/is' |
| | | defineOptions({ name: 'DialogHistory' }) |
| | | defineOptions({ name: 'Dialog' }) |
| | | |
| | | const slots = useSlots() |
| | | |
| | |
| | | modelValue: propTypes.bool.def(false), |
| | | title: propTypes.string.def('Dialog'), |
| | | fullscreen: propTypes.bool.def(true), |
| | | width: propTypes.oneOfType([String, Number]).def('30%'), |
| | | width: propTypes.oneOfType([String, Number]).def('40%'), |
| | | scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话,按照 maxHeight 设置最大高度 |
| | | maxHeight: propTypes.oneOfType([String, Number]).def('400px') |
| | | }) |
| | |
| | | } |
| | | ) |
| | | |
| | | const dialogStyle = computed(() => { |
| | | return { |
| | | height: unref(dialogHeight) |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <ElDialog |
| | | v-bind="getBindValue" |
| | | :fullscreen="isFullscreen" |
| | | :close-on-click-modal="true" |
| | | :fullscreen="isFullscreen" |
| | | :width="width" |
| | | destroy-on-close |
| | | lock-scroll |
| | | draggable |
| | | class="history-dialog" |
| | | class="com-dialog" |
| | | :show-close="false" |
| | | > |
| | | <template #header="{ close }"> |
| | | <div class="relative h-30px flex items-center justify-between pl-15px pr-15px"> |
| | | <div class="relative h-54px flex items-center justify-between pl-15px pr-15px"> |
| | | <slot name="title"> |
| | | {{ title }} |
| | | </slot> |
| | | <div |
| | | class="absolute right-15px top-[50%] h-40px flex translate-y-[-50%] items-center justify-between" |
| | | class="absolute right-15px top-[50%] h-54px flex translate-y-[-50%] items-center justify-between" |
| | | > |
| | | <Icon |
| | | v-if="fullscreen" |
| | | class="is-hover mr-10px cursor-pointer" |
| | | :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'" |
| | | color="var(--el-color-info)" |
| | | hover-color="var(--el-color-primary)" |
| | | @click="toggleFull" |
| | | /> |
| | | <Icon |
| | | class="is-hover cursor-pointer" |
| | | icon="ep:close" |
| | | hover-color="var(--el-color-primary)" |
| | | color="#73C4FF" |
| | | color="var(--el-color-info)" |
| | | @click="close" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <slot></slot> |
| | | <ElScrollbar v-if="scroll" :style="dialogStyle"> |
| | | <slot></slot> |
| | | </ElScrollbar> |
| | | <slot v-else></slot> |
| | | <template v-if="slots.footer" #footer> |
| | | <slot name="footer"></slot> |
| | | </template> |
| | |
| | | </template> |
| | | |
| | | <style lang="scss"> |
| | | .history-dialog { |
| | | height: 90vh; |
| | | .com-dialog { |
| | | height: 62vh; |
| | | color: #73C4FF; |
| | | margin-top: 30px; |
| | | background: rgba(3,29,76,0.79); |
| | | border-radius: 4px 4px 4px 4px; |
| | | border: 1px solid; |
| | |
| | | padding: 0; |
| | | margin-right: 0 !important; |
| | | background: |
| | | url("@/assets/ai/zhuanlu/common_title.png") left no-repeat, |
| | | url("@/assets/ai/zhuanlu/suggest_title.png") left no-repeat, |
| | | linear-gradient(to bottom, #0a1633dd, #0a1633dd); /* 叠加深色遮罩 */ |
| | | div { |
| | | color: #73C4FF; |
| | |
| | | color: #73C4FF; |
| | | top: 0; |
| | | } |
| | | |
| | | &__body { |
| | | padding: 15px !important; |
| | | } |
| | | |
| | | &__footer { |
| | | border-top: 1px solid var(--el-border-color); |
| | | } |
| | | |
| | | &__headerbtn { |
| | | top: 0; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | } |
| | | } |
| | | // 获得下拉数据 |
| | | models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT) |
| | | models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT + "," + AiModelTypeEnum.LLM) |
| | | } |
| | | defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
| | | |
| | |
| | | import * as authUtil from "@/utils/auth"; |
| | | import {refreshToken} from "@/api/login"; |
| | | import {formatToDateTime} from "@/utils/dateUtil"; |
| | | import {ElLoading} from "element-plus"; |
| | | import { formatReasoningContent } from '@/views/ai/utils/utils' |
| | | |
| | | /** AI 聊天对话 列表 */ |
| | | defineOptions({ name: 'NormalConversation' }) |
| | |
| | | return [] |
| | | }) |
| | | |
| | | // //处理调度推理结论(deepSeek) |
| | | // const dealResult = (conversations: any) => { |
| | | // const regex = /<think>(\n*)([\s\S]*?)(\n*)<\/think>(\n*)([\s\S]*)/; |
| | | // conversations.forEach((conversation) => { |
| | | // if(conversation.content.includes('<\/think>')) { |
| | | // conversation.thinkingFlag = false |
| | | // } else { |
| | | // conversation.thinkingFlag = true |
| | | // } |
| | | // const match = conversation.content.match(regex); |
| | | // if(match) { |
| | | // conversation.thinking = match[2]; |
| | | // conversation.conclusion = match[5] |
| | | // } |
| | | // }) |
| | | // } |
| | | |
| | | //处理调度推理结论(微调大模型) |
| | | const dealResult = (messages: any) => { |
| | | messages.forEach((message) => { |
| | |
| | | } else { |
| | | message.thinking = message.content |
| | | } |
| | | // 处理推理思路内容 |
| | | message.thinking = formatReasoningContent(message.thinking); |
| | | } |
| | | }) |
| | | } |
| | |
| | | position: absolute; |
| | | left: 320px; // 初始展开位置 |
| | | top: 40%; |
| | | z-index: 1000; |
| | | z-index: 1; |
| | | width: 20px; |
| | | height: 80px; |
| | | background: rgba(115, 196, 255, 0.5); |
| | |
| | | type: Boolean || null, |
| | | required: true |
| | | }, |
| | | defaultMessage: {} |
| | | defaultMessage: { |
| | | type: Object as PropType<ChatMessageVO> |
| | | } |
| | | }) |
| | | |
| | | // 定义钩子 |
| | |
| | | } |
| | | } |
| | | // 获得下拉数据 |
| | | models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT) |
| | | models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.LLM) |
| | | } |
| | | defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
| | | |
| | |
| | | } |
| | | } |
| | | // 获得下拉数据 |
| | | models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT) |
| | | models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.LLM) |
| | | } |
| | | defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
| | | |
| | |
| | | <template> |
| | | <DialogHistory title="历史建议" v-model="dialogVisible" width="1200" custom-class="transparent-dialog"> |
| | | <DialogAi title="" v-model="dialogVisible" width="1200" custom-class="transparent-dialog"> |
| | | <!-- 搜索工作栏 --> |
| | | <el-form |
| | | class="-mb-15px query-area" |
| | |
| | | v-model:limit="queryParams.pageSize" |
| | | @pagination="handleQuery" |
| | | /> |
| | | </DialogHistory> |
| | | </DialogAi> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | |
| | | /** 回到底部 */ |
| | | const handleGoBottom = async () => { |
| | | const scrollContainer = messageContainer.value |
| | | console.log(scrollContainer.scrollHeight) |
| | | scrollContainer.scrollTop = scrollContainer.scrollHeight |
| | | } |
| | | |
| | | /** 回到顶部 */ |
| | | const handlerGoTop = async () => { |
| | | const scrollContainer = messageContainer.value |
| | | console.log(scrollContainer.scrollHeight) |
| | | scrollContainer.scrollTop = 0 |
| | | } |
| | | |
| | |
| | | <template> |
| | | <div ref="messageContainer" class="h-100%"> |
| | | <div ref="messageContainer" class="h-100% relative"> |
| | | <div class="chat-list" v-for="(item, index) in list" :key="index"> |
| | | <!-- 靠左 message:system、assistant 类型 --> |
| | | <div class="left-message message-item" v-if="item.type !== 'user'"> |
| | |
| | | import MarkdownView from '@/components/MarkdownView/index.vue' |
| | | import { useClipboard } from '@vueuse/core' |
| | | import { ArrowDownBold} from '@element-plus/icons-vue' |
| | | import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message' |
| | | import { ChatMessageVO } from '@/api/ai/chat/message' |
| | | import { ChatConversationVO } from '@/api/ai/chat/conversation' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import {formatDate} from "@/utils/formatTime"; |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | const { copy } = useClipboard() // 初始化 copy 到粘贴板 |
| | |
| | | scrollContainer.scrollTop = 0 |
| | | } |
| | | |
| | | defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用 |
| | | defineExpose({ scrollToBottom, handlerGoTop, handleGoBottom }) // 提供方法给 parent 调用 |
| | | |
| | | // ============ 处理消息操作 ============== |
| | | |
| | | /** 复制 */ |
| | | const copyContent = async (content) => { |
| | | await copy(content) |
| | | message.success('复制成功!') |
| | | } |
| | | |
| | | /** 删除 */ |
| | | const onDelete = async (id) => { |
| | | // 删除 message |
| | | await ChatMessageApi.deleteChatMessage(id) |
| | | message.success('删除成功!') |
| | | // 回调 |
| | | emits('onDeleteSuccess') |
| | | } |
| | | |
| | | /** 刷新 */ |
| | | const onRefresh = async (message: ChatMessageVO) => { |
对比新文件 |
| | |
| | | <template> |
| | | <DialogSuggest title="" v-model="dialogVisible" width="1300"> |
| | | <!-- 搜索工作栏 --> |
| | | <el-form |
| | | class="-mb-15px query-area" |
| | | :model="queryParams" |
| | | ref="queryFormRef" |
| | | :inline="true" |
| | | label-width="68px" |
| | | > |
| | | <el-form-item label="采纳状态" prop="status"> |
| | | <el-select |
| | | v-model="queryParams.status" |
| | | placeholder="请选择" |
| | | size="large" |
| | | class="!w-200px" |
| | | clearable |
| | | @change="getList" |
| | | > |
| | | <el-option |
| | | v-for="item in suggestStatus" |
| | | :key="item.key" |
| | | :label="item.name" |
| | | :value="item.key" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="调度时间" prop="createTime"> |
| | | <el-date-picker |
| | | v-model="queryParams.createTime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | type="datetimerange" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
| | | class="!w-360px transparent-date-picker-popper" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button @click="handleQuery"> |
| | | <Icon icon="ep:search" class="mr-5px"/> |
| | | 搜索 |
| | | </el-button> |
| | | <el-button @click="resetQuery"> |
| | | <Icon icon="ep:refresh" class="mr-5px"/> |
| | | 重置 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <!-- 对话详情 --> |
| | | <el-container class="detail-container"> |
| | | <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> |
| | | <el-table-column |
| | | label="调度时间" |
| | | align="center" |
| | | prop="createTime" |
| | | :formatter="dateFormatter" |
| | | width="180px" |
| | | /> |
| | | <el-table-column label="调度建议" align="center" prop="content" width="750"/> |
| | | <el-table-column label="采纳状态" align="center" prop="status" width="90"> |
| | | <template #default="scope"> |
| | | <template v-if="scope.row.status === 0"> |
| | | 未处理 |
| | | </template> |
| | | <template v-else-if="scope.row.status === 1"> |
| | | <span style="color: var(--el-color-success)">已采纳</span> |
| | | </template> |
| | | <template v-else> |
| | | <span style="color: var(--el-color-danger)">已忽略</span> |
| | | </template> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" align="center"> |
| | | <template #default="scope"> |
| | | <template v-if="scope.row.status === 0"> |
| | | <el-button |
| | | link |
| | | type="success" |
| | | @click="operateSuggest(scope.row.id, 1)" |
| | | > |
| | | 采纳建议 |
| | | </el-button> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | @click="operateSuggest(scope.row.id, 2)" |
| | | > |
| | | 忽略建议 |
| | | </el-button> |
| | | </template> |
| | | <template v-else-if="scope.row.status === 1"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | @click="operateSuggest(scope.row.id, 2)" |
| | | > |
| | | 取消采纳 |
| | | </el-button> |
| | | </template> |
| | | <template v-else> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | > |
| | | 已忽略 |
| | | </el-button> |
| | | </template> |
| | | <el-button |
| | | link |
| | | type="danger" |
| | | @click="handleDelete(scope.row.id)" |
| | | v-hasPermi="['ai:schedule-suggest:delete']" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-container> |
| | | <!-- 分页 --> |
| | | <Pagination |
| | | :total="total" |
| | | v-model:page="queryParams.pageNo" |
| | | v-model:limit="queryParams.pageSize" |
| | | @pagination="handleQuery" |
| | | /> |
| | | </DialogSuggest> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import {ChatMessageVO} from '@/api/ai/chat/message' |
| | | import {ref} from "vue"; |
| | | import {dateFormatter} from "@/utils/formatTime"; |
| | | import {ScheduleSuggestApi, ScheduleSuggestVO} from "@/api/ai/schedulesuggest"; |
| | | import {OtherPlatformEnum} from "@/views/ai/utils/constants"; |
| | | |
| | | /** AI 聊天对话 列表 */ |
| | | defineOptions({ name: 'HistoryMessageDialog' }) |
| | | |
| | | // 接收父组件传递的方法 |
| | | // const props = defineProps({ |
| | | // parentMethod: Function, |
| | | // gotoManualMethod: Function |
| | | // }); |
| | | |
| | | // 定义发射事件 |
| | | // const emit = defineEmits(['gotoManualMethod']) |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | const { t } = useI18n() // 国际化 |
| | | |
| | | const dialogVisible = ref(false) // 弹窗的是否展示 |
| | | const loading = ref(true) // 列表的加载中 |
| | | const list = ref<ScheduleSuggestVO[]>([]) // 列表的数据 |
| | | const total = ref(0) // 列表的总页数 |
| | | const queryParams = reactive({ |
| | | pageNo: 1, |
| | | pageSize: 10, |
| | | modelId: undefined, |
| | | conversationId: undefined, |
| | | messageId: undefined, |
| | | content: undefined, |
| | | status: undefined, |
| | | createTime: [], |
| | | }) |
| | | const operateData = ref({ |
| | | id: undefined, |
| | | status: undefined, |
| | | }) |
| | | const queryFormRef = ref() // 搜索的表单 |
| | | |
| | | const suggestStatus = ref([ |
| | | { |
| | | key: 0, |
| | | name: '未处理' |
| | | }, |
| | | { |
| | | key: 1, |
| | | name: '已采纳' |
| | | }, |
| | | { |
| | | key: 2, |
| | | name: '已忽略' |
| | | } |
| | | ]) |
| | | |
| | | /** 打开弹窗 */ |
| | | const open = async () => { |
| | | dialogVisible.value = true |
| | | await nextTick() // 等待弹窗DOM挂载 |
| | | await getList() |
| | | } |
| | | |
| | | defineExpose({ open }) // 提供方法给 parent 调用 |
| | | |
| | | /** 查询列表 */ |
| | | const getList = async () => { |
| | | loading.value = true |
| | | try { |
| | | const data = await ScheduleSuggestApi.getScheduleSuggestPage(queryParams) |
| | | list.value = data.list |
| | | total.value = data.total |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | /** 采纳与取消采纳建议 */ |
| | | const operateSuggest = async (id: number, status: number) => { |
| | | const data = operateData.value as unknown as ScheduleSuggestVO |
| | | data.id = id |
| | | data.status = status |
| | | await ScheduleSuggestApi.operateScheduleSuggest(data) |
| | | message.success(t('common.updateSuccess')) |
| | | // 刷新列表 |
| | | await getList() |
| | | } |
| | | |
| | | /** 删除按钮操作 */ |
| | | const handleDelete = async (id: number) => { |
| | | try { |
| | | // 删除的二次确认 |
| | | await message.delConfirm() |
| | | // 发起删除 |
| | | await ScheduleSuggestApi.deleteScheduleSuggest(id) |
| | | message.success(t('common.delSuccess')) |
| | | // 刷新列表 |
| | | await getList() |
| | | } catch {} |
| | | } |
| | | |
| | | /** 搜索按钮操作 */ |
| | | const handleQuery = () => { |
| | | getList() |
| | | } |
| | | |
| | | /** 重置按钮操作 */ |
| | | const resetQuery = () => { |
| | | queryFormRef.value.resetFields() |
| | | queryParams.status = undefined |
| | | handleQuery() |
| | | } |
| | | |
| | | // const gotoManual = async (item: ChatMessageVO) => { |
| | | // emit('gotoManualMethod', item) // 发送数据给父组件 |
| | | // } |
| | | |
| | | |
| | | /** 初始化 **/ |
| | | onMounted(async () => {}) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | |
| | | .query-area { |
| | | margin-top: 2px; |
| | | margin-bottom: 2px; |
| | | float: left; |
| | | :deep(.el-select__wrapper) { |
| | | background: rgba(255,255,255,0.1) !important; /* 保留浅色背景 */ |
| | | min-height: 30px; |
| | | box-shadow: none !important; |
| | | } |
| | | :deep(.el-select__placeholder) { |
| | | color: #DBEEFF; |
| | | } |
| | | :deep(.el-form-item__label) { |
| | | color: #73C4FF; |
| | | } |
| | | :deep(.el-date-editor .el-icon) { |
| | | color: #DBEEFF; |
| | | } |
| | | :deep(.el-date-editor .el-range-input) { |
| | | color: rgba(219, 238, 255, 0.5); |
| | | } |
| | | /* 移除所有输入框边框 */ |
| | | :deep(.el-form-item .el-input__wrapper) { |
| | | border: none !important; |
| | | box-shadow: none !important; |
| | | background: rgba(255,255,255,0.1) !important; /* 保留浅色背景 */ |
| | | } |
| | | /* 所有状态通用透明背景 */ |
| | | :deep(.el-button) { |
| | | background: transparent !important; |
| | | border-color: currentColor; /* 保持与文字同色 */ |
| | | color: #409EFF; /* 蓝色文字 */ |
| | | } |
| | | |
| | | /* 悬停状态 */ |
| | | :deep(.el-button:hover) { |
| | | background: rgba(0, 0, 0, 0.5) !important; /* 轻微悬停反馈 */ |
| | | } |
| | | |
| | | /* 点击状态 */ |
| | | :deep(.el-button:active) { |
| | | background: rgba(0, 0, 0, 0.8) !important; |
| | | } |
| | | } |
| | | |
| | | // 头部 |
| | | .detail-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 100%; |
| | | height: 42vh; |
| | | background-color: rgba(0, 0, 0, 0); /* 透明背景 */ |
| | | /* 表格透明背景 */ |
| | | :deep(.el-table) { |
| | | background-color: transparent !important; |
| | | border: 1px solid rgba(255, 255, 255, 0.3) !important; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | :deep(.el-table tr) { |
| | | color: #00b4ff; /* 蓝色文字 */ |
| | | background: transparent !important; |
| | | } |
| | | |
| | | /* 表头单元格边框 */ |
| | | :deep(.el-table th.el-table__cell) { |
| | | border-bottom: 1px solid rgba(115,196,255,0.14) !important; |
| | | border-right: 1px solid rgba(115,196,255,0.14) !important; |
| | | } |
| | | |
| | | /* 表格内容单元格边框 */ |
| | | :deep(.el-table td.el-table__cell) { |
| | | border-bottom: 1px solid rgba(115,196,255,0.14) !important; |
| | | border-right: 1px solid rgba(115,196,255,0.14) !important; |
| | | } |
| | | |
| | | /* 表头 */ |
| | | :deep(.el-table__header thead tr th) { |
| | | background-color: rgba(0, 194, 255, 0.2); |
| | | border-bottom: none; |
| | | } |
| | | |
| | | /* 行头样式 */ |
| | | :deep(.el-table .el-table__body td:first-child) { |
| | | color: #8FD6FE; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | :deep(.el-table .el-table__body tr:nth-child(even) td) { |
| | | background-color: rgba(0, 194, 255, 0.1); |
| | | } |
| | | |
| | | :deep(.el-table .el-table__body tr:nth-child(odd) td) { |
| | | background-color: rgba(0, 194, 255, 0.1); |
| | | } |
| | | |
| | | /* 移除表格内部边框线 */ |
| | | :deep(.el-table td, .el-table th.is-leaf) { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | :deep(.el-table .el-table__inner-wrapper:before) { |
| | | background-color: transparent !important; |
| | | } |
| | | |
| | | } |
| | | |
| | | // main 容器 |
| | | .main-container { |
| | | padding: 0; |
| | | position: relative; |
| | | overflow: hidden; /* 隐藏外层滚动条 */ |
| | | width: 100%; |
| | | |
| | | .message-container { |
| | | position: absolute; |
| | | top: 0; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | overflow-y: hidden; |
| | | /* Firefox */ |
| | | scrollbar-width: thin; |
| | | scrollbar-color: rgba(0, 0, 0, 0.15) transparent; |
| | | |
| | | /* WebKit */ |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | background: transparent; |
| | | } |
| | | &::-webkit-scrollbar-thumb { |
| | | border-radius: 4px; |
| | | background: rgba(0, 0, 0, 0.15); |
| | | transition: background 0.3s; |
| | | &:hover { background: rgba(0, 0, 0, 0.25); } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-pagination { |
| | | //--el-pagination-button-bg-color: transparent; |
| | | opacity: 0.6; |
| | | :deep(.el-pagination__total) { |
| | | color: white; |
| | | } |
| | | :deep(.el-pager) { |
| | | color: rgba(3,27,21); |
| | | font-weight: bold; |
| | | } |
| | | :deep(.el-pagination__jump) { |
| | | color: white; |
| | | } |
| | | :deep(.el-select__popper) { |
| | | background-color: transparent; |
| | | } |
| | | :deep(.el-scrollbar) { |
| | | --el-scrollbar-opacity: 0.8; |
| | | --el-scrollbar-bg-color: transparent; |
| | | } |
| | | } |
| | | |
| | | </style> |
对比新文件 |
| | |
| | | <template> |
| | | <div ref="messageContainer" class="h-100% overflow-y-auto relative"> |
| | | <div class="chat-list" v-for="(item, index) in list" :key="index"> |
| | | <!-- 靠左 message:system、assistant 类型 --> |
| | | <div class="left-message message-item" v-if="item.type !== 'user'"> |
| | | <div class="avatar"> |
| | | <el-avatar :src="roleAvatar" /> |
| | | </div> |
| | | <div class="message"> |
| | | <div> |
| | | <el-text class="time">{{ formatDate(item.createTime) }}</el-text> |
| | | </div> |
| | | <div v-if="item.thinkingFlag" class="left-text-container-thinking" ref="markdownViewRef"> |
| | | <MarkdownView v-if="item.thinking" class="left-text thinking" :content="item.thinking" /> |
| | | <MarkdownView v-else class="left-text thinking" :content="item.content" /> |
| | | </div> |
| | | <div v-else-if="item.thinking" class="left-text-container-thinking" ref="markdownViewRef"> |
| | | <MarkdownView class="left-text thinking" :content="item.thinking" /> |
| | | </div> |
| | | <div v-else class="left-text-container-conclusion" ref="markdownViewRef"> |
| | | <MarkdownView class="left-text" :content="item.content" /> |
| | | </div> |
| | | <div class="left-text-container-conclusion" ref="markdownViewRef"> |
| | | <MarkdownView class="left-text" :content="item.conclusion" /> |
| | | </div> |
| | | <div class="right-btns"> |
| | | <el-button class="btn-cus" link @click="copyContent(item.content)"> |
| | | <img class="btn-image" src="@/assets/ai/zhuanlu/copy.png" /> |
| | | </el-button> |
| | | <!-- 暂时不能删除,随意删除会影响首页echarts图表展示 --> |
| | | <!-- <el-button v-if="item.id > 0" class="btn-cus" link @click="onDelete(item.id)">--> |
| | | <!-- <img class="btn-image h-17px" src="@/assets/ai/zhuanlu/delete.png" />--> |
| | | <!-- </el-button>--> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 靠右 message:user 类型 --> |
| | | <div class="left-message message-item" v-if="item.type === 'user'"> |
| | | <div class="avatar"> |
| | | <el-avatar :src="userAvatar" /> |
| | | </div> |
| | | <div class="message"> |
| | | <div> |
| | | <el-text class="time">{{ formatDate(item.createTime) }}</el-text> |
| | | </div> |
| | | <div class="right-text-container question" @click="gotoManual(item)"> |
| | | <div class="right-text">{{ item.content }}</div> |
| | | </div> |
| | | <div class="right-btns"> |
| | | <el-button class="btn-cus" link @click="copyContent(item.content)"> |
| | | <img class="btn-image" src="@/assets/ai/zhuanlu/copy.png" /> |
| | | </el-button> |
| | | <el-button class="btn-cus" link @click="onDelete(item.id)"> |
| | | <img class="btn-image h-17px mr-12px" src="@/assets/ai/zhuanlu/delete.png" /> |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 回到底部 --> |
| | | <div v-if="isScrolling" class="to-bottom" @click="handleGoBottom"> |
| | | <el-button :icon="ArrowDownBold" circle /> |
| | | </div> |
| | | </template> |
| | | <script setup lang="ts"> |
| | | import { PropType } from 'vue' |
| | | import { formatDate } from '@/utils/formatTime' |
| | | import MarkdownView from '@/components/MarkdownView/index.vue' |
| | | import { useClipboard } from '@vueuse/core' |
| | | import { ArrowDownBold } from '@element-plus/icons-vue' |
| | | import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message' |
| | | import { ChatConversationVO } from '@/api/ai/chat/conversation' |
| | | import userAvatarDefaultImg from '@/assets/ai/zhuanlu/user.png' |
| | | import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png' |
| | | |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | const { copy } = useClipboard() // 初始化 copy 到粘贴板 |
| | | |
| | | // 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方) |
| | | const messageContainer: any = ref(null) |
| | | const isScrolling = ref(false) //用于判断用户是否在滚动 |
| | | |
| | | const userAvatar = computed(() => userAvatarDefaultImg) |
| | | const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg) |
| | | |
| | | |
| | | // 定义 props |
| | | const props = defineProps({ |
| | | conversation: { |
| | | type: Object as PropType<ChatConversationVO>, |
| | | required: true |
| | | }, |
| | | list: { |
| | | type: Array as PropType<ChatMessageVO[]>, |
| | | required: true |
| | | }, |
| | | |
| | | gotoManualMethod: Function |
| | | }) |
| | | |
| | | const { list } = toRefs(props) // 消息列表 |
| | | |
| | | const emits = defineEmits(['onDeleteSuccess']) // 定义 emits |
| | | |
| | | // ============ 处理对话滚动 ============== |
| | | |
| | | /** 滚动到底部 */ |
| | | const scrollToBottom = async (isIgnore?: boolean) => { |
| | | // 注意要使用 nextTick 以免获取不到 dom |
| | | await nextTick() |
| | | if (isIgnore || !isScrolling.value) { |
| | | messageContainer.value.scrollTop = |
| | | messageContainer.value.scrollHeight - messageContainer.value.offsetHeight |
| | | } |
| | | } |
| | | |
| | | function handleScroll() { |
| | | const scrollContainer = messageContainer.value |
| | | const scrollTop = scrollContainer.scrollTop |
| | | const scrollHeight = scrollContainer.scrollHeight |
| | | const offsetHeight = scrollContainer.offsetHeight |
| | | if (scrollTop + offsetHeight < scrollHeight - 100) { |
| | | // 用户开始滚动并在最底部之上,取消保持在最底部的效果 |
| | | isScrolling.value = true |
| | | } else { |
| | | // 用户停止滚动并滚动到最底部,开启保持到最底部的效果 |
| | | isScrolling.value = false |
| | | } |
| | | } |
| | | |
| | | /** 回到底部 */ |
| | | const handleGoBottom = async () => { |
| | | const scrollContainer = messageContainer.value |
| | | scrollContainer.scrollTop = scrollContainer.scrollHeight |
| | | } |
| | | |
| | | /** 回到顶部 */ |
| | | const handlerGoTop = async () => { |
| | | const scrollContainer = messageContainer.value |
| | | scrollContainer.scrollTop = 0 |
| | | } |
| | | |
| | | defineExpose({ scrollToBottom, handlerGoTop, handleGoBottom }) // 提供方法给 parent 调用 |
| | | |
| | | // ============ 处理消息操作 ============== |
| | | |
| | | const gotoManual = async (item: ChatMessageVO) => { |
| | | if(props.gotoManualMethod) { |
| | | props.gotoManualMethod(item) |
| | | } |
| | | } |
| | | |
| | | /** 复制 */ |
| | | const copyContent = async (content) => { |
| | | await copy(content) |
| | | message.success('复制成功!') |
| | | } |
| | | |
| | | /** 删除 */ |
| | | const onDelete = async (id) => { |
| | | // 删除 message |
| | | await ChatMessageApi.deleteChatMessage(id) |
| | | message.success('删除成功!') |
| | | // 回调 |
| | | emits('onDeleteSuccess') |
| | | } |
| | | |
| | | /** 初始化 */ |
| | | onMounted(async () => { |
| | | messageContainer.value.addEventListener('scroll', handleScroll) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | /* 添加或修改以下样式 */ |
| | | div[ref="messageContainer"] { |
| | | height: 100%; /* 继承父容器高度 */ |
| | | overflow-y: auto; /* 启用垂直滚动 */ |
| | | max-height: 720px; /* 或根据实际需求调整 */ |
| | | padding-bottom: 20px; /* 避免底部内容被截断 */ |
| | | } |
| | | // 中间 |
| | | .chat-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: auto; |
| | | padding: 0 20px; |
| | | |
| | | .left-message { |
| | | display: flex; |
| | | flex-direction: row; |
| | | } |
| | | |
| | | .message { |
| | | display: flex; |
| | | flex-direction: column; |
| | | text-align: left; |
| | | margin: 0 15px; |
| | | |
| | | .question:hover { |
| | | cursor: pointer; |
| | | background: rgba(40, 139, 255, 0.3); |
| | | } |
| | | |
| | | .time { |
| | | text-align: left; |
| | | line-height: 30px; |
| | | } |
| | | |
| | | .left-text-container-thinking { |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow-wrap: break-word; |
| | | background: rgba(115,196,255,0.05); |
| | | border-radius: 4px 4px 4px 4px; |
| | | border-left: 1px solid #73C4FF; |
| | | padding: 10px 10px 5px 10px; |
| | | .left-text { |
| | | color: rgba(219,238,255,0.5); |
| | | font-size: 0.85rem; |
| | | } |
| | | } |
| | | |
| | | .left-text-container-conclusion { |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow-wrap: break-word; |
| | | background: rgba(115,196,255,0); |
| | | border-radius: 4px 4px 4px 4px; |
| | | padding: 0 10px 5px 0; |
| | | .left-text { |
| | | color: rgba(219,238,255,0.8); |
| | | font-size: 1rem; |
| | | } |
| | | } |
| | | |
| | | .right-text-container { |
| | | display: flex; |
| | | flex-direction: row-reverse; |
| | | |
| | | .right-text { |
| | | font-size: 0.95rem; |
| | | color: #DBEEFF; |
| | | display: inline; |
| | | background: rgba(40,139,255,0.1); |
| | | box-shadow: 0 0 0 0 rgba(40,139,255,0.3); |
| | | border-radius: 10px; |
| | | padding: 10px; |
| | | width: auto; |
| | | overflow-wrap: break-word; |
| | | white-space: pre-wrap; |
| | | } |
| | | } |
| | | |
| | | .left-btns { |
| | | display: flex; |
| | | flex-direction: row; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .right-btns { |
| | | display: flex; |
| | | flex-direction: row-reverse; |
| | | margin-top: 8px; |
| | | } |
| | | } |
| | | |
| | | // 复制、删除按钮 |
| | | .btn-cus { |
| | | display: flex; |
| | | background-color: transparent; |
| | | align-items: center; |
| | | |
| | | .btn-image { |
| | | height: 20px; |
| | | } |
| | | } |
| | | |
| | | .btn-cus:hover { |
| | | cursor: pointer; |
| | | background-color: #f6f6f6; |
| | | } |
| | | } |
| | | |
| | | // 回到底部 |
| | | .to-bottom { |
| | | position: absolute; |
| | | z-index: 1000; |
| | | bottom: 0; |
| | | right: 50%; |
| | | .el-button { |
| | | background: rgba(255,255,255,0.1); |
| | | border: solid 1px rgba(255,215,0,0.6); |
| | | color: rgba(255,215,0,0.5); |
| | | } |
| | | .el-button:hover { |
| | | cursor: pointer; |
| | | background-color: rgba(255,255,255,0.4); |
| | | border: solid 2px rgba(255,215,0); |
| | | color: rgba(255,215,0); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div id="tsxx"> |
| | | <div class="title"></div> |
| | | <div class="data1-item" v-for="(item, index) in tsxxList" :key="`dynamics-${index}`"> |
| | | <div class="content"> |
| | | <div class="value"> |
| | | <span>{{item.value}}</span> <span>{{item.unit}}</span> |
| | | <div class="content1"> |
| | | <div class="value" v-if="item.type == 1"> |
| | | <div class="item" v-for="(list, i) in item.lists" :key="`dynamics-${i}`"> |
| | | <span>{{list.no}}</span><span>{{list.value}}</span> |
| | | </div> |
| | | </div> |
| | | <div class="value" v-else> |
| | | <span v-if="item.value == '进行'" style="color: #49FFD3; font-size: 14px; font-weight: bold;">{{item.value}}</span> |
| | | <span v-else style="color: #FFAE81; font-size: 14px; font-weight: bold;">{{item.value}}</span> |
| | | </div> |
| | | <div class="name"> |
| | | {{item.name}} |
| | |
| | | <div class="gas-scheduling-right"> |
| | | <div id="ldghslyc"> |
| | | <div class="title"></div> |
| | | <div ref="LDGHSLYCEhartContainer" style="width: 100%; height: 180px"></div> |
| | | <div ref="LDGHSLYCEhartContainer" style="width: 100%; height: 140px"></div> |
| | | </div> |
| | | <div id="ldggrqsyc"> |
| | | <div class="title"></div> |
| | | <div ref="LDGGRYCEhartContainer" style="width: 100%; height: 180px"></div> |
| | | <div ref="LDGGRYCEhartContainer" style="width: 100%; height: 140px"></div> |
| | | </div> |
| | | <div id="mqhsjhxx"> |
| | | <div class="title"></div> |
| | |
| | | <div class="item right-label"></div> |
| | | </div> |
| | | </div> |
| | | <div class="schedule-suggest"> |
| | | <div class="result-title"> |
| | | <span>推理结论</span><el-button @click="openSuggest" size="small" class="result-button" :icon="ArrowRight">查看更多</el-button> |
| | | </div> |
| | | <div class="result-content"> |
| | | <div class="content-item" v-for="(item, index) in topSuggests" :key="`dynamics-${index}`"> |
| | | <div class="time"> |
| | | <span>{{formatDate(item.createTime, 'MM-DD HH:mm')}}</span> |
| | | </div> |
| | | <el-tooltip |
| | | effect="dark" |
| | | :content="item.content" |
| | | placement="top" |
| | | :disabled="!isOverflow" |
| | | > |
| | | <div class="content" ref="contentRef"> |
| | | {{ item.content }} |
| | | </div> |
| | | </el-tooltip> |
| | | </div> |
| | | </div> |
| | | <!-- 推理结论 --> |
| | | <ScheduleSuggestDialog |
| | | ref="scheduleSuggestRef" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | import MessageLoading from '../components/message/MessageLoading.vue' |
| | | import ConversationList from "../components/conversation/ConversationList.vue"; |
| | | import HistoryMessageDialog from "../components/message/HistoryMessageDialog.vue" |
| | | import ScheduleSuggestDialog from "../components/suggest/ScheduleSuggestDialog.vue" |
| | | import * as echarts from "echarts"; |
| | | import {formatToDateTime} from "@/utils/dateUtil"; |
| | | import { formatReasoningContent } from '@/views/ai/utils/utils' |
| | | import {refreshToken} from "@/api/login"; |
| | | import {round} from "lodash-es"; |
| | | import {ArrowUpBold} from "@element-plus/icons-vue"; |
| | | import {ArrowRight, ArrowUpBold} from "@element-plus/icons-vue"; |
| | | import * as authUtil from "@/utils/auth"; |
| | | import HistoryMessageList from "@/views/ai/dashboard/components/message/HistoryMessageList.vue"; |
| | | import {ScheduleSuggestApi, ScheduleSuggestVO} from "@/api/ai/schedulesuggest"; |
| | | import {formatDate} from "@/utils/formatTime"; |
| | | |
| | | const mqhsList = ref([ |
| | | { |
| | |
| | | }, |
| | | { |
| | | name: '转炉煤气 O 含量', |
| | | value: 618, |
| | | value: 10, |
| | | unit: '%' |
| | | }, |
| | | { |
| | |
| | | const tsxxList = ref([ |
| | | { |
| | | name: '各高炉出铁水信号', |
| | | value: '进行', |
| | | unit: '' |
| | | type: 1, |
| | | lists: [ |
| | | { |
| | | no: '1#', |
| | | value: '不进行', |
| | | }, |
| | | { |
| | | no: '2#', |
| | | value: '不进行', |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | name: '各高炉出铁量', |
| | | value: 5000, |
| | | unit: '吨' |
| | | type: 1, |
| | | lists: [ |
| | | { |
| | | no: '1#', |
| | | value: '500t', |
| | | }, |
| | | { |
| | | no: '2#', |
| | | value: '600t', |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | name: '各高炉铁水装入鱼雷罐车信号', |
| | | value: '进行', |
| | | unit: 'm³/h' |
| | | type: 1, |
| | | lists: [ |
| | | { |
| | | no: '1#', |
| | | value: '不进行', |
| | | }, |
| | | { |
| | | no: '2#', |
| | | value: '不进行', |
| | | } |
| | | ] |
| | | }, |
| | | { |
| | | name: '鱼雷罐车等待信号', |
| | | value: '进行', |
| | | unit: 'm³/h' |
| | | type: 2, |
| | | value: '进行' |
| | | }, |
| | | { |
| | | name: '铁水倒入铁水包信号', |
| | | value: '不进行', |
| | | unit: 'm³/h' |
| | | type: 2, |
| | | value: '不进行' |
| | | }, |
| | | { |
| | | name: '铁产量计划', |
| | | value: 6000, |
| | | unit: '吨' |
| | | type: 3, |
| | | value: '6000t', |
| | | }, |
| | | ]) |
| | | |
| | |
| | | |
| | | const mqhsjhxxList = ref([ |
| | | { |
| | | name: '转炉总炉数\n' + |
| | | '日计划', |
| | | value: 567, |
| | | name: '转炉总炉数日计划', |
| | | value: 123, |
| | | unit: '炉' |
| | | }, |
| | | { |
| | | name: '转炉入炉铁水量\n' + |
| | | '日计划', |
| | | value: 200, |
| | | unit: '吨' |
| | | }, |
| | | { |
| | | name: '转炉检修计划', |
| | | value: '未进行', |
| | | value: '0', |
| | | unit: '' |
| | | }, |
| | | { |
| | | name: '钢产量日计划', |
| | | value: 300, |
| | | unit: '吨' |
| | | }, |
| | | { |
| | | name: '转炉加入废钢总量', |
| | | value: 500, |
| | | unit: '吨' |
| | | value: 20000, |
| | | unit: 't' |
| | | }, |
| | | { |
| | | name: '转炉实绩钢产量', |
| | | value: 100, |
| | | unit: '吨' |
| | | value: 20929, |
| | | unit: 't' |
| | | } |
| | | ]) |
| | | |
| | |
| | | { |
| | | id: 1, |
| | | name: '1#转炉', |
| | | current: 20, |
| | | total: 30 |
| | | current: 4, |
| | | total: 29 |
| | | }, |
| | | { |
| | | id: 2, |
| | | name: '2#转炉', |
| | | current: 25, |
| | | total: 100 |
| | | current: 5, |
| | | total: 42 |
| | | }, |
| | | { |
| | | id: 3, |
| | | name: '3#转炉', |
| | | current: 4, |
| | | total: 29 |
| | | current: 6, |
| | | total: 42 |
| | | } |
| | | ]) |
| | | |
| | |
| | | } |
| | | |
| | | ]) |
| | | |
| | | const topSuggests = ref<ScheduleSuggestVO[]>([]) |
| | | |
| | | const contentRef = ref([]); |
| | | const isOverflow = ref([]); |
| | | |
| | | const ddtlResult = ref('') |
| | | |
| | |
| | | message.thinking = match[2]; |
| | | message.conclusion = match[4] |
| | | } |
| | | message.thinking = formatReasoningContent(message.thinking) |
| | | return message |
| | | } |
| | | |
| | | |
| | | /** 调度建议 */ |
| | | const scheduleSuggestRef = ref() |
| | | const openSuggest = async () => { |
| | | scheduleSuggestRef.value.open() |
| | | } |
| | | /** |
| | | * 消息列表 |
| | | * |
| | |
| | | */ |
| | | const messageList = computed(() => { |
| | | if (activeMessageList.value.length > 0) { |
| | | activeMessageList.value[1].thinking = dealResultAndData(activeMessageList.value[1].content) |
| | | return activeMessageList.value |
| | | // 对AI返回的消息进行格式化处理 |
| | | const formattedList = activeMessageList.value.map(msg => { |
| | | if (msg.type === 'assistant') { |
| | | // 复制消息对象以避免修改原始数据 |
| | | const formattedMsg = {...msg}; |
| | | // 处理推理思路内容 |
| | | formattedMsg.content = formatReasoningContent(msg.content); |
| | | return formattedMsg; |
| | | } |
| | | return msg; |
| | | }); |
| | | |
| | | // 处理调度推理结论及数据 |
| | | formattedList[1].thinking = dealResultAndData(formattedList[1].content); |
| | | |
| | | return formattedList; |
| | | } |
| | | // 没有消息时,如果有 systemMessage 则展示它 |
| | | if (activeConversation.value?.systemMessage) { |
| | |
| | | } |
| | | initLDGGRQSYCChart() |
| | | return content |
| | | } |
| | | |
| | | const getScheduleResult = (content: string) => { |
| | | const spliceText = content.includes("总结:") ? "总结:" : "结论:"; |
| | | const regex = new RegExp(`^([\\s\\S]*?)${spliceText}([\\s\\S]*)$`); |
| | | const match = content.match(regex); |
| | | const result = match ? match[2].trim() : ''; |
| | | return result |
| | | } |
| | | |
| | | const extractRecoveryDetails = (text, consume, gui, totalMinutes = 60) => { |
| | |
| | | conversationId: activeConversationId.value, |
| | | content: content |
| | | } as ChatMessageVO) |
| | | // 保存调度建议 |
| | | setTimeout(async () => { |
| | | await createSuggest() |
| | | }, 1000) |
| | | } |
| | | |
| | | /** 真正执行【发送】消息操作 */ |
| | |
| | | } catch {} |
| | | } |
| | | |
| | | const suggestData = ref({ |
| | | id: undefined, |
| | | modelId: undefined, |
| | | conversationId: undefined, |
| | | messageId: undefined, |
| | | content: undefined, |
| | | status: undefined, |
| | | }) |
| | | |
| | | const createSuggest = async () => { |
| | | const suggestParam = suggestData.value as unknown as ScheduleSuggestVO |
| | | let assistantMessage = activeMessageList.value[1] |
| | | suggestParam.content = getScheduleResult(assistantMessage.content) |
| | | if(suggestParam.content != '') { |
| | | suggestParam.modelId = activeConversation.value.modelId |
| | | suggestParam.conversationId = activeConversation.value.id |
| | | suggestParam.messageId = assistantMessage.id |
| | | suggestParam.createTime = assistantMessage.createTime |
| | | suggestParam.status = 0 |
| | | await ScheduleSuggestApi.createScheduleSuggest(suggestParam) |
| | | // 刷新首页推理结果列表 |
| | | await getTopSuggest() |
| | | } |
| | | } |
| | | |
| | | const LDGHSLYCEhartContainer = ref(); |
| | | |
| | | // 生成未来60秒的时间标签(LDG回收量预测) |
| | |
| | | const max = LDGMaxTotalValue() |
| | | const schedule = modelData.value.schedule[type] |
| | | // 返回对象格式数据,包含原始值和基准值 |
| | | const baseline = round(max, 0) + (6 - 2 * type) |
| | | const baseline = round(max, 0) + (4.5 - 2 * type) |
| | | return schedule.map(item => ({ |
| | | value: item + baseline, // 显示值 = 原始值 + 基准值 |
| | | original: item // 原始值 |
| | |
| | | }, |
| | | grid: { |
| | | left: 25, |
| | | right: 25, |
| | | right: 5, |
| | | bottom: 10, |
| | | top: 30, |
| | | containLabel: true |
| | | }, |
| | | legend: { |
| | | top: 10, |
| | | top: 5, |
| | | right: 10, |
| | | data: ['1#转炉', '2#转炉', '3#转炉', '总回收量'], |
| | | data: ['1#转炉', '2#转炉', '3#转炉'], |
| | | textStyle: { |
| | | color: '#8FD6FE' |
| | | }, |
| | |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 1, |
| | | color: '#FF7686' // 粉色 |
| | | } |
| | | }, |
| | |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 1, |
| | | color: '#49FFD3' // 绿色 |
| | | } |
| | | }, |
| | |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 1, |
| | | color: '#FFAE81' // 橙色 |
| | | }, |
| | | }, |
| | |
| | | focus: 'series' |
| | | }, |
| | | lineStyle: { |
| | | width: 1, |
| | | color: 'white' |
| | | }, |
| | | } |
| | |
| | | left: 0, |
| | | right: 0, |
| | | bottom: 10, |
| | | top: 20, |
| | | top: 10, |
| | | containLabel: true |
| | | }, |
| | | tooltip: { |
| | |
| | | data: realData, |
| | | smooth: true, |
| | | symbol: 'none', |
| | | lineStyle: { color: '#95E6FF' }, |
| | | lineStyle: { |
| | | color: '#95E6FF', |
| | | width: 1 |
| | | }, |
| | | markLine: { |
| | | symbol: ['none', 'none'], |
| | | label: { |
| | |
| | | lineStyle: { |
| | | type: 'dashed', |
| | | color: '#E76666', |
| | | width: 1, |
| | | dashOffset: 5 |
| | | } |
| | | }, |
| | |
| | | // 初始状态检测 |
| | | updateFullscreenStatus(); |
| | | } |
| | | /** 查询列表 */ |
| | | const getTopSuggest = async () => { |
| | | const data = await ScheduleSuggestApi.getTopScheduleSuggests(5) |
| | | topSuggests.value = data |
| | | } |
| | | |
| | | /** 初始化 **/ |
| | | onMounted(async () => { |
| | |
| | | // 获取列表数据 |
| | | activeMessageListLoading.value = true |
| | | await getMessageList() |
| | | await getTopSuggest() |
| | | }) |
| | | |
| | | // 清理监听 |
| | | onUnmounted(() => { |
| | | console.log('stopStream') |
| | | const events = ['fullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange']; |
| | | events.forEach(event => { |
| | | document.removeEventListener(event, handleFullscreenChange); |
| | |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #C7E7FF; |
| | | } |
| | | } |
| | | .name { |
| | | height: 16px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #C7E7FF; |
| | | } |
| | | } |
| | | .content1 { |
| | | margin-left: 16px; |
| | | .value { |
| | | span:nth-child(1) { |
| | | height: 16px; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #C7E7FF; |
| | | } |
| | | span:nth-child(2){ |
| | | padding-left: 3px; |
| | | height: 19px; |
| | | font-weight: bold; |
| | | font-size: 14px; |
| | | color: #FFAE81; |
| | | line-height: 19px; |
| | | } |
| | | .item { |
| | | display: inline-block; |
| | | width: 45%; |
| | | } |
| | | } |
| | | .name { |
| | |
| | | } |
| | | } |
| | | .data2-item { |
| | | height: 2.8rem; |
| | | width: 45%; |
| | | height: 1.4rem; |
| | | width: 46%; |
| | | display: inline-block; |
| | | margin: 6px 8px; |
| | | margin: 8px 8px; |
| | | background: url("@/assets/ai/zhuanlu/data_bg3.png") no-repeat; |
| | | } |
| | | .content { |
| | |
| | | margin-left: 10px; |
| | | |
| | | .name { |
| | | width: 95px; |
| | | height: 18px; |
| | | width: 130px; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #C7E7FF; |
| | | } |
| | | |
| | | .value { |
| | | margin-top: 10px; |
| | | margin-left: auto; |
| | | margin-right: 5px; |
| | | span:nth-child(1) { |
| | |
| | | .little-title { |
| | | font-size: 14px; |
| | | color: #8FD6FE; |
| | | margin: 10px; |
| | | margin: 5px 10px 5px 10px; |
| | | } |
| | | .data3-item { |
| | | height: 5.2rem; |
| | | width: 30%; |
| | | display: inline-block; |
| | | margin: 0 6px 6px 6px; |
| | | margin: 0 6px 0 6px; |
| | | background: url("@/assets/ai/zhuanlu/data_bg4.png") center/cover no-repeat; |
| | | .name { |
| | | font-family: Alimama ShuHeiTi, Alimama ShuHeiTi; |
| | |
| | | } |
| | | } |
| | | } |
| | | .schedule-suggest { |
| | | .result-title { |
| | | margin-top: 10px; |
| | | background: url("@/assets/ai/zhuanlu/ddtljl_result_title.png") no-repeat; |
| | | height: 1.8rem; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #8FD6FE; |
| | | text-align: left; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | span { |
| | | margin-left: 30px; |
| | | } |
| | | .result-button { |
| | | color: rgba(143, 214, 254); |
| | | font-weight: bold; |
| | | float: right; |
| | | margin-right: 5px; |
| | | background-color: rgba(0, 255, 255, 0.1); |
| | | border-radius: 3px; |
| | | padding: 0 5px; |
| | | border: none; |
| | | cursor: pointer |
| | | } |
| | | .history-button:hover { |
| | | color: rgba(143, 214, 254, 0.5); |
| | | } |
| | | } |
| | | .result-content { |
| | | margin-top: 5px; |
| | | display: inline-block; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: rgba(130,202,255,0.89); |
| | | text-align: left; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | .content-item { |
| | | height: 28px; |
| | | background: rgba(69,133,255,0.2); |
| | | border-radius: 2px 2px 2px 2px; |
| | | padding-left: 3px; |
| | | margin: 3px 0; |
| | | display: flex; |
| | | align-items: center; |
| | | overflow: hidden; |
| | | } |
| | | .time { |
| | | flex-shrink: 0; |
| | | margin-right: 12px; |
| | | } |
| | | .content { |
| | | width: 350px; |
| | | flex: 1; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | import * as AiQuestionTemplateApi from '@/api/ai/questiontemplate' |
| | | import TemplateForm from './templateForm.vue' |
| | | import * as AiModelApi from "@/api/ai/model/model"; |
| | | import {AiModelTypeEnum} from "@/views/ai/utils/constants"; |
| | | |
| | | defineOptions({name: 'AiTemplate'}) |
| | | |
| | |
| | | /** 初始化 **/ |
| | | onMounted(async () => { |
| | | |
| | | aiModelList.value = await AiModelApi.ModelApi.getModelSimpleList(1) |
| | | aiModelList.value = await AiModelApi.ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT + "," + AiModelTypeEnum.LLM) |
| | | await getList() |
| | | }) |
| | | </script> |
| | |
| | | import {CommonStatusEnum} from '@/utils/constants' |
| | | import {ElMessage} from 'element-plus' |
| | | import * as AiModelApi from "@/api/ai/model/model"; |
| | | import {AiModelTypeEnum} from "@/views/ai/utils/constants"; |
| | | |
| | | const aiModelList = ref([] as AiModelApi.ModelVO[]) |
| | | defineOptions({name: 'AiTemplateForm'}) |
| | |
| | | formType.value = type |
| | | resetForm() |
| | | // 加载调度模型列表 |
| | | aiModelList.value = await AiModelApi.ModelApi.getModelSimpleList(1) |
| | | aiModelList.value = await AiModelApi.ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT + "," + AiModelTypeEnum.LLM) |
| | | if (id) { |
| | | formLoading.value = true |
| | | try { |
对比新文件 |
| | |
| | | <template> |
| | | <Dialog :title="dialogTitle" v-model="dialogVisible"> |
| | | <el-form |
| | | ref="formRef" |
| | | :model="formData" |
| | | :rules="formRules" |
| | | label-width="100px" |
| | | v-loading="formLoading" |
| | | > |
| | | <el-form-item label="模型id" prop="modelId"> |
| | | <el-input v-model="formData.modelId" placeholder="请输入模型id" /> |
| | | </el-form-item> |
| | | <el-form-item label="会话id" prop="conversationId"> |
| | | <el-input v-model="formData.conversationId" placeholder="请输入会话id" /> |
| | | </el-form-item> |
| | | <el-form-item label="消息id" prop="messageId"> |
| | | <el-input v-model="formData.messageId" placeholder="请输入消息id" /> |
| | | </el-form-item> |
| | | <el-form-item label="调度建议" prop="content"> |
| | | <Editor v-model="formData.content" height="150px" /> |
| | | </el-form-item> |
| | | <el-form-item label="状态(0-未处理 1-已采纳 2-已忽略)" prop="status"> |
| | | <el-radio-group v-model="formData.status"> |
| | | <el-radio label="1">请选择字典生成</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
| | | <el-button @click="dialogVisible = false">取 消</el-button> |
| | | </template> |
| | | </Dialog> |
| | | </template> |
| | | <script setup lang="ts"> |
| | | import { ScheduleSuggestApi, ScheduleSuggestVO } from '@/api/ai/schedulesuggest' |
| | | |
| | | /** 大模型调度建议 表单 */ |
| | | defineOptions({ name: 'ScheduleSuggestForm' }) |
| | | |
| | | const { t } = useI18n() // 国际化 |
| | | const message = useMessage() // 消息弹窗 |
| | | |
| | | const dialogVisible = ref(false) // 弹窗的是否展示 |
| | | const dialogTitle = ref('') // 弹窗的标题 |
| | | const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
| | | const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
| | | const formData = ref({ |
| | | id: undefined, |
| | | modelId: undefined, |
| | | conversationId: undefined, |
| | | messageId: undefined, |
| | | content: undefined, |
| | | status: undefined, |
| | | }) |
| | | const formRules = reactive({ |
| | | modelId: [{ required: true, message: '模型id不能为空', trigger: 'blur' }], |
| | | conversationId: [{ required: true, message: '会话id不能为空', trigger: 'blur' }], |
| | | messageId: [{ required: true, message: '消息id不能为空', trigger: 'blur' }], |
| | | status: [{ required: true, message: '状态(0-未处理 1-已采纳 2-已忽略)不能为空', trigger: 'blur' }], |
| | | }) |
| | | const formRef = ref() // 表单 Ref |
| | | |
| | | /** 打开弹窗 */ |
| | | const open = async (type: string, id?: number) => { |
| | | dialogVisible.value = true |
| | | dialogTitle.value = t('action.' + type) |
| | | formType.value = type |
| | | resetForm() |
| | | // 修改时,设置数据 |
| | | if (id) { |
| | | formLoading.value = true |
| | | try { |
| | | formData.value = await ScheduleSuggestApi.getScheduleSuggest(id) |
| | | } finally { |
| | | formLoading.value = false |
| | | } |
| | | } |
| | | } |
| | | defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
| | | |
| | | /** 提交表单 */ |
| | | const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
| | | const submitForm = async () => { |
| | | // 校验表单 |
| | | await formRef.value.validate() |
| | | // 提交请求 |
| | | formLoading.value = true |
| | | try { |
| | | const data = formData.value as unknown as ScheduleSuggestVO |
| | | if (formType.value === 'create') { |
| | | await ScheduleSuggestApi.createScheduleSuggest(data) |
| | | message.success(t('common.createSuccess')) |
| | | } else { |
| | | await ScheduleSuggestApi.updateScheduleSuggest(data) |
| | | message.success(t('common.updateSuccess')) |
| | | } |
| | | dialogVisible.value = false |
| | | // 发送操作成功的事件 |
| | | emit('success') |
| | | } finally { |
| | | formLoading.value = false |
| | | } |
| | | } |
| | | |
| | | /** 重置表单 */ |
| | | const resetForm = () => { |
| | | formData.value = { |
| | | id: undefined, |
| | | modelId: undefined, |
| | | conversationId: undefined, |
| | | messageId: undefined, |
| | | content: undefined, |
| | | status: undefined, |
| | | } |
| | | formRef.value?.resetFields() |
| | | } |
| | | </script> |
对比新文件 |
| | |
| | | <template> |
| | | <ContentWrap> |
| | | <!-- 搜索工作栏 --> |
| | | <el-form |
| | | class="-mb-15px" |
| | | :model="queryParams" |
| | | ref="queryFormRef" |
| | | :inline="true" |
| | | label-width="68px" |
| | | > |
| | | <el-form-item label="模型id" prop="modelId"> |
| | | <el-input |
| | | v-model="queryParams.modelId" |
| | | placeholder="请输入模型id" |
| | | clearable |
| | | @keyup.enter="handleQuery" |
| | | class="!w-240px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="会话id" prop="conversationId"> |
| | | <el-input |
| | | v-model="queryParams.conversationId" |
| | | placeholder="请输入会话id" |
| | | clearable |
| | | @keyup.enter="handleQuery" |
| | | class="!w-240px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="消息id" prop="messageId"> |
| | | <el-input |
| | | v-model="queryParams.messageId" |
| | | placeholder="请输入消息id" |
| | | clearable |
| | | @keyup.enter="handleQuery" |
| | | class="!w-240px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="状态(0-未处理 1-已采纳 2-已忽略)" prop="status"> |
| | | <el-select |
| | | v-model="queryParams.status" |
| | | placeholder="请选择状态(0-未处理 1-已采纳 2-已忽略)" |
| | | clearable |
| | | class="!w-240px" |
| | | > |
| | | <el-option label="请选择字典生成" value="" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="创建时间" prop="createTime"> |
| | | <el-date-picker |
| | | v-model="queryParams.createTime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | type="daterange" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
| | | class="!w-240px" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
| | | <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
| | | <el-button |
| | | type="primary" |
| | | plain |
| | | @click="openForm('create')" |
| | | v-hasPermi="['ai:schedule-suggest:create']" |
| | | > |
| | | <Icon icon="ep:plus" class="mr-5px" /> 新增 |
| | | </el-button> |
| | | <el-button |
| | | type="success" |
| | | plain |
| | | @click="handleExport" |
| | | :loading="exportLoading" |
| | | v-hasPermi="['ai:schedule-suggest:export']" |
| | | > |
| | | <Icon icon="ep:download" class="mr-5px" /> 导出 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </ContentWrap> |
| | | |
| | | <!-- 列表 --> |
| | | <ContentWrap> |
| | | <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> |
| | | <el-table-column label="id" align="center" prop="id" /> |
| | | <el-table-column label="模型id" align="center" prop="modelId" /> |
| | | <el-table-column label="会话id" align="center" prop="conversationId" /> |
| | | <el-table-column label="消息id" align="center" prop="messageId" /> |
| | | <el-table-column label="调度建议" align="center" prop="content" /> |
| | | <el-table-column label="状态(0-未处理 1-已采纳 2-已忽略)" align="center" prop="status" /> |
| | | <el-table-column |
| | | label="创建时间" |
| | | align="center" |
| | | prop="createTime" |
| | | :formatter="dateFormatter" |
| | | width="180px" |
| | | /> |
| | | <el-table-column label="操作" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | @click="openForm('update', scope.row.id)" |
| | | v-hasPermi="['ai:schedule-suggest:update']" |
| | | > |
| | | 编辑 |
| | | </el-button> |
| | | <el-button |
| | | link |
| | | type="danger" |
| | | @click="handleDelete(scope.row.id)" |
| | | v-hasPermi="['ai:schedule-suggest:delete']" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <!-- 分页 --> |
| | | <Pagination |
| | | :total="total" |
| | | v-model:page="queryParams.pageNo" |
| | | v-model:limit="queryParams.pageSize" |
| | | @pagination="getList" |
| | | /> |
| | | </ContentWrap> |
| | | |
| | | <!-- 表单弹窗:添加/修改 --> |
| | | <ScheduleSuggestForm ref="formRef" @success="getList" /> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { dateFormatter } from '@/utils/formatTime' |
| | | import download from '@/utils/download' |
| | | import { ScheduleSuggestApi, ScheduleSuggestVO } from '@/api/ai/schedulesuggest' |
| | | import ScheduleSuggestForm from './ScheduleSuggestForm.vue' |
| | | |
| | | /** 大模型调度建议 列表 */ |
| | | defineOptions({ name: 'ScheduleSuggest' }) |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | const { t } = useI18n() // 国际化 |
| | | |
| | | const loading = ref(true) // 列表的加载中 |
| | | const list = ref<ScheduleSuggestVO[]>([]) // 列表的数据 |
| | | const total = ref(0) // 列表的总页数 |
| | | const queryParams = reactive({ |
| | | pageNo: 1, |
| | | pageSize: 10, |
| | | modelId: undefined, |
| | | conversationId: undefined, |
| | | messageId: undefined, |
| | | content: undefined, |
| | | status: undefined, |
| | | createTime: [], |
| | | }) |
| | | const queryFormRef = ref() // 搜索的表单 |
| | | const exportLoading = ref(false) // 导出的加载中 |
| | | |
| | | /** 查询列表 */ |
| | | const getList = async () => { |
| | | loading.value = true |
| | | try { |
| | | const data = await ScheduleSuggestApi.getScheduleSuggestPage(queryParams) |
| | | list.value = data.list |
| | | total.value = data.total |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | /** 搜索按钮操作 */ |
| | | const handleQuery = () => { |
| | | queryParams.pageNo = 1 |
| | | getList() |
| | | } |
| | | |
| | | /** 重置按钮操作 */ |
| | | const resetQuery = () => { |
| | | queryFormRef.value.resetFields() |
| | | handleQuery() |
| | | } |
| | | |
| | | /** 添加/修改操作 */ |
| | | const formRef = ref() |
| | | const openForm = (type: string, id?: number) => { |
| | | formRef.value.open(type, id) |
| | | } |
| | | |
| | | /** 删除按钮操作 */ |
| | | const handleDelete = async (id: number) => { |
| | | try { |
| | | // 删除的二次确认 |
| | | await message.delConfirm() |
| | | // 发起删除 |
| | | await ScheduleSuggestApi.deleteScheduleSuggest(id) |
| | | message.success(t('common.delSuccess')) |
| | | // 刷新列表 |
| | | await getList() |
| | | } catch {} |
| | | } |
| | | |
| | | /** 导出按钮操作 */ |
| | | const handleExport = async () => { |
| | | try { |
| | | // 导出的二次确认 |
| | | await message.exportConfirm() |
| | | // 发起导出 |
| | | exportLoading.value = true |
| | | const data = await ScheduleSuggestApi.exportScheduleSuggest(queryParams) |
| | | download.excel(data, '大模型调度建议.xls') |
| | | } catch { |
| | | } finally { |
| | | exportLoading.value = false |
| | | } |
| | | } |
| | | |
| | | /** 初始化 **/ |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | |
| | | } |
| | | |
| | | export const AiModelTypeEnum = { |
| | | CHAT: 1, // 聊天 |
| | | IMAGE: 2, // 图像 |
| | | VOICE: 3, // 音频 |
| | | VIDEO: 4, // 视频 |
| | | EMBEDDING: 5, // 向量 |
| | | RERANK: 6 // 重排 |
| | | CHAT: '1', // 聊天 |
| | | IMAGE: '2', // 图像 |
| | | VOICE: '3', // 音频 |
| | | VIDEO: '4', // 视频 |
| | | EMBEDDING: '5', // 向量 |
| | | LLM: '6' // 重排 |
| | | } |
| | | |
| | | export const OtherPlatformEnum: ImageModelVO[] = [ |
| | |
| | | export const hasChinese = (str: string) => { |
| | | return /[\u4e00-\u9fa5]/.test(str) |
| | | } |
| | | |
| | | export const formatReasoningContent = (content: string) => { |
| | | // 匹配 "数字" + "." + ("中文"或"空格") + "其他内容" + ":" |
| | | const stepRegex = /(\d+\.(?:[\u4e00-\u9fa5]|\s)[^:]*:)(\s*)/g; |
| | | |
| | | // 替换逻辑: |
| | | // - 如果标题后没有换行(即 `$2` 是空或只有空格),则添加 `<br>` |
| | | // - 如果标题后已有换行(如 `\n` 或 `<br>`),则不额外添加 |
| | | return content.replace( |
| | | stepRegex, |
| | | (match, title, whitespace) => { |
| | | const hasNewline = whitespace.includes('\\n') || whitespace.includes('<br>'); |
| | | const lineBreak = hasNewline ? '' : '<br>'; |
| | | return `<strong style="font-size: 16px; line-height: 32px; color: #FFFFFF;">${title}</strong>${lineBreak}`; |
| | | } |
| | | ); |
| | | } |
| | |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="模型编号" prop="modelCode"> |
| | | <el-input v-model="formData.modelCode" placeholder="请输入模型编号"/> |
| | | <el-input v-model="formData.modelCode" placeholder="请输入模型编号" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-row> |
| | | <el-divider content-position="left">模型信息</el-divider> |
| | | <div style="width: 120px;text-align: right;margin-bottom: 8px"> |
| | | <el-popover placement="right" :width="300" trigger="click" ref="modelPopover" |
| | | @before-enter="model = undefined"> |
| | | <el-popover placement="right" :width="300" trigger="click" ref="modelPopover" @before-enter="model = undefined"> |
| | | <template #reference> |
| | | <span style="color: #409eff;cursor: pointer">关联模型信息</span> |
| | | </template> |
| | | <template #default> |
| | | <div style="display:flex;flex-direction: row;align-items: center;"> |
| | | <el-cascader style="width: 100%" v-model="model" placeholder="选择模型" |
| | | :teleported="false" @change="changeModel" :options="scheduleModelList" v-if="formData.invocation != '4'"/> |
| | | <el-cascader style="width: 100%" v-model="model" placeholder="选择模型" |
| | | :teleported="false" @change="changeAiModel" :options="aiModelList" v-if="formData.invocation === '4'"/> |
| | | <el-cascader style="width: 100%" v-model="model" placeholder="选择模型" :teleported="false" @change="changeModel" :options="scheduleModelList"/> |
| | | </div> |
| | | </template> |
| | | </el-popover> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="类名" prop="className"> |
| | | <el-input v-model="formData.className" placeholder="请输入类名" :disabled="true"/> |
| | | <el-input v-model="formData.className" placeholder="请输入类名" :disabled="true" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="方法名" prop="methodName"> |
| | | <el-input v-model="formData.methodName" placeholder="请输入方法名" :disabled="true"/> |
| | | <el-input v-model="formData.methodName" placeholder="请输入方法名" :disabled="true" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="参数数量" prop="portLength"> |
| | | <el-input-number v-model="formData.portLength" :min="0" controls-position="right" |
| | | :disabled="true"/> |
| | | <el-input-number v-model="formData.portLength" :min="0" controls-position="right" :disabled="true" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="参数构造" prop="paramStructure"> |
| | | <el-input v-model="formData.paramStructure" placeholder="请输入参数构造" :disabled="true"/> |
| | | <el-input v-model="formData.paramStructure" placeholder="请输入参数构造" :disabled="true" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="模型路径" prop="modelPath"> |
| | | <el-input v-model="formData.modelPath" placeholder="模型路径" :disabled="true"/> |
| | | <el-input v-model="formData.modelPath" placeholder="模型路径" :disabled="true" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | width="150" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-select v-model="scope.row.modelparamtype" placeholder="请选择" |
| | | @change="changeModelparamtype(scope.row)"> |
| | | <el-select v-model="scope.row.modelparamtype" placeholder="请选择" @change="changeModelparamtype(scope.row)"> |
| | | <el-option |
| | | v-for="dict in getStrDictOptions(DICT_TYPE.MODEL_PARAM_TYPE)" |
| | | :key="dict.value" |
| | |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-select-v2 v-if="scope.row.modelparamtype === 'NormalItem'" |
| | | v-model="scope.row.modelparamid" |
| | | :options="modelparamListMap['NormalItem'] || []" |
| | | placeholder="请选择" |
| | | :props="{value:'value',label:'label',options:'children'}" |
| | | clearable |
| | | filterable |
| | | :fit-input-width="false" |
| | | v-model="scope.row.modelparamid" |
| | | :options="modelparamListMap['NormalItem'] || []" |
| | | placeholder="请选择" |
| | | :props="{value:'value',label:'label',options:'children'}" |
| | | clearable |
| | | filterable |
| | | :fit-input-width="false" |
| | | /> |
| | | <el-select-v2 v-else |
| | | v-model="scope.row.modelparamid" |
| | | :options="modelparamListMap[scope.row.modelparamtype] || []" |
| | | placeholder="请选择" |
| | | :props="{value:'id',label:'name'}" |
| | | clearable |
| | | filterable |
| | | :fit-input-width="false" |
| | | v-model="scope.row.modelparamid" |
| | | :options="modelparamListMap[scope.row.modelparamtype] || []" |
| | | placeholder="请选择" |
| | | :props="{value:'id',label:'name'}" |
| | | clearable |
| | | filterable |
| | | :fit-input-width="false" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | width="160" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.datalength" :min="0" clearable |
| | | controls-position="right" |
| | | <el-input-number v-model="scope.row.datalength" :min="0" clearable controls-position="right" |
| | | style="width:100%;hight:100%"/> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <el-divider content-position="left">模型下发配置</el-divider> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="4"> |
| | | <el-button type="primary" size="small" @click="addRowOut()">新增</el-button> |
| | | <el-button type="primary" size="small" @click="addRowOut()" >新增</el-button> |
| | | </el-col> |
| | | </el-row> |
| | | <el-table |
| | |
| | | </el-table-column> |
| | | <el-table-column prop="resultPort" label="角标1" align="center" width="100"> |
| | | <template #default="scope"> |
| | | <el-input-number :min="0" clearable controls-position="right" size="small" |
| | | v-model="scope.row.resultPort" style="width:100%;height:100%"/> |
| | | <el-input-number :min="0" clearable controls-position="right" size="small" v-model="scope.row.resultPort" style="width:100%;height:100%"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="resultIndex" label="角标2" align="center" width="100"> |
| | | <template #default="scope"> |
| | | <el-input-number :min="0" clearable controls-position="right" size="small" |
| | | v-model="scope.row.resultIndex" style="width:100%;height:100%"/> |
| | | <el-input-number :min="0" clearable controls-position="right" size="small" v-model="scope.row.resultIndex" style="width:100%;height:100%"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="isWrite" label="是否下发" align="center" width="100"> |
| | |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="disturbancePointNo" label="无扰切换点位" align="center" min-width="200"> |
| | | <el-table-column prop="disturbancePointNo’" label="无扰切换点位" align="center" min-width="200"> |
| | | <template #default="scope"> |
| | | <el-select-v2 |
| | | v-model="scope.row.disturbancePointNo" |
| | |
| | | </Dialog> |
| | | </template> |
| | | <script lang="ts" setup> |
| | | import {DICT_TYPE, getStrDictOptions} from '@/utils/dict' |
| | | import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' |
| | | import * as ScheduleModelApi from '@/api/model/sche/model' |
| | | import {CommonStatusEnum} from '@/utils/constants' |
| | | import { CommonStatusEnum } from '@/utils/constants' |
| | | import * as MpkApi from "@/api/model/mpk/mpk"; |
| | | import {generateUUID} from "@/utils"; |
| | | import {ElMessage, ElMessageBox} from 'element-plus' |
| | | import {Refresh} from '@element-plus/icons-vue' |
| | | import { ElMessage,ElMessageBox } from 'element-plus' |
| | | import { Refresh } from '@element-plus/icons-vue' |
| | | |
| | | defineOptions({name: 'ScheduleModelForm'}) |
| | | defineOptions({ name: 'ScheduleModelForm' }) |
| | | |
| | | const {t} = useI18n() // 国际化 |
| | | const { t } = useI18n() // 国际化 |
| | | const message = useMessage() // 消息弹窗 |
| | | const dialogVisible = ref(false) // 弹窗的是否展示 |
| | | const dialogTitle = ref('') // 弹窗的标题 |
| | |
| | | modelOut: [] |
| | | }) |
| | | const formRules = reactive({ |
| | | modelCode: [{required: true, message: '模型编号不能为空', trigger: 'blur'}], |
| | | modelName: [{required: true, message: '模型名称不能为空', trigger: 'blur'}], |
| | | modelType: [{required: true, message: '模型类型不能为空', trigger: 'blur'}] |
| | | modelCode: [{ required: true, message: '模型编号不能为空', trigger: 'blur' }], |
| | | modelName: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }], |
| | | modelType: [{ required: true, message: '模型类型不能为空', trigger: 'blur' }] |
| | | }) |
| | | const formRef = ref() // 表单 Ref |
| | | const modelparamListMap = ref({}) |
| | | // 调度模型列表 |
| | | const scheduleModelList = ref([]) |
| | | const aiModelList = ref([]) |
| | | const aiModel = ref([]) |
| | | const model = ref() |
| | | const modelPopover = ref() |
| | | |
| | |
| | | modelparamListMap.value = await ScheduleModelApi.getModelParamList(id) |
| | | // 加载调度模型列表 |
| | | getScheduleModelList() |
| | | // 加载ai模型列表 |
| | | getAiModelList() |
| | | } |
| | | defineExpose({open}) // 提供 open 方法,用于打开弹窗 |
| | | defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
| | | |
| | | /** 提交表单 */ |
| | | const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
| | |
| | | } |
| | | } |
| | | |
| | | const getAiModelList = async () => { |
| | | aiModel.value = await MpkApi.aiList(null) |
| | | if (aiModel.value && aiModel.value.length > 0) { |
| | | aiModelList.value = aiModel.value.map(e => { |
| | | return { |
| | | value: e.id, |
| | | label: e.name, |
| | | children: e.children.map(template => { |
| | | return { |
| | | value: template.id, |
| | | label: template.questionName |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | }; |
| | | |
| | | // 选择调度模型 |
| | | const changeModel = async () => { |
| | | // 校验 |
| | |
| | | ElMessageBox.confirm( |
| | | '是否更新输入参数?', |
| | | '提示', |
| | | { |
| | | confirmButtonText: '是', |
| | | cancelButtonText: '否', |
| | | type: 'success', |
| | | icon: markRaw(Refresh), |
| | | closeOnClickModal: false, |
| | | closeOnPressEscape: false |
| | | } |
| | | {confirmButtonText: '是', cancelButtonText: '否', type: 'success',icon: markRaw(Refresh),closeOnClickModal:false,closeOnPressEscape:false} |
| | | ).then(() => { |
| | | relevanceModel(true) |
| | | }).catch(() => { |
| | | relevanceModel(false) |
| | | }) |
| | | } else { |
| | | }else { |
| | | message.error("请先选择模型") |
| | | } |
| | | } |
| | | |
| | | // 选择ai模型 |
| | | const changeAiModel = () => { |
| | | if (!model.value && model.value.length > 0) return |
| | | // 查找选中的模型组 |
| | | const modelGroup = aiModel.value.find( |
| | | group => group.id === model.value[0] |
| | | ) |
| | | // 查找选中的模板 |
| | | const template = modelGroup?.children.find( |
| | | t => t.id === model.value[1] |
| | | ) |
| | | if (!modelGroup || !template) return |
| | | // 弹窗确认 |
| | | ElMessageBox.confirm( |
| | | '是否更新输入参数?', |
| | | '提示', |
| | | { |
| | | confirmButtonText: '是', |
| | | cancelButtonText: '否', |
| | | type: 'success', |
| | | icon: markRaw(Refresh), |
| | | closeOnClickModal: false, |
| | | closeOnPressEscape: false |
| | | } |
| | | ).then(() => { |
| | | // 更新表单数据 |
| | | updateAiModelForm(modelGroup, template, true) |
| | | }).catch(() => { |
| | | updateAiModelForm(modelGroup, template, false) |
| | | }) |
| | | } |
| | | |
| | | // 更新AI模型表单数据 |
| | | const updateAiModelForm = (modelGroup, template, updateParams) => { |
| | | // 设置基本信息 |
| | | formData.value.modelName = modelGroup.name |
| | | formData.value.methodName = template.questionName |
| | | formData.value.portLength = template.dataLength |
| | | |
| | | // 设置参数列表转换 |
| | | formData.value.settingList = template.settingList.map(setting => { |
| | | return { |
| | | key: setting.settingKey, |
| | | name: setting.settingName, |
| | | value: setting.settingValue, |
| | | valuetype: 'string' |
| | | } |
| | | }) |
| | | // 输入参数 |
| | | let paramList = [] |
| | | for (let i = 0; i < formData.value.portLength; i++) { |
| | | paramList.push({ |
| | | modelparamportorder: i + 1 + '', |
| | | modelparamorder: '1', |
| | | modelparamtype: '', |
| | | modelparamid: '', |
| | | datalength: 0 |
| | | }) |
| | | } |
| | | formData.value.paramList = paramList |
| | | } |
| | | |
| | | |
| | | function relevanceModel(refreshParam) { |
| | | const modelInfo = model.value[0] |
| | |
| | | let paramList = [] |
| | | for (let i = 0; i < methodInfo.dataLength; i++) { |
| | | paramList.push({ |
| | | modelparamportorder: i + 1 + '', |
| | | modelparamportorder: i+1 + '', |
| | | modelparamorder: '1', |
| | | modelparamtype: '', |
| | | modelparamid: '', |
| | |
| | | function changeModelparamtype(row) { |
| | | row.modelparamid = '' |
| | | } |
| | | |
| | | const addRowOut = function () { |
| | | if (formData.value.modelOut === undefined) { |
| | | const addRowOut= function () { |
| | | if(formData.value.modelOut===undefined) { |
| | | formData.value.modelOut = [] |
| | | } |
| | | formData.value.modelOut.push({ |
| | |
| | | port: 0, |
| | | index: 0, |
| | | isWrite: 1, |
| | | pointNo: undefined, |
| | | sort: undefined, |
| | | disturbancePointNo: undefined, |
| | | pointNo:undefined, |
| | | sort:undefined, |
| | | disturbancePointNo:undefined, |
| | | }) |
| | | } |
| | | const deleteModelOutRow = function (index) { |