新增历史任务详情页
This commit is contained in:
parent
94cf2be101
commit
d08db8a76a
@ -13,9 +13,12 @@ export default {
|
||||
copy: "Copy",
|
||||
paste: "Paste",
|
||||
cut: "Cut",
|
||||
baseInfo: "Base Info",
|
||||
|
||||
createdDt: "Created Date",
|
||||
updatedDt: "Updated Date",
|
||||
noData: "No Data",
|
||||
|
||||
menu: {
|
||||
task: "Task",
|
||||
history: "History",
|
||||
|
@ -13,9 +13,12 @@ export default {
|
||||
copy: "复制",
|
||||
paste: "粘贴",
|
||||
cut: "剪切",
|
||||
baseInfo: "基本信息",
|
||||
|
||||
createdDt: "创建时间",
|
||||
updatedDt: "更新时间",
|
||||
noData: "暂无数据",
|
||||
|
||||
menu: {
|
||||
task: "任务",
|
||||
history: "历史记录",
|
||||
|
@ -32,7 +32,7 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: 'history',
|
||||
component: () => import('@/views/task/History.vue'),
|
||||
component: () => import('@/views/task/HistoryIndex.vue'),
|
||||
meta: {
|
||||
keepAlive: false,
|
||||
title: "历史记录",
|
||||
|
@ -1,4 +1,4 @@
|
||||
<template :lang="i18n.locale">
|
||||
<template>
|
||||
<div class="main-content">
|
||||
<el-card>
|
||||
<template #header>
|
||||
@ -54,11 +54,11 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, inject, computed, onMounted, onBeforeUnmount, onUnmounted, watch } from 'vue'
|
||||
import { FolderAdd, Promotion, Eleme, CircleClose } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useConfig } from '@/store/config'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import i18n from '@/locales/i18n'
|
||||
|
||||
const router = useRouter()
|
||||
const utils = inject('utils')
|
||||
const config = useConfig()
|
||||
const { t } = useI18n()
|
||||
@ -171,7 +171,6 @@ function search() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
const handleSelectionChange = (val) => {
|
||||
selectedRows.value = val
|
||||
}
|
||||
@ -229,120 +228,10 @@ function resetSearch() {
|
||||
|
||||
function toTaskInfo(taskId) {
|
||||
console.log("toTaskInfo:", taskId)
|
||||
router.push("/task/"+taskId)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.output-area {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.dialog-user {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dialog-user .blank {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.dialog-user .content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: end;
|
||||
border-radius: 12px;
|
||||
background-color: var(--el-fg-color);
|
||||
}
|
||||
|
||||
.dialog-user .title {
|
||||
/** 防止子元素宽度被设置为100%, 子元素的align-self设置除auto和stretch之外的值 */
|
||||
align-self: flex-end;
|
||||
margin: 6px 16px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.dialog-user .prompt {
|
||||
/** 防止子元素宽度被设置为100%, 子元素的align-self设置除auto和stretch之外的值 */
|
||||
align-self: flex-end;
|
||||
margin: 0px 16px 6px 16px;
|
||||
}
|
||||
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.dialog-ai {
|
||||
margin-bottom: 16px;
|
||||
background-color: var(--el-fg-color);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.dialog-ai .title {
|
||||
margin: 6px 12px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
flex-grow: 0;
|
||||
width: 100%;
|
||||
max-height: 180px;
|
||||
padding-left: 80px;
|
||||
padding-right: 80px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
background-color: var(--el-fg-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-style {
|
||||
width: 100%;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.input-style :deep(.el-textarea__inner) {
|
||||
outline: none;
|
||||
border: none;
|
||||
resize: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.add-file-area {
|
||||
margin-left: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.send-area {
|
||||
margin-left: 8px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.sub-step-time {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
@ -1,4 +1,4 @@
|
||||
<template :lang="i18n.locale">
|
||||
<template>
|
||||
<div class="main-content fc">
|
||||
<el-scrollbar ref="scrollRef" style="width: 100%;">
|
||||
<div class="output-area" v-show="taskInfo.taskId != null">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<template :lang="i18n.locale">
|
||||
<template>
|
||||
<div class="main-content fc">
|
||||
<el-scrollbar ref="scrollRef">
|
||||
<div class="output-area" v-show="taskInfo.taskId != null">
|
||||
@ -6,9 +6,12 @@
|
||||
<div class="dialog-user">
|
||||
<div class="blank"></div>
|
||||
<div class="content">
|
||||
<el-text class="title">
|
||||
<div class="title fxc">
|
||||
<img src="@/assets/img/user.png" class="user-img"/>
|
||||
<el-text>
|
||||
{{ t('user') }}
|
||||
</el-text>
|
||||
</div>
|
||||
<el-text class="prompt">
|
||||
{{ taskInfo.prompt }}
|
||||
</el-text>
|
||||
@ -84,7 +87,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, inject, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { FolderAdd, Promotion, Eleme, CircleClose } from '@element-plus/icons-vue'
|
||||
import { FolderAdd, Promotion, User, CircleClose } from '@element-plus/icons-vue'
|
||||
import { useConfig } from '@/store/config'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@ -296,6 +299,7 @@ function stop() {
|
||||
<style scoped>
|
||||
.output-area {
|
||||
flex-grow: 1;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.dialog-user {
|
||||
@ -331,12 +335,17 @@ function stop() {
|
||||
margin: 0px 16px 6px 16px;
|
||||
}
|
||||
|
||||
.dialog-user .user-img{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.dialog-ai {
|
||||
margin-bottom: 16px;
|
||||
background-color: var(--el-fg-color);
|
||||
|
@ -1,14 +1,20 @@
|
||||
<template :lang="i18n.locale">
|
||||
<template>
|
||||
<div class="main-content fc">
|
||||
<el-scrollbar ref="scrollRef">
|
||||
<div class="output-area" v-show="taskInfo.taskId != null">
|
||||
<!-- 展示模块-暂无数据 -->
|
||||
<div class="no-data" v-show="baseNoData">{{ t('noData') }}</div>
|
||||
|
||||
<!-- 展示模块 -->
|
||||
<div class="output-area" v-show="baseShow">
|
||||
|
||||
<div class="dialog-user">
|
||||
<div class="blank"></div>
|
||||
<div class="content">
|
||||
<el-text class="title">
|
||||
<div class="title fxc">
|
||||
<img src="@/assets/img/user.png" class="user-img" />
|
||||
<el-text>
|
||||
{{ t('user') }}
|
||||
</el-text>
|
||||
</div>
|
||||
<el-text class="prompt">
|
||||
{{ taskInfo.prompt }}
|
||||
</el-text>
|
||||
@ -47,256 +53,53 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-text class="pr-10">{{ t('taskStatus.name') }}:</el-text>
|
||||
<el-text>{{ taskInfo.status }}</el-text>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="input-box">
|
||||
<el-icon @click="uploadFile" class="add-file-area" :size="24">
|
||||
<FolderAdd />
|
||||
</el-icon>
|
||||
<el-input ref="promptEle" type="textarea" v-model="prompt" class="input-style" style="border: none;"
|
||||
:autosize="{ minRows: 1, maxRows: 4 }" autofocus :placeholder="t('promptInputPlaceHolder')"
|
||||
@keydown.enter="handleInputEnter" />
|
||||
|
||||
<el-link class="send-area">
|
||||
<el-icon @click="sendPrompt" :size="24" v-show="!loading">
|
||||
<Promotion />
|
||||
</el-icon>
|
||||
<el-icon @click="stop" :size="24" v-show="loading">
|
||||
<CircleClose />
|
||||
</el-icon>
|
||||
</el-link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-text class="tips">{{ t('openManusAgiTips') }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, inject, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { FolderAdd, Promotion, Eleme, CircleClose } from '@element-plus/icons-vue'
|
||||
import { User } from '@element-plus/icons-vue'
|
||||
import { useConfig } from '@/store/config'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import i18n from '@/locales/i18n'
|
||||
|
||||
const utils = inject('utils')
|
||||
const config = useConfig()
|
||||
const { t } = useI18n()
|
||||
|
||||
const prompt = ref('')
|
||||
const promptEle = ref(null)
|
||||
// 视图模式
|
||||
const viewModel = reactive({
|
||||
base: 'show'
|
||||
})
|
||||
|
||||
const eventTypes = ['think', 'tool', 'act', 'log', 'run', 'message']
|
||||
const eventSource = ref(null)
|
||||
const baseShow = computed(() => {
|
||||
return viewModel.base == 'show' || viewModel.base == 'showMore'
|
||||
})
|
||||
|
||||
const baseNoData = computed(() => {
|
||||
return baseShow && taskInfo.value == null
|
||||
})
|
||||
|
||||
const taskInfo = computed(() => {
|
||||
return config.getCurrTask()
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const scrollRef = ref(null)
|
||||
|
||||
// 建立EventSource连接
|
||||
const buildEventSource = (taskId) => {
|
||||
loading.value = true
|
||||
eventSource.value = new EventSource('http://localhost:5172/tasks/' + taskId + '/events')
|
||||
eventSource.value.onmessage = (event) => {
|
||||
console.log('Received data:', event.data)
|
||||
// 在这里处理接收到的数据 不起作用
|
||||
}
|
||||
|
||||
eventTypes.forEach(type => {
|
||||
eventSource.value.addEventListener(type, (event) => handleEvent(event, type))
|
||||
})
|
||||
|
||||
eventSource.value.onerror = (error) => {
|
||||
console.error('EventSource failed:', error)
|
||||
// 处理错误情况
|
||||
loading.value = false
|
||||
eventSource.value.close()
|
||||
taskInfo.value.status = "taskStatus.failed"
|
||||
utils.pop("任务执行失败", "error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const handleEvent = (event, type) => {
|
||||
console.log('Received event, type:', type, event.data)
|
||||
// clearInterval(heartbeatTimer);
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("type:", type, "data:", data)
|
||||
if (eventSource.value.readyState === EventSource.CLOSED) {
|
||||
console.log('Connection is closed');
|
||||
}
|
||||
if (type == "complete" || data.status == "completed") {
|
||||
console.log('task completed');
|
||||
loading.value = false
|
||||
eventSource.value.close()
|
||||
taskInfo.value.status = "taskStatus.success"
|
||||
utils.pop("任务已完成", "success")
|
||||
return
|
||||
}
|
||||
// autoScroll(stepContainer);
|
||||
buildOutput(taskInfo.value.taskId)
|
||||
} catch (e) {
|
||||
console.error(`Error handling ${type} event:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
async function buildOutput(taskId) {
|
||||
// 同步执行,确保数据顺序
|
||||
await utils.awaitGet('http://localhost:5172/tasks/' + taskId).then(data => {
|
||||
console.log("task info resp:", data)
|
||||
buildStepList(data.steps)
|
||||
console.log("stepList:", taskInfo.value.stepList)
|
||||
// 滚动到底部
|
||||
setTimeout(() => {
|
||||
scrollToBottom()
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
// 封装stepList
|
||||
const buildStepList = (steps) => {
|
||||
// stepList
|
||||
steps.forEach((step, idx) => {
|
||||
// 步骤
|
||||
if (step.type == "log" && step.result.startsWith("Executing step")) {
|
||||
const stepStr = step.result.replace("Executing step ", "").replace("\n", "")
|
||||
const stepNo = stepStr.split("/")[0]
|
||||
if (taskInfo.value.stepList.length < stepNo) {
|
||||
// 添加此step到stepList
|
||||
const parentStep = {
|
||||
type: "log",
|
||||
idx: idx,
|
||||
stepNo: stepNo,
|
||||
result: stepStr,
|
||||
subList: [],
|
||||
createdDt: utils.dateFormat(new Date())
|
||||
}
|
||||
taskInfo.value.stepList.push(parentStep)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 子步骤
|
||||
const subStep = {
|
||||
type: step.type,
|
||||
idx: idx,
|
||||
result: step.result,
|
||||
createdDt: utils.dateFormat(new Date())
|
||||
}
|
||||
// 判定添加到stepList中的哪个元素元素的subList中
|
||||
console.log("stepList:", taskInfo.value.stepList, "idx:", idx)
|
||||
let parentStep = null
|
||||
const pStepIndex = taskInfo.value.stepList.findIndex(parentStep => parentStep.idx > idx)
|
||||
console.log("pStepIndex:", pStepIndex)
|
||||
if (pStepIndex != -1) {
|
||||
// 取pStep的上一个元素
|
||||
parentStep = taskInfo.value.stepList[pStepIndex - 1]
|
||||
} else {
|
||||
// 不存在时, 添加到stepList最后一个元素末尾
|
||||
parentStep = taskInfo.value.stepList[taskInfo.value.stepList.length - 1]
|
||||
}
|
||||
console.log("parentStep:", parentStep)
|
||||
const existSubStep = parentStep.subList.find(existSubStep => existSubStep.idx == idx)
|
||||
if (!existSubStep) {
|
||||
// 不存在时, 添加到末尾
|
||||
parentStep.subList.push(subStep)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
// 组件卸载时关闭EventSource连接
|
||||
if (eventSource.value) {
|
||||
eventSource.value.close()
|
||||
}
|
||||
})
|
||||
|
||||
function handleInputEnter(event) {
|
||||
console.log("handleInputEnter:", event)
|
||||
event.preventDefault()
|
||||
sendPrompt()
|
||||
}
|
||||
|
||||
function uploadFile() {
|
||||
utils.pop("暂不支持,开发中", "warning")
|
||||
}
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (scrollRef.value) {
|
||||
console.log("scrollRef:", scrollRef.value, scrollRef.value.wrapRef)
|
||||
const container = scrollRef.value.wrapRef
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 发送提示词
|
||||
function sendPrompt() {
|
||||
// 关闭之前的连接
|
||||
if (eventSource.value != null) {
|
||||
eventSource.value.close()
|
||||
}
|
||||
|
||||
if (utils.isBlank(prompt.value)) {
|
||||
utils.pop("Please enter a valid prompt", "error")
|
||||
promptEle.value.focus()
|
||||
return
|
||||
}
|
||||
|
||||
utils.post('http://localhost:5172/tasks', { prompt: prompt.value }).then(data => {
|
||||
if (!data.task_id) {
|
||||
throw new Error('Invalid task ID')
|
||||
}
|
||||
const newTask = {
|
||||
taskId: data.task_id,
|
||||
prompt: prompt.value,
|
||||
status: "running",
|
||||
createdDt: utils.dateFormat(new Date()),
|
||||
stepList: []
|
||||
}
|
||||
// 保存历史记录
|
||||
config.addTaskHistory(newTask)
|
||||
// 发送完成后清空输入框
|
||||
prompt.value = ''
|
||||
// 建立新的EventSource连接
|
||||
buildEventSource(data.task_id)
|
||||
|
||||
console.log("new task created:", newTask)
|
||||
}).catch(error => {
|
||||
console.error('Failed to create task:', error)
|
||||
})
|
||||
}
|
||||
|
||||
function stop() {
|
||||
console.log("stop")
|
||||
loading.value = false
|
||||
eventSource.value.close()
|
||||
taskInfo.value.status = "taskStatus.terminated"
|
||||
utils.pop("用户终止任务", "error")
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.output-area {
|
||||
flex-grow: 1;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dialog-user {
|
||||
@ -332,6 +135,12 @@ function stop() {
|
||||
margin: 0px 16px 6px 16px;
|
||||
}
|
||||
|
||||
.dialog-user .user-img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
|
Loading…
x
Reference in New Issue
Block a user