QueueView.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. <template>
  2. <OCRDialog ref="ocrDialog" :title="queueData.title" :queue_category="queueData.queue_category"
  3. :queue_name="queueData.queue_name" @success="jobCreated"></OCRDialog>
  4. <QueueSelectDialog ref="queueSelectDialog" :title="'选择队列'" @success="jobMoved" :queue_data="queueIdNameDict">
  5. </QueueSelectDialog>
  6. <TextViewDialog ref="textViewDialog" :title="'查看文件'" :content="textViewData"></TextViewDialog>
  7. <el-row>
  8. <span style="text-align: right;">
  9. <h2>工作队列- {{ pageTitle }} </h2>
  10. </span>
  11. </el-row>
  12. <el-row style="margin:15px;">
  13. <el-form :model="searchForm" :inline="true" label-position="right" :size="'medium'">
  14. <el-form-item label="任务名称" prop="job_name" :label-width="70" style="margin-left:0px;margin-right:0px;">
  15. <el-input v-model="searchForm.job_name" :maxlength="32" :size="30"></el-input>
  16. </el-form-item>
  17. <el-form-item label="任务类型" prop="start_category" :label-width="70"
  18. style="margin-left:5px;margin-right:0px;">
  19. <el-select v-model="searchForm.start_category" placeholder="请选择任务类型" style="width:100px;">
  20. <el-option v-for="item in jobStartCategoryList" :key="item.id" :label="item.label"
  21. :value="item.id"></el-option>
  22. </el-select>
  23. </el-form-item>
  24. <el-form-item label="工序" prop="job_category" :label-width="50" style="margin-left:5px;margin-right:0px;">
  25. <el-select v-model="searchForm.job_category" placeholder="请选择任务类型" style="width:120px;margin:0px;">
  26. <el-option v-for="item in jobCategoryList" :key="item.id" :label="item.label"
  27. :value="item.id"></el-option>
  28. </el-select>
  29. </el-form-item>
  30. <el-form-item label="状态" prop="status" :label-width="50" style="margin-left:5px;margin-right:0px;">
  31. <el-select v-model="searchForm.status" placeholder="请选择状态" style="width:120px;">
  32. <el-option v-for="item in jobStatusList" :key="item.value" :label="item.label"
  33. :value="item.value"></el-option>
  34. </el-select>
  35. </el-form-item>
  36. <el-form-item style="justify-items: right;margin-left:10px;">
  37. <el-button type="primary" @click="handleSearch">搜索</el-button>
  38. <el-button type="primary" @click="handleSearchReset" style="margin-left:5px;">重置</el-button>
  39. <el-dropdown style="margin-left:5px;">
  40. <el-button type="primary">
  41. 新建<i class="el-icon-arrow-down el-icon--right"></i>
  42. </el-button>
  43. <template #dropdown>
  44. <el-dropdown-menu>
  45. <el-dropdown-item @click="handleCreateJob('SYSTEM', 'OCR', 'PDF 任务')">PDF
  46. 任务</el-dropdown-item>
  47. <el-dropdown-item @click="handleCreateJob('SYSTEM', 'WORD', 'Word 任务')">Word
  48. 任务</el-dropdown-item>
  49. </el-dropdown-menu>
  50. </template>
  51. </el-dropdown>
  52. <el-button @click="handleRefreshTable" style="margin-left:5px;" type="success">刷新</el-button>
  53. </el-form-item>
  54. </el-form>
  55. </el-row>
  56. <el-row>
  57. <el-table :data="pageData.records" height="650" style="width: 100%;" highlight-current-row
  58. @current-change="handleCurrentChange">
  59. <el-table-column prop="id" label="ID" width="50"></el-table-column>
  60. <el-table-column prop="job_name" label="文件名称" width="200"></el-table-column>
  61. <el-table-column prop="start_category" label="任务类型" width="200">
  62. <template v-slot="scope">
  63. {{ formatJobCategory(scope.row.start_category) }}
  64. </template>
  65. </el-table-column>
  66. <el-table-column prop="job_category" label="当前工序" width="200">
  67. <template v-slot="scope">
  68. {{ formatJobCategory(scope.row.job_category) }}
  69. </template>
  70. </el-table-column>
  71. <el-table-column prop="job_creator" label="创建人" width="100"></el-table-column>
  72. <el-table-column prop="status" label="状态" width="100">
  73. <template v-slot="scope">
  74. <el-tag :type="getJobStatusTag(scope.row.status)">{{ formatStatus(scope.row.status) }}</el-tag>
  75. </template>
  76. </el-table-column>
  77. <el-table-column label="操作" width="500">
  78. <template v-slot="scope">
  79. <el-menu mode="horizontal">
  80. <el-sub-menu index="1">
  81. <template #title>操作</template>
  82. <el-menu-item index="1-1" @click="handleViewJob(scope.row)">查看</el-menu-item>
  83. <!-- <el-menu-item index="1-2" @click="handlePutJobIntoQueue(scope.row)">移入队列</el-menu-item> -->
  84. <el-menu-item index="1-3" @click="handleDeleteJob(scope.row)">删除</el-menu-item>
  85. </el-sub-menu>
  86. </el-menu>
  87. </template>
  88. </el-table-column>
  89. </el-table>
  90. </el-row>
  91. <el-row style="margin-top:30px;">
  92. <el-pagination background layout="prev, pager, next" :total="pageData.total" :page-size="pageData.page_size"
  93. @current-change="handleCurrentPageChange" :current-page.sync="pageData.page">
  94. </el-pagination>
  95. <span style="margin-left: 20px;">{{ pageData.total }}条记录</span>
  96. </el-row>
  97. <el-drawer title="工作详情" v-model="showDrawer" :direction="showDrawerDirection" size="45%">
  98. <el-tabs type="border-card" v-model="jobDetailsActivateTab">
  99. <el-tab-pane label="基本信息" name="basic_info">
  100. <div class="prop_header">任务类型</div>
  101. <div>{{ currentJob?.start_category }}</div>
  102. <div class="prop_header">任务名称</div>
  103. <div>{{ currentJob?.job_name }}</div>
  104. <div class="prop_header">创建人</div>
  105. <div>{{ currentJob?.job_creator }}</div>
  106. <div class="prop_header">创建时间</div>
  107. <div>{{ formatDate(currentJob?.created) }}</div>
  108. <div class="prop_header">更新时间</div>
  109. <div>{{ formatDate(currentJob?.updated) }}</div>
  110. <div class="prop_header">日志</div>
  111. <div><el-input type="textarea" :rows="20">{{ currentJob?.job_logs }}</el-input></div>
  112. </el-tab-pane>
  113. <el-tab-pane label="文件夹" name="file_browse">
  114. <el-row>
  115. <el-col :span="4" style="border-right: 1px solid #CDCDCD;">
  116. <el-tree :data="jobFileData" :props="jobFileTree" @node-click="handleTreeNodeClick"></el-tree>
  117. </el-col>
  118. <el-col :span="20">
  119. <el-table v-if="jobFileInDir.length > 0" :data="jobFileInDir" highlight-current-row
  120. style="width: 100%;">
  121. <el-table-column prop="type" label="类型" width="50">
  122. </el-table-column>
  123. <el-table-column prop="name" label="名称" width="250">
  124. </el-table-column>
  125. <el-table-column prop="size" label="操作" width="100">
  126. <template v-slot="scope">
  127. <a
  128. :href="'/api/file/download/' + currentJob?.id + '?path=' + encodeURIComponent(scope.row.path)">下载</a>
  129. <a @click="handleViewFile(currentJob, scope.row.path)">查看</a>
  130. </template>
  131. </el-table-column>
  132. </el-table>
  133. </el-col>
  134. </el-row>
  135. </el-tab-pane>
  136. </el-tabs>
  137. </el-drawer>
  138. </template>
  139. <script setup lang="ts">
  140. import { ref, onMounted, watch, watchEffect } from 'vue'
  141. import { useRoute, useRouter } from 'vue-router'
  142. import {
  143. getJob,
  144. getJobs,
  145. getStartQueueJobs,
  146. getQueue,
  147. browseJobFile,
  148. getJobStatus,
  149. getJobStatusTag,
  150. JobSatus,
  151. deleteJob,
  152. updateJob,
  153. updateJobStatus,
  154. searchJobs,
  155. getFileContent
  156. } from '@/api/AgentApi'
  157. import type { JobData, SearchData } from '@/api/AgentApi'
  158. import OCRDialog from '@/dialogs/OCRDialog.vue'
  159. import QueueSelectDialog from '@/dialogs/QueueSelectDialog.vue'
  160. import TextViewDialog from '@/dialogs/TextViewDialog.vue'
  161. import { ElNotification, ElMessageBox } from 'element-plus'
  162. import { formatTime } from 'element-plus/es/components/countdown/src/utils.mjs'
  163. import { formatDate } from '@/utils/misc'
  164. import { getSessionVar, saveSessionVar } from '@/utils/session'
  165. const router = useRouter()
  166. //从路由参数中获取队列ID
  167. interface ViewJobData {
  168. id: number,
  169. job_category: string,
  170. job_name: string,
  171. job_creator: string,
  172. created: string,
  173. updated: string,
  174. job_logs: string,
  175. start_category: string,
  176. }
  177. const sessionId = ref("")
  178. const ocrDialog = ref()
  179. const pageTitle = ref("全部工作")
  180. const allowCreateJob = ref(false)
  181. const queueSelectDialog = ref()
  182. const textViewDialog = ref()
  183. //const queueId = ref("")
  184. const queueData = ref({ id: "0", queue_category: "", queue_name: "", title: "" })
  185. const showDrawer = ref(false)
  186. const showDrawerDirection = ref('rtl')
  187. const jobDetailsActivateTab = ref('basic_info')
  188. const queueIdNameDict = ref([
  189. { id: "0", name: "DEFAULT", category: 'SYSTEM', title: "全部工作", dialog: null, is_start: false },
  190. { id: "1", name: "OCR", category: 'SYSTEM', title: "文档OCR", dialog: ocrDialog, is_start: true },
  191. { id: "2", name: "OCR_RESULTS", title: "OCR结果校对", category: 'SYSTEM', dialog: null, is_start: false },
  192. { id: "3", name: "CHUNKS", title: "文本切片队列", category: 'SYSTEM', dialog: null, is_start: false },
  193. { id: "4", name: "KB_EXTRACT", title: "知识抽取", category: 'SYSTEM', dialog: null, is_start: false },
  194. { id: "5", name: "KB_BUILD", title: "图谱数据构建", category: 'SYSTEM', dialog: null, is_start: false },
  195. { id: "6", name: "WORD", title: "WORD文档抽取", category: 'SYSTEM', dialog: ocrDialog, is_start: true },
  196. ])
  197. const searchForm = ref<SearchData>({ job_name: "", start_category: "SYSTEM_ALL", status: 9999, job_category: "SYSTEM_ALL" })
  198. const jobStartCategoryList = ref([{ id: "SYSTEM_ALL", label: "所有类型" }, { id: "SYSTEM_WORD", label: "WORD文档抽取" }, { id: "SYSTEM_OCR", label: "PDF文档抽取" }])
  199. const jobCategoryList = ref([{ id: "SYSTEM_ALL", label: "所有工序" }, { id: "SYSTEM_CHUNKS", label: "文档切片" }, { id: "SYSTEM_KB_EXTRACT", label: "知识抽取" }, { id: "SYSTEM_KB_BUILD", label: "知识构建" }])
  200. var textViewContent = "hello"
  201. const textViewData = ref(textViewContent)
  202. const jobStatusList = ref(JobSatus)
  203. const jobFileTree = ref({
  204. children: 'children',
  205. label: 'display_name'
  206. })
  207. const pageData = ref({ page: 1, pages: 1, page_size: 10, total: 0, records: [] })
  208. const jobFileData = ref([])
  209. const jobFileInDir = ref([])
  210. const currentJob = ref<ViewJobData>({ id: 0, job_category: "USER", job_name: "", job_creator: "", created: "", updated: "", job_logs: "no job logs", start_category: "" })
  211. const currentActionJob = ref({ id: 0, job_category: "USER", job_name: "" })
  212. const route = useRoute()
  213. // watchEffect(route, (newValue, oldValue) => {
  214. // let para = newValue.params.id as string | undefined
  215. // if (para == undefined) {
  216. // return
  217. // }
  218. // console.log('route changed')
  219. // //queueId.value = para
  220. // queueData.value.id = "0"
  221. // loadQueueData()
  222. // })
  223. watchEffect(() => {
  224. let para = route.params.id as string | undefined
  225. if (para == undefined) {
  226. return
  227. }
  228. console.log('route changed')
  229. //queueId.value = para
  230. queueData.value.id = "0"
  231. // queueData.value.queue_category = "SYSTEM"
  232. // queueData.value.queue_name = "ALL"
  233. // queueData.value.title = "全部工作"
  234. loadQueueData()
  235. })
  236. const handleCreateJob = (category: string, name: string, dialog_title: string) => {
  237. if (queueData.value.id == "0") {
  238. ElNotification.info({
  239. title: '消息',
  240. message: '工作队列数据正在加载,请稍候再试',
  241. })
  242. return
  243. }
  244. queueData.value.queue_category = category
  245. queueData.value.queue_name = name
  246. queueData.value.title = dialog_title
  247. let queue_data = queueIdNameDict.value.find((item) => item.category == category && item.name == name)
  248. if (queue_data == undefined) {
  249. return
  250. }
  251. console.log("HHHH")
  252. if (queue_data.dialog != undefined) {
  253. queue_data.dialog.showDialog()
  254. }
  255. }
  256. const handleViewFile = (job: any, path: string) => {
  257. console.log("handleViewFile")
  258. const url = '/api/file/view/' + job?.id + '?path=' + encodeURIComponent(path)
  259. console.log(url)
  260. getFileContent(url).then((res) => {
  261. console.log(res)
  262. textViewData.value = res.records[0].content
  263. textViewDialog.value.showDialog(true)
  264. })
  265. }
  266. const handleRefreshTable = () => {
  267. handleCurrentPageChange(pageData.value.page)
  268. }
  269. function formatStatus(status: number) {
  270. return getJobStatus(status)
  271. }
  272. function formatJobCategory(category: string) {
  273. var title = ""
  274. queueIdNameDict.value.forEach((item) => {
  275. // console.log(category, 'category')
  276. if ((item.category + "_" + item.name) == category) {
  277. // console.log(item.title)
  278. title = item.title
  279. }
  280. })
  281. return title
  282. }
  283. function formatFolderName(name: string) {
  284. let translate = new Map([
  285. ["logs", "日志"],
  286. ["results", "结果"],
  287. ["chunks", "切片"],
  288. ["files", "文件"],
  289. ["ocr_output", "文本转换结果"],
  290. ["kb_extract", "知识抽取"],
  291. ["kb_build", "知识构建"],
  292. ["upload", "文件上传"],
  293. ["output", "输出"]]
  294. )
  295. if (translate.get(name) != undefined) {
  296. return translate.get(name)
  297. }
  298. return name
  299. }
  300. function resetViewData() {
  301. jobFileData.value = []
  302. jobFileInDir.value = []
  303. currentJob.value = { id: 0, job_category: "USER", job_name: "", job_creator: "", created: "", updated: "", job_logs: "no job logs", start_category: "" }
  304. searchForm.value = { job_name: "", start_category: "SYSTEM_ALL", status: 9999, job_category: "SYSTEM_ALL" }
  305. pageData.value.page = 1
  306. pageData.value.page_size = 10
  307. }
  308. function loadJobs(reset: boolean) {
  309. console.log("searchJobs")
  310. if (reset) {
  311. resetViewData()
  312. }
  313. searchJobs(searchForm.value, pageData.value.page, pageData.value.page_size).then((res) => {
  314. pageData.value.page = res.meta.page
  315. pageData.value.pages = res.meta.pages
  316. pageData.value.total = res.meta.total
  317. pageData.value.records = res.records
  318. })
  319. }
  320. function loadQueueData() {
  321. jobFileData.value = []
  322. jobFileInDir.value = []
  323. //currentJob.value = {id:0,job_category:"USER",job_name:""}
  324. currentActionJob.value = { id: 0, job_category: "USER", job_name: "" }
  325. // 根据队列ID获取队列数据
  326. let queue_data = queueIdNameDict.value.find((item) => item.category == queueData.value.queue_category && item.name == queueData.value.queue_name)
  327. if (queue_data == undefined) {
  328. queueData.value = { id: "999", queue_category: 'SYSTEM', queue_name: 'ALL', title: "未知队列" }
  329. return
  330. }
  331. if (queue_data.dialog != null) {
  332. console.log("queue_data.dialog is allowed")
  333. allowCreateJob.value = true
  334. } else {
  335. allowCreateJob.value = false
  336. }
  337. getQueue(queue_data.category, queue_data.name).then((res) => {
  338. if (res.records.length == 0) {
  339. return
  340. }
  341. queueData.value.id = res.records[0].id
  342. queueData.value.queue_category = res.records[0].queue_category
  343. queueData.value.queue_name = res.records[0].queue_name
  344. queueData.value.title = queue_data.title
  345. console.log(queueData.value, ' queueData')
  346. handleCurrentPageChange(1)
  347. })
  348. }
  349. function jobMoved() {
  350. queueSelectDialog.value.showDialog(null, false)
  351. var filterdJobs = pageData.value.records.filter((item: JobData) => {
  352. return item.id != currentActionJob.value.id
  353. })
  354. pageData.value.records = filterdJobs
  355. ElNotification.success({
  356. title: '成功',
  357. message: '工作已经移入指定队列了',
  358. })
  359. }
  360. function jobCreated() {
  361. let queue_data = queueIdNameDict.value.find((item) => item.category == queueData.value.queue_category && item.name == queueData.value.queue_name)
  362. if (queue_data == undefined) {
  363. ElNotification.info({
  364. title: '失败',
  365. message: '工作队列不存在',
  366. })
  367. return
  368. }
  369. if (queue_data.dialog != undefined) {
  370. queue_data.dialog.showDialog(false)
  371. }
  372. ElNotification.success({
  373. title: '成功',
  374. message: '创建工作成功',
  375. })
  376. }
  377. function handleJobSetStatus(job: JobData, status: number) {
  378. if (job == null) {
  379. return
  380. }
  381. job.status = status
  382. updateJobStatus(job).then((res) => {
  383. handleRefreshTable()
  384. ElNotification.success({
  385. title: '成功',
  386. message: '变更工作状态成功',
  387. })
  388. })
  389. }
  390. function handleDeleteJob(job: JobData) {
  391. if (job == null) {
  392. return
  393. }
  394. ElMessageBox.confirm(
  395. '此操作将永久删除该工作, 是否继续?',
  396. '提示',
  397. ).then(() => {
  398. deleteJob({ job_id: job.id }).then((res) => {
  399. handleRefreshTable()
  400. ElNotification.success({
  401. title: '成功',
  402. message: '删除工作成功',
  403. })
  404. })
  405. }).catch(() => {
  406. ElNotification.error({
  407. title: '失败',
  408. message: '删除工作失败,请稍后再试',
  409. })
  410. })
  411. }
  412. function handlePutJobIntoQueue(job: JobData) {
  413. currentActionJob.value = job
  414. queueSelectDialog.value.showDialog(job)
  415. }
  416. function handleTreeNodeClick(data: any) {
  417. if (data.type == 'dir') {
  418. browseJobFile({ job_id: currentJob.value.id, path: data.name }).then((res) => {
  419. jobFileInDir.value = res.records
  420. })
  421. }
  422. }
  423. function handleCurrentPageChange(page: number) {
  424. pageData.value.page = page
  425. loadJobs(false)
  426. // let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
  427. // console.log(queue_data)
  428. // if (queue_data == undefined) {
  429. // return
  430. // }
  431. // if (queue_data?.is_start == false) {
  432. // getJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
  433. // pageData.value.page = res.meta.page
  434. // pageData.value.pages = res.meta.pages
  435. // pageData.value.total = res.meta.total
  436. // pageData.value.records = res.records
  437. // })
  438. // } else {
  439. // getStartQueueJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
  440. // pageData.value.page = res.meta.page
  441. // pageData.value.pages = res.meta.pages
  442. // pageData.value.total = res.meta.total
  443. // pageData.value.records = res.records
  444. // })
  445. // }
  446. }
  447. function handleJobMenuSelect(key: string, keyPath: string[]) {
  448. console.log(key, keyPath)
  449. }
  450. function handleViewJob(job: JobData) {
  451. if (job == null) {
  452. return
  453. }
  454. router.push({ name: 'job-view', params: { id: job.id } })
  455. // jobFileData.value = []
  456. // jobFileInDir.value = []
  457. // getJob(job.id).then((res) => {
  458. // currentJob.value = res.records[0]
  459. // showDrawer.value = true
  460. // browseJobFile({ job_id:job.id, path:""}).then((res) => {
  461. // for (let i = 0; i < res.records.length; i++) {
  462. // res.records[i]['label'] = res.records[i].name
  463. // res.records[i]['children'] = []
  464. // res.records[i]['display_name'] = formatFolderName(res.records[i].name)
  465. // }
  466. // jobFileData.value = res.records
  467. // console.log("show drawer")
  468. // })
  469. // })
  470. }
  471. function handleCurrentChange(data: any) {
  472. if (data == null) {
  473. return
  474. }
  475. //currentJob.value = data
  476. }
  477. function handleSearch() {
  478. pageData.value.page = 1
  479. pageData.value.pages = 0
  480. pageData.value.total = 0
  481. pageData.value.records = []
  482. pageTitle.value = "搜索结果"
  483. loadJobs(false)
  484. }
  485. function handleSearchReset() {
  486. pageData.value.page = 1
  487. pageData.value.pages = 0
  488. pageData.value.total = 0
  489. pageData.value.records = []
  490. resetViewData()
  491. pageTitle.value = "全部工作"
  492. loadJobs(false)
  493. }
  494. onMounted(() => {
  495. sessionId.value = getSessionVar("session_id") as string | ""
  496. //loadQueueData()
  497. loadJobs(true)
  498. })
  499. </script>
  500. <style lang="css" scoped>
  501. .prop_header {
  502. font-weight: bold;
  503. margin-top: 10px;
  504. margin-bottom: 5px;
  505. }
  506. .el-dropdown {
  507. vertical-align: top;
  508. }
  509. .el-dropdown+.el-dropdown {
  510. margin-left: 15px;
  511. }
  512. .el-icon-arrow-down {
  513. font-size: 12px;
  514. }
  515. </style>