app.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. const storageKey = "arbitration_cases";
  2. const state = {
  3. cases: [],
  4. activeCaseId: null,
  5. };
  6. const caseForm = document.getElementById("caseForm");
  7. const caseIdInput = document.getElementById("caseId");
  8. const caseTitleInput = document.getElementById("caseTitle");
  9. const caseDescInput = document.getElementById("caseDesc");
  10. const caseFormReset = document.getElementById("caseFormReset");
  11. const casesTable = document.getElementById("casesTable");
  12. const caseTimeline = document.getElementById("caseTimeline");
  13. const arbitrationForm = document.getElementById("arbitrationForm");
  14. const arbitrationCaseSelect = document.getElementById("arbitrationCaseSelect");
  15. const newCaseIdInput = document.getElementById("newCaseId");
  16. const newCaseTitleInput = document.getElementById("newCaseTitle");
  17. const applicationResult = document.getElementById("applicationResult");
  18. const transcriptResult = document.getElementById("transcriptResult");
  19. const finalDecision = document.getElementById("finalDecision");
  20. const confirmMaterials = document.getElementById("confirmMaterials");
  21. const confirmDecision = document.getElementById("confirmDecision");
  22. const arbitrationStatus = document.getElementById("arbitrationStatus");
  23. const applicationFiles = document.getElementById("applicationFiles");
  24. const transcriptFiles = document.getElementById("transcriptFiles");
  25. const evidenceFiles = document.getElementById("evidenceFiles");
  26. const lawResults = document.getElementById("lawResults");
  27. const judgementReasoning = document.getElementById("judgementReasoning");
  28. const disputeFindings = document.getElementById("disputeFindings");
  29. const apiStatus = document.getElementById("apiStatus");
  30. const workflowStatus = document.getElementById("workflowStatus");
  31. const toolApplicationRun = document.getElementById("toolApplicationRun");
  32. const toolTranscriptRun = document.getElementById("toolTranscriptRun");
  33. const toolLawRun = document.getElementById("toolLawRun");
  34. const toolApplicationInput = document.getElementById("toolApplicationInput");
  35. const toolTranscriptInput = document.getElementById("toolTranscriptInput");
  36. const toolLawInput = document.getElementById("toolLawInput");
  37. const toolApplicationOutput = document.getElementById("toolApplicationOutput");
  38. const toolTranscriptOutput = document.getElementById("toolTranscriptOutput");
  39. const toolLawOutput = document.getElementById("toolLawOutput");
  40. const toolApplicationFiles = document.getElementById("toolApplicationFiles");
  41. const toolTranscriptFiles = document.getElementById("toolTranscriptFiles");
  42. const toolLawFiles = document.getElementById("toolLawFiles");
  43. const previewModal = document.getElementById("previewModal");
  44. const modalBackdrop = document.getElementById("modalBackdrop");
  45. const modalClose = document.getElementById("modalClose");
  46. const modalFilePreview = document.getElementById("modalFilePreview");
  47. const modalResultText = document.getElementById("modalResultText");
  48. const toast = document.getElementById("toast");
  49. const arbitrationSteps = document.querySelectorAll(".stepper .step");
  50. let modalTarget = null;
  51. function loadCases() {
  52. return callBackend("/api/cases", null, false, "GET")
  53. .then((result) => {
  54. state.cases = result.cases || [];
  55. })
  56. .catch(() => {
  57. state.cases = [];
  58. });
  59. }
  60. function saveCases() {
  61. return;
  62. }
  63. function getApiBase() {
  64. if (window.location.origin === "null") {
  65. return "http://127.0.0.1:8000";
  66. }
  67. return window.location.origin;
  68. }
  69. function upsertCase(caseData) {
  70. const payload = {
  71. case_id: caseData.caseId,
  72. title: caseData.title,
  73. description: caseData.description || "",
  74. status: caseData.status || "草稿",
  75. stage: caseData.stage || "案件管理",
  76. };
  77. return callBackend("/api/cases", payload, false, "POST")
  78. .then(() => loadCases())
  79. .finally(() => {
  80. renderCases();
  81. renderCaseSelect();
  82. });
  83. }
  84. function deleteCase(caseId) {
  85. const payload = { case_id: caseId };
  86. return callBackend("/api/cases", payload, false, "DELETE")
  87. .then(() => loadCases())
  88. .finally(() => {
  89. renderCases();
  90. renderCaseSelect();
  91. });
  92. }
  93. function getCaseId(item) {
  94. return item?.caseId || item?.case_id || "";
  95. }
  96. function updateCaseStatus(caseId, status, stage) {
  97. const target = state.cases.find((item) => getCaseId(item) === caseId);
  98. if (target) {
  99. target.status = status;
  100. target.stage = stage || target.stage;
  101. target.updatedAt = new Date().toLocaleString();
  102. const payload = {
  103. case_id: getCaseId(target),
  104. title: target.title,
  105. description: target.description || "",
  106. status: target.status,
  107. stage: target.stage || "",
  108. };
  109. callBackend("/api/cases", payload, false, "PUT")
  110. .then(() => loadCases())
  111. .finally(() => {
  112. renderCases();
  113. updateWorkflowStatus(target.stage);
  114. });
  115. }
  116. }
  117. function renderCases() {
  118. casesTable.innerHTML = "";
  119. state.cases.forEach((item) => {
  120. const row = document.createElement("tr");
  121. const updated = item.updated_at ? new Date(item.updated_at).toLocaleString() : item.updatedAt || "-";
  122. const caseId = getCaseId(item);
  123. row.innerHTML = `
  124. <td>${caseId}</td>
  125. <td>${item.title}</td>
  126. <td>${item.status || "草稿"}</td>
  127. <td>${updated}</td>
  128. <td>
  129. <button class="button secondary" data-action="edit" data-id="${caseId}">编辑</button>
  130. <button class="button secondary" data-action="delete" data-id="${caseId}">删除</button>
  131. </td>
  132. `;
  133. casesTable.appendChild(row);
  134. });
  135. caseTimeline.innerHTML = "";
  136. state.cases.forEach((item) => {
  137. const node = document.createElement("div");
  138. node.className = "timeline-item";
  139. const caseId = getCaseId(item);
  140. node.textContent = `${caseId} - ${item.stage || "未开始"} - ${item.status || "草稿"}`;
  141. caseTimeline.appendChild(node);
  142. });
  143. }
  144. function renderCaseSelect() {
  145. arbitrationCaseSelect.innerHTML = `<option value="">选择已有案件</option>`;
  146. state.cases.forEach((item) => {
  147. const option = document.createElement("option");
  148. const caseId = getCaseId(item);
  149. option.value = caseId;
  150. option.textContent = `${caseId} - ${item.title}`;
  151. arbitrationCaseSelect.appendChild(option);
  152. });
  153. }
  154. function resetCaseForm() {
  155. caseIdInput.value = "";
  156. caseTitleInput.value = "";
  157. caseDescInput.value = "";
  158. }
  159. caseForm.addEventListener("submit", async (event) => {
  160. event.preventDefault();
  161. const caseId = caseIdInput.value.trim();
  162. const title = caseTitleInput.value.trim();
  163. const description = caseDescInput.value.trim();
  164. if (!caseId || !title) {
  165. return;
  166. }
  167. const submitButton = caseForm.querySelector("button[type='submit']");
  168. setLoading(submitButton, true);
  169. showToast("保存中");
  170. try {
  171. await upsertCase({
  172. caseId,
  173. title,
  174. description,
  175. status: "草稿",
  176. stage: "案件管理",
  177. updatedAt: new Date().toLocaleString(),
  178. });
  179. showToast("保存成功");
  180. } catch (error) {
  181. showToast("保存失败,请检查服务状态");
  182. } finally {
  183. setLoading(submitButton, false);
  184. resetCaseForm();
  185. }
  186. });
  187. caseFormReset.addEventListener("click", () => {
  188. resetCaseForm();
  189. });
  190. function switchTab(tabName) {
  191. document.querySelectorAll(".nav-item").forEach((btn) => btn.classList.remove("active"));
  192. document.querySelectorAll(".tab").forEach((tab) => tab.classList.remove("active"));
  193. const targetButton = document.querySelector(`.nav-item[data-tab="${tabName}"]`);
  194. const targetTab = document.getElementById(`tab-${tabName}`);
  195. if (targetButton) {
  196. targetButton.classList.add("active");
  197. }
  198. if (targetTab) {
  199. targetTab.classList.add("active");
  200. }
  201. }
  202. async function loadArbitrationCase(caseId) {
  203. if (!caseId) {
  204. return;
  205. }
  206. updateArbitrationStatus("正在加载案件数据...");
  207. try {
  208. const result = await callBackend(`/api/arbitration/case?case_id=${encodeURIComponent(caseId)}`, null, false, "GET");
  209. const data = result?.case || {};
  210. if (newCaseTitleInput) {
  211. newCaseTitleInput.value = data.title || "";
  212. }
  213. if (applicationResult) {
  214. applicationResult.value = data.application_result || data.application_text || "";
  215. }
  216. if (transcriptResult) {
  217. transcriptResult.value = data.transcript_result || data.transcript_text || "";
  218. }
  219. renderFileList(applicationFiles, data.application_files || [], applicationResult);
  220. renderFileList(transcriptFiles, data.transcript_files || [], transcriptResult);
  221. renderEvidenceList(evidenceFiles, data.evidence_files || {});
  222. if (lawResults) {
  223. lawResults.value = formatLawResults(data.law_results);
  224. }
  225. if (judgementReasoning) {
  226. judgementReasoning.value = data.final_judgement?.reasoning || "";
  227. }
  228. if (disputeFindings) {
  229. disputeFindings.value = formatDisputeFindings(data.final_judgement?.dispute_point_findings);
  230. }
  231. if (finalDecision) {
  232. finalDecision.value = data.final_judgement?.final_decision || data.final_decision || "";
  233. }
  234. updateArbitrationStatus("案件数据已载入");
  235. showToast("已加载案件数据");
  236. } catch (error) {
  237. updateArbitrationStatus(`案件数据加载失败:${error.message || "请检查服务状态"}`);
  238. showToast("案件数据加载失败");
  239. }
  240. }
  241. casesTable.addEventListener("click", (event) => {
  242. const target = event.target;
  243. if (!(target instanceof HTMLButtonElement)) {
  244. return;
  245. }
  246. const caseId = target.dataset.id;
  247. if (!caseId) {
  248. return;
  249. }
  250. if (target.dataset.action === "edit") {
  251. switchTab("arbitration");
  252. arbitrationCaseSelect.value = caseId;
  253. newCaseIdInput.value = "";
  254. newCaseTitleInput.value = "";
  255. loadArbitrationCase(caseId);
  256. }
  257. if (target.dataset.action === "delete") {
  258. deleteCase(caseId);
  259. }
  260. });
  261. function getCurrentCaseId() {
  262. const selected = arbitrationCaseSelect.value;
  263. if (selected) {
  264. return selected;
  265. }
  266. const newCaseId = newCaseIdInput.value.trim();
  267. if (newCaseId) {
  268. return newCaseId;
  269. }
  270. return "";
  271. }
  272. function ensureCaseForArbitration() {
  273. let caseId = arbitrationCaseSelect.value;
  274. if (!caseId) {
  275. caseId = newCaseIdInput.value.trim();
  276. const title = newCaseTitleInput.value.trim() || "未命名案件";
  277. if (caseId) {
  278. upsertCase({
  279. caseId,
  280. title,
  281. status: "草稿",
  282. stage: "材料提交",
  283. updatedAt: new Date().toLocaleString(),
  284. });
  285. arbitrationCaseSelect.value = caseId;
  286. }
  287. }
  288. return caseId;
  289. }
  290. function updateArbitrationStatus(message) {
  291. arbitrationStatus.textContent = message || "";
  292. }
  293. function updateStepper(activeIndex) {
  294. if (!arbitrationSteps || arbitrationSteps.length === 0) {
  295. return;
  296. }
  297. arbitrationSteps.forEach((step, index) => {
  298. step.classList.toggle("active", index === activeIndex);
  299. step.classList.toggle("done", index < activeIndex);
  300. });
  301. }
  302. function showToast(message) {
  303. if (!toast) {
  304. return;
  305. }
  306. toast.textContent = message;
  307. toast.classList.add("show");
  308. setTimeout(() => {
  309. toast.classList.remove("show");
  310. }, 1800);
  311. }
  312. function setLoading(button, isLoading) {
  313. if (!button) {
  314. return;
  315. }
  316. const loadingText = button.getAttribute("data-loading-text") || "处理中...";
  317. if (isLoading) {
  318. button.dataset.originalText = button.textContent || "";
  319. button.textContent = loadingText;
  320. button.classList.add("loading");
  321. button.disabled = true;
  322. } else {
  323. button.textContent = button.dataset.originalText || button.textContent;
  324. button.classList.remove("loading");
  325. button.disabled = false;
  326. }
  327. }
  328. function openPreviewModal(file, targetTextarea) {
  329. if (!previewModal) {
  330. return;
  331. }
  332. modalTarget = targetTextarea || null;
  333. if (modalResultText) {
  334. modalResultText.value = modalTarget ? modalTarget.value : "";
  335. }
  336. if (modalFilePreview) {
  337. modalFilePreview.src = file.url || "";
  338. }
  339. previewModal.classList.add("open");
  340. }
  341. function closePreviewModal() {
  342. if (!previewModal) {
  343. return;
  344. }
  345. previewModal.classList.remove("open");
  346. if (modalFilePreview) {
  347. modalFilePreview.src = "";
  348. }
  349. modalTarget = null;
  350. }
  351. if (modalClose) {
  352. modalClose.addEventListener("click", closePreviewModal);
  353. }
  354. if (modalBackdrop) {
  355. modalBackdrop.addEventListener("click", closePreviewModal);
  356. }
  357. if (modalResultText) {
  358. modalResultText.addEventListener("input", () => {
  359. if (modalTarget) {
  360. modalTarget.value = modalResultText.value;
  361. }
  362. });
  363. }
  364. function renderFileList(container, files, targetTextarea) {
  365. if (!container) {
  366. return;
  367. }
  368. container.innerHTML = "";
  369. if (!files || files.length === 0) {
  370. return;
  371. }
  372. files.forEach((item) => {
  373. const row = document.createElement("div");
  374. row.className = "file-item";
  375. const name = document.createElement("div");
  376. name.textContent = item.name || "文件";
  377. const actions = document.createElement("div");
  378. const viewBtn = document.createElement("button");
  379. viewBtn.textContent = "预览对照";
  380. viewBtn.addEventListener("click", () => {
  381. openPreviewModal(item, targetTextarea);
  382. });
  383. const link = document.createElement("a");
  384. link.textContent = "下载";
  385. link.href = item.url || "#";
  386. link.target = "_blank";
  387. actions.appendChild(viewBtn);
  388. actions.appendChild(link);
  389. row.appendChild(name);
  390. row.appendChild(actions);
  391. container.appendChild(row);
  392. });
  393. }
  394. function renderEvidenceFileList(container, files) {
  395. if (!container) {
  396. return;
  397. }
  398. container.innerHTML = "";
  399. if (!Array.isArray(files) || files.length === 0) {
  400. return;
  401. }
  402. files.forEach((item) => {
  403. const row = document.createElement("div");
  404. row.className = "file-item";
  405. const name = document.createElement("div");
  406. name.textContent = item.name || "文件";
  407. const actions = document.createElement("div");
  408. const link = document.createElement("a");
  409. link.textContent = "下载";
  410. link.href = item.url || "#";
  411. link.target = "_blank";
  412. actions.appendChild(link);
  413. row.appendChild(name);
  414. row.appendChild(actions);
  415. container.appendChild(row);
  416. });
  417. }
  418. function renderEvidenceList(container, evidenceMap) {
  419. if (!container) {
  420. return;
  421. }
  422. container.innerHTML = "";
  423. if (!evidenceMap || typeof evidenceMap !== "object") {
  424. return;
  425. }
  426. Object.keys(evidenceMap).forEach((category) => {
  427. const group = document.createElement("div");
  428. group.className = "file-group";
  429. const title = document.createElement("div");
  430. title.className = "file-group-title";
  431. title.textContent = category;
  432. const list = document.createElement("div");
  433. list.className = "file-group-list";
  434. group.appendChild(title);
  435. group.appendChild(list);
  436. container.appendChild(group);
  437. const files = Array.isArray(evidenceMap[category]) ? evidenceMap[category] : [];
  438. renderEvidenceFileList(list, files);
  439. });
  440. }
  441. function formatLawResults(lawResultsMap) {
  442. if (!lawResultsMap) {
  443. return "";
  444. }
  445. let data = lawResultsMap;
  446. if (typeof data === "string") {
  447. try {
  448. data = JSON.parse(data);
  449. } catch (error) {
  450. return data;
  451. }
  452. }
  453. const lines = [];
  454. if (Array.isArray(data)) {
  455. data.forEach((item, index) => {
  456. const lawId = item?.law_id || "未知";
  457. const content = item?.content || "";
  458. lines.push(`${index + 1}. ${lawId} ${content}`);
  459. });
  460. return lines.join("\n").trim();
  461. }
  462. if (typeof data !== "object") {
  463. return "";
  464. }
  465. Object.keys(data).forEach((point) => {
  466. lines.push(`争议焦点:${point}`);
  467. const items = Array.isArray(data[point]) ? data[point] : [];
  468. items.forEach((item, index) => {
  469. const lawId = item?.law_id || "未知";
  470. const content = item?.content || "";
  471. lines.push(`${index + 1}. ${lawId} ${content}`);
  472. });
  473. lines.push("");
  474. });
  475. return lines.join("\n").trim();
  476. }
  477. function formatDisputeFindings(findings) {
  478. if (!Array.isArray(findings)) {
  479. return "";
  480. }
  481. return findings
  482. .map((item, index) => {
  483. const point = item?.dispute_point || "未命名争议焦点";
  484. const finding = item?.finding || "";
  485. const evidence = Array.isArray(item?.evidence_used) ? item.evidence_used.join("、") : "";
  486. const laws = Array.isArray(item?.law_applied) ? item.law_applied.join("、") : "";
  487. return `${index + 1}. ${point}\n结论:${finding}\n证据:${evidence}\n法律:${laws}`;
  488. })
  489. .join("\n\n");
  490. }
  491. async function callBackend(path, payload, isFormData, method = "POST") {
  492. const url = `${getApiBase()}${path}`;
  493. const options = {
  494. method,
  495. headers: {},
  496. body: null,
  497. };
  498. if (method === "GET") {
  499. options.body = null;
  500. } else if (isFormData) {
  501. options.body = payload;
  502. } else {
  503. options.headers["Content-Type"] = "application/json";
  504. options.body = JSON.stringify(payload);
  505. }
  506. const response = await fetch(url, options);
  507. if (!response.ok) {
  508. let message = "后端请求失败";
  509. try {
  510. const data = await response.json();
  511. message = data.detail || data.message || message;
  512. } catch (error) {
  513. try {
  514. const text = await response.text();
  515. if (text) {
  516. message = text;
  517. }
  518. } catch (readError) {
  519. message = "后端请求失败";
  520. }
  521. }
  522. throw new Error(message);
  523. }
  524. return response.json();
  525. }
  526. arbitrationForm.addEventListener("submit", async (event) => {
  527. event.preventDefault();
  528. const caseId = ensureCaseForArbitration();
  529. if (!caseId) {
  530. updateArbitrationStatus("请先选择或输入案件编号");
  531. return;
  532. }
  533. const submitButton = arbitrationForm.querySelector("button[type='submit']");
  534. setLoading(submitButton, true);
  535. updateCaseStatus(caseId, "草稿", "材料提交");
  536. updateArbitrationStatus("材料提交中...");
  537. updateStepper(0);
  538. const formData = new FormData(arbitrationForm);
  539. formData.append("case_id", caseId);
  540. formData.append("case_title", newCaseTitleInput.value.trim());
  541. try {
  542. const result = await callBackend("/api/arbitration/submit", formData, true);
  543. if (result) {
  544. applicationResult.value = result.application_result || "";
  545. transcriptResult.value = result.transcript_result || "";
  546. renderFileList(applicationFiles, result.application_files || [], applicationResult);
  547. renderFileList(transcriptFiles, result.transcript_files || [], transcriptResult);
  548. renderEvidenceList(evidenceFiles, result.evidence_files || {});
  549. } else {
  550. applicationResult.value = "申请书处理结果已生成,可编辑调整。";
  551. transcriptResult.value = "庭审笔录处理结果已生成,可编辑调整。";
  552. }
  553. if (!applicationResult.value && !transcriptResult.value) {
  554. updateArbitrationStatus("未识别到材料内容,请确认已上传申请书/庭审笔录");
  555. showToast("未识别到材料内容");
  556. } else {
  557. updateArbitrationStatus("材料已处理,可修改后确认");
  558. showToast("材料处理完成");
  559. }
  560. updateStepper(1);
  561. } catch (error) {
  562. applicationResult.value = "申请书处理结果已生成,可编辑调整。";
  563. transcriptResult.value = "庭审笔录处理结果已生成,可编辑调整。";
  564. updateArbitrationStatus(`材料处理失败:${error.message || "请检查服务状态"}`);
  565. showToast("材料处理失败");
  566. } finally {
  567. setLoading(submitButton, false);
  568. }
  569. });
  570. confirmMaterials.addEventListener("click", async () => {
  571. const caseId = getCurrentCaseId();
  572. if (!caseId) {
  573. updateArbitrationStatus("请先提交材料");
  574. return;
  575. }
  576. setLoading(confirmMaterials, true);
  577. updateCaseStatus(caseId, "草稿", "裁决中");
  578. updateArbitrationStatus("正在生成裁决...");
  579. updateStepper(1);
  580. const payload = {
  581. case_id: caseId,
  582. application_result: applicationResult.value.trim(),
  583. transcript_result: transcriptResult.value.trim(),
  584. };
  585. try {
  586. const result = await callBackend("/api/arbitration/judgement", payload, false);
  587. if (result) {
  588. finalDecision.value = result.final_decision || "";
  589. if (lawResults) {
  590. lawResults.value = formatLawResults(result.law_results);
  591. }
  592. if (judgementReasoning) {
  593. judgementReasoning.value = result.final_judgement?.reasoning || "";
  594. }
  595. if (disputeFindings) {
  596. disputeFindings.value = formatDisputeFindings(result.final_judgement?.dispute_point_findings);
  597. }
  598. } else {
  599. finalDecision.value = "裁决结果已生成,可编辑调整。";
  600. }
  601. updateArbitrationStatus("裁决已生成,可修改后确认");
  602. updateStepper(2);
  603. showToast("裁决生成完成");
  604. } catch (error) {
  605. finalDecision.value = "裁决结果已生成,可编辑调整。";
  606. updateArbitrationStatus(`裁决生成失败:${error.message || "请检查服务状态"}`);
  607. showToast("裁决生成失败");
  608. } finally {
  609. setLoading(confirmMaterials, false);
  610. }
  611. });
  612. confirmDecision.addEventListener("click", async () => {
  613. const caseId = getCurrentCaseId();
  614. if (!caseId) {
  615. updateArbitrationStatus("请先完成裁决");
  616. return;
  617. }
  618. setLoading(confirmDecision, true);
  619. const payload = {
  620. case_id: caseId,
  621. final_decision: finalDecision.value.trim(),
  622. };
  623. try {
  624. const result = await callBackend("/api/arbitration/confirm", payload, false);
  625. updateArbitrationStatus("裁决已入库");
  626. updateStepper(2);
  627. showToast("裁决已入库");
  628. } catch (error) {
  629. updateArbitrationStatus(`入库失败:${error.message || "请检查服务状态"}`);
  630. showToast("裁决入库失败");
  631. }
  632. updateCaseStatus(caseId, "已完成", "裁决完成");
  633. setLoading(confirmDecision, false);
  634. });
  635. function runTool(input, output, path) {
  636. return async () => {
  637. if (!input.files || input.files.length === 0) {
  638. output.value = "请选择文件";
  639. showToast("请选择文件");
  640. return;
  641. }
  642. const button = input.closest(".card")?.querySelector("button");
  643. setLoading(button, true);
  644. output.value = "处理中...";
  645. showToast("开始处理");
  646. const payload = new FormData();
  647. Array.from(input.files).forEach((file) => payload.append("files", file));
  648. try {
  649. const result = await callBackend(path, payload, true);
  650. output.value = JSON.stringify(result, null, 2);
  651. if (path.includes("/application")) {
  652. renderFileList(toolApplicationFiles, result.files || [], output);
  653. }
  654. if (path.includes("/transcript")) {
  655. renderFileList(toolTranscriptFiles, result.files || [], output);
  656. }
  657. showToast("处理完成");
  658. } catch (error) {
  659. output.value = `处理失败:${error.message || "请检查服务状态"}`;
  660. showToast("处理失败");
  661. } finally {
  662. setLoading(button, false);
  663. }
  664. };
  665. }
  666. toolApplicationRun.addEventListener("click", runTool(toolApplicationInput, toolApplicationOutput, "/api/tools/application"));
  667. toolTranscriptRun.addEventListener("click", runTool(toolTranscriptInput, toolTranscriptOutput, "/api/tools/transcript"));
  668. toolLawRun.addEventListener("click", async () => {
  669. const query = toolLawInput?.value?.trim() || "";
  670. if (!query) {
  671. toolLawOutput.value = "请输入法律问题";
  672. showToast("请输入法律问题");
  673. return;
  674. }
  675. setLoading(toolLawRun, true);
  676. toolLawOutput.value = "检索中...";
  677. showToast("开始检索");
  678. try {
  679. const result = await callBackend("/api/tools/law", { query }, false);
  680. toolLawOutput.value = JSON.stringify(result.result || [], null, 2);
  681. showToast("检索完成");
  682. } catch (error) {
  683. toolLawOutput.value = `检索失败:${error.message || "请检查服务状态"}`;
  684. showToast("检索失败");
  685. } finally {
  686. setLoading(toolLawRun, false);
  687. }
  688. });
  689. document.querySelectorAll(".nav-item").forEach((item) => {
  690. item.addEventListener("click", () => {
  691. document.querySelectorAll(".nav-item").forEach((btn) => btn.classList.remove("active"));
  692. document.querySelectorAll(".tab").forEach((tab) => tab.classList.remove("active"));
  693. item.classList.add("active");
  694. const target = item.dataset.tab;
  695. document.getElementById(`tab-${target}`).classList.add("active");
  696. });
  697. });
  698. function updateWorkflowStatus(stage) {
  699. if (!workflowStatus) {
  700. return;
  701. }
  702. workflowStatus.textContent = stage || "待提交材料";
  703. }
  704. function initApiStatus() {
  705. if (apiStatus) {
  706. apiStatus.textContent = getApiBase();
  707. }
  708. }
  709. loadCases().finally(() => {
  710. renderCases();
  711. renderCaseSelect();
  712. initApiStatus();
  713. updateWorkflowStatus();
  714. updateStepper(0);
  715. });