From c8cdd3325c1c4b53730a03b33abe999ea2518a7d Mon Sep 17 00:00:00 2001 From: Bifang <915779419@qq.com> Date: Mon, 11 May 2026 10:00:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=BB=E7=BB=93=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/app_state.json | 2 +- .../results/json/1778233001316/sub_topic.json | 61 +-- .../md/1778233001316/meeting_summary.md | 64 ++- frontend/assets/app.js | 397 ++++++++++++++++-- frontend/assets/styles.css | 156 +++++++ frontend/index.html | 69 ++- template_guides/template2.md | 17 +- web/__pycache__/server.cpython-314.pyc | Bin 39625 -> 40301 bytes web/server.py | 31 +- 9 files changed, 697 insertions(+), 100 deletions(-) diff --git a/data/app_state.json b/data/app_state.json index f228c6b..d5cebbe 100644 --- a/data/app_state.json +++ b/data/app_state.json @@ -1,3 +1,3 @@ { - "active_meeting_id": "1778233001316" + "active_meeting_id": "1778221747643" } \ No newline at end of file diff --git a/data/results/json/1778233001316/sub_topic.json b/data/results/json/1778233001316/sub_topic.json index d717c35..1f2f21f 100644 --- a/data/results/json/1778233001316/sub_topic.json +++ b/data/results/json/1778233001316/sub_topic.json @@ -1,59 +1,44 @@ { "sub_topics": [ { - "title": "宽带业务上门量、转化率与退单管控", + "title": "宽带装维指标与业务转化率分析", "time_interval": "00:00:01 - 00:03:51", - "overview": "会议通报了宽带安装与上门维护的整体进度,累计上门量达标但距3000户目标仍有差距。关键指标方面,弱光率降至0.51接近目标,三代终端压降改善,月度转化率87.35%接近90%目标,退单率控制在6.53%。主要退单因素为用户原因、天气影响及审核流程待优化,需加强B2C协调与一线审批意识。" + "overview": "会议通报宽带装维上门量及安装进度,指出受天气影响累计进度偏后,但弱光指标(0.51)及三代终端指标持续向好。同时分析业务月度转化率(87.35%)与退单率(6.53%),指出退单主因为用户原因及B2C协调审核流程问题,需优化审批意识。" }, { - "title": "PCDN专线质量恶化与学校出口限速机制", - "time_interval": "00:03:53 - 00:05:31", - "overview": "会议重点讨论了PCDN专线流量持续恶化问题,主要集中于两所学校开学后的出口带宽占用。目前市公司已介入分析涉及学校的IP地址,正协同校方建立发现超限直接限速的管控机制。该问题因学校互联网带宽免费导致管控难度大,需加快限速策略落地以保障网络质量。" + "title": "PCDN专线问题与网络质量巡检通报", + "time_interval": "00:03:53 - 00:07:56", + "overview": "针对PCDN专线恶化问题,明确主要集中于学校出口带宽,已上报市公司分析IP并计划协同限速。网络质量方面,超频基站故障已及时处理,专线护航118条存量及新建线路巡检按计划推进,当周收到33件报障且光缆问题为主。" }, { - "title": "无线网络质量优化与专线护航巡检进展", - "time_interval": "00:05:33 - 00:07:56", - "overview": "网络质量方面,物业点检测因天线问题延期至4月初,唱厅工单需待智能体质检结束,5G基站正通过参数调整试点解决漏话问题。超频站点故障已及时恢复,旁站故障率低。专线护航巡检方面,118条存量专线已巡检56条,未验收工程45条全量巡检,预计月底可按时完成全量巡检任务。" + "title": "故障通报、二级基站拆除与网络评估体系", + "time_interval": "00:07:57 - 00:10:00", + "overview": "通报当周网络故障情况,东环指标保持良好。重点汇报二级基站拆除进度,剩余75站预计两周内完成下电及服务费调整,以配合4月15日前完成专项调整。此外,已收到网络质量综合评估体系文件,下周将分解制定目标。" }, { - "title": "当周故障通报与二级站拆除及网络评估体系", - "time_interval": "00:07:57 - 00:09:35", - "overview": "当周共收报障33件,其中9件网络相关故障以光缆问题为主。重点推进金沙管家二级站拆除工作,剩余75站预计两周内完成下电及电费调整,目标4月15日前全量完成。此外,内部已收到网络质量综合评估体系文件,下周将分解制定具体目标。" + "title": "综合部重点工作进展与资产优化推进", + "time_interval": "00:10:01 - 00:12:39", + "overview": "汇报建委框架清单、投资计划评估及终端占比措施等已完成事项,并就打印设备采购价格差异问题制定外派保障方案。资产优化方面形成清单周报每周沟通推进,数据中心包装、无人机需求等7项工作正按计划落实。" }, { - "title": "综合部重点工作推进与跨部门协同事项", - "time_interval": "00:09:46 - 00:12:39", - "overview": "综合部汇报了25年剩余两项工作进展,建委框架清单与投资计划已汇报完成,终端占比问题已制定五项措施。会议同步推进数据中心包装、基建宣传、渠道产能评估及资产优化等7项工作,部分事项因兼职或流程原因仍在梳理方案,预计本周内完成专题汇报。" + "title": "工会经费公示、后勤改造与运动会筹备调度", + "time_interval": "00:12:40 - 00:19:34", + "overview": "通报工会经费压减情况,公示食堂改造(约29.8万)及下半年引入自饮机方案以节约成本。重点筹备周五第四届体育文化节,因主力选手肌肉拉伤缺席,需各部门抽调人员补齐方阵,并明确参与激励政策。" }, { - "title": "工会经费管理、食堂改造与员工文体活动筹备", - "time_interval": "00:12:40 - 00:18:54", - "overview": "工会经费因上级压减需严考严用,已公示并上报软性工程,食堂改造预算约29.8万元正进行审计与方案优化。同时规划下半年引入自饮机节约成本,并全年组织六场大型体育活动。针对周五第四届体育文化节,需各部门抽调人员补充方阵缺额,确定人员后将统一购衣并于周中排练。" + "title": "主题教育开展、招待费管控与区级评优申报评估", + "time_interval": "00:19:36 - 00:27:28", + "overview": "通报主题教育首期简报及基层党组织学习情况,公示2025年招待费使用超预算14%(综合部超107%),强调政企接待需统筹分摊。同时评估河川区“担当作为”评优申报可行性,因缺乏明确推荐机制及竞争激烈,建议暂缓盲目投入。" }, { - "title": "党建主题教育、招待费管控与区级荣誉申报", - "time_interval": "00:18:55 - 00:27:30", - "overview": "主题教育一期简报已发布,基层党组织学习已按期完成,招待费已按年度向管理层公示并报备纪检。25年招待费整体超预算14%,综合部超支严重,政企部需统筹对外招待避免客户经理个人承担。此外,正对接河川区担当作为集体/个人评选,因评选偏向政府机构且流程尚不明确,建议先摸清内部意向再决定是否申报。" + "title": "三期拆迁增收、商客市场拓展与管理作风整顿", + "time_interval": "00:27:29 - 00:35:21", + "overview": "汇报三期拆迁工作聚焦二次光改与价值提升,本周开展5场社区与单位营销但签约效果未达预期。商客市场2月收入88.5万同比增1万,价值拓展完成115。会议强调管理人员需养成“日清日结”习惯,杜绝工作拖延。" }, { - "title": "商客市场收入拓展与社区/单位拆迁营销进展", - "time_interval": "00:30:23 - 00:32:04", - "overview": "商客市场2月收入88.5万元,较上期增加1万元,超改、城北和南京改增幅超2万元,价值拓展完成115户。拆迁营销主要聚焦通货场景价值提升,已完成1145户改造,通过社区清洗与单位集中服务相结合推进,本周开展5场活动但签约转化率未达预期,需重点加强落实签约板块。" - }, - { - "title": "建委评估体系落实与工作执行纪律要求", - "time_interval": "00:32:15 - 00:35:22", - "overview": "会议强调需严格落实建委今年综合评估体系,要求各项回复工作做到日清日结、量化结果。针对当前部分经理回复滞后、工作拖延的问题,会议严肃指出必须养成及时汇报与跟进的习惯,未完成任务需主动沟通困难。同时要求针对上课指标拿出具体可行方案,结合专线助账客单位业务推进,确保指标达成。" - }, - { - "title": "市场部季度收官、满意度测评整改与指标管控", - "time_interval": "00:35:23 - 00:42:25", - "overview": "市场部需提前谋划二季度业务活动,避免淡季疲软,并要求本周内汇报招聘、农村渠道进度及西消队伍建设方案。针对满意度测评结果不佳,会议批评了主管思想松懈问题,强调需按四公司规定打造满意客户样板,对不满客户利用时间节点和技巧进行干预。要求商务部每日微信发送日报,并摸细KPI考核规则,利用可控操作动作平衡指标。" - }, - { - "title": "运动会安排、年度考核摸排与执行力强调", - "time_interval": "00:42:26 - 00:46:16", - "overview": "运动会筹备工作已明确时间节点,分管领导出差期间需提前视频汇报。会议重点强调四公司年度考核的提前摸排,要求各部门主动了解可能影响考核的指标变化,避免被动。领导指出当前部分工作存在知道怎么做却不去做的作风问题,要求各级人员强化执行力,确保年度考核起跑线公平,争取通过努力改善成绩。" + "title": "市场部季度收官、满意度考核攻坚与年度考核部署", + "time_interval": "00:35:22 - 00:46:16", + "overview": "要求市场部提前谋划二季度活动,本周内汇报招聘、农村渠道及营销方案进度。针对满意度测评拉分项,要求负责人亲自抓,按市公司规范操作“80/200/2000/20000”指标并每日报送日报。最后强调全员执行力,提前摸排年度考核影响因素,确保在公平竞争中取得改善。" } ] } \ No newline at end of file diff --git a/data/results/md/1778233001316/meeting_summary.md b/data/results/md/1778233001316/meeting_summary.md index 63f1ba1..4b9b05a 100644 --- a/data/results/md/1778233001316/meeting_summary.md +++ b/data/results/md/1778233001316/meeting_summary.md @@ -2,13 +2,21 @@ # 会议记录 -议 题:合川分公司周例会(2026年第X期) +议 题:合川分公司周例会 时 间:2026年5月6日 13:37 +地 点:分公司会议室 + 主持人:AlanPaine -议程:各部门工作汇报、重点工作部署与执行纪律要求 +参加人:分公司领导、各部门经理及负责人 + +议程: + + 一、各部门汇报 + + 二、分公司领导指示部署 --- @@ -16,31 +24,45 @@ ### 一、各部门汇报 -- **网络/建维方面:** 宽带安装累计上门量达标但距3000户目标仍有差距;弱光率降至0.51,三代终端压降改善,月度转化率87.35%(目标90%),退单率6.53%;PCDN专线流量恶化集中于两所学校,正协同市公司分析IP并建立超限限速机制;5G基站通过参数调整试点解决漏话,超频站点及旁站故障已及时恢复;118条存量专线及45条未验收工程已全量巡检,预计月底按时完成;当周报障33件(9件网络相关,以光缆为主);金沙管家二级站剩余75站预计两周内完成下电及电费调整;网络质量综合评估体系已下发,下周分解目标。 -- **综合部方面:** 建委框架清单、投资计划及终端占比措施已汇报完成;数据中心包装、基建宣传、渠道产能评估等7项工作正梳理方案;工会经费压减需严考严用,食堂改造预算29.8万元正审计,下半年拟引入自饮机;第四届体育文化节方阵缺9人,需各部门抽调,周中排练;主题教育一期简报已发,基层党组织学习完成;招待费已按年度公示报备,整体超预算14%,综合部超支明显;河川区担当作为集体/个人评选正对接,建议摸清意向后再申报。 -- **市场/商客方面:** 商客市场2月收入88.5万元,较上期增1万元,价值拓展完成115户;拆迁营销聚焦通货场景价值提升,已完成1145户,本周开展5场社区/单位活动但签约转化率未达预期;满意度测评结果不佳,主管存在思想松懈问题;商务部需本周内汇报招聘、农村渠道进度及西消队伍建设方案。 +网络建维部、综合部、市场部按议程现场依次汇报。综合部通报前期重点工作推进及资产优化情况:建委框架清单、投资计划评估等已落实;资产优化形成清单周报每周沟通推进;食堂改造方案已拟定;工会经费压减及自饮机引入方案已明确。 + +**网络建维方面:** +- 宽带装维当周上门量580户,受天气影响累计进度偏后,弱光指标0.51持续向好,三代终端指标正按年度目标压降。 +- PCDN专线恶化集中于学校出口带宽,已报市公司分析IP并协同限速。 +- 二级基站剩余75站预计2周内完成下电及服务费调整,配合4月中旬专项调整。 +- 专线护航118条存量及新建线路按计划推进巡检;当周收到故障报障33件,以光缆问题为主。 + +**综合部方面:** +- 打印设备采购价格差异大,拟采用外派保障方案确保招投标打印不受影响。 +- 工会经费压减,公示食堂改造(约29.8万)及下半年引入自饮机方案以节约成本。 +- 重点筹备周五第四届体育文化节,因主力队员肌肉拉伤缺席,需各部门抽调人员补齐方阵,明确参与激励政策。 +- 通报主题教育开展情况,2025年招待费超预算14%(综合部超107%),政企接待需统筹分摊;评估河川区“担当作为”评优申报,因缺乏明确推荐机制建议暂缓。 + +**市场部方面:** +- 三期拆迁聚焦二次光改与价值提升,本周开展5场社区与单位营销,但落实签约效果未达预期。 +- 商客市场2月收入88.5万元,价值拓展完成115;商客经理基础业务统计需按周推进。 +- 满意度测评存在拉分项,需负责人亲自抓,按市公司规范操作指标。 --- ### 二、部署强调 -#### 建维及综合分管领导强调: +#### 分公司领导强调: -1. **工作纪律与执行反馈:** - - 各项回复工作必须做到日清日结、量化结果,严禁拖延或敷衍,未完成任务需主动沟通困难并推进。 - - 针对“上课”指标,需结合专线助账客单位业务发展拿出具体可行方案,明确责任人确保达成。 -2. **资源统筹与合规管理:** - - 招待费使用需统筹规范,政企部对外招待应合理分摊,避免由个别客户经理个人承担,严禁历史费用拖欠。 - - 河川区荣誉申报需先摸清内部意向与流程,确认有推荐名额后再行申报,避免资源浪费。 +1. **网络与运维方面:** + - 强化“日清日结”工作习惯,杜绝工作拖延,未完成事项需及时沟通推进并量化结果。 + - PCDN学校出口问题需尽快落实限速机制,保障专线质量。 + - 二级基站拆除务必按期完成下电及服务费调整,配合专项工作节点。 ---- +2. **市场与商客方面:** + - 提前谋划二季度业务活动,打破淡季思维,全面造势提升业务活跃度。 + - 本周内汇报招聘、农村渠道建设及营销方案进度;商客经理需按周统计基础业务,加强落实签约。 -#### 市场及考核分管领导强调: +3. **满意度与考核攻坚:** + - 针对满意度测评拉分项,负责人必须亲自抓,严格按市公司规范操作“80/200/2000/20000”指标,每日微信报送日报。 + - 明确KPI考核方向(工信部有责及网率),充分利用操作空间平衡指标,确保考核达标。 -1. **市场收官与满意度整改:** - - 市场部需提前谋划二季度业务活动,克服淡季疲软思想,全面提振签约、商客及AI军团等业务活动声势。 - - 满意度工作必须落实到位,主管需亲自抓,严格按四公司规定打造满意客户样板;对不满客户需利用时间节点和技巧进行干预,商务部每日需发送日报。 - - 摸细KPI考核规则(工信部有责指标及网率考核),利用可控的操作动作平衡指标,确保考核成绩。 -2. **年度考核与执行力建设:** - - 提前摸排四公司年度考核指标变化,主动了解可能影响考核的病毒、卡位点或政策调整,提前沟通争取有利条件,避免被动。 - - 严厉批评“知道怎么做却不去做”的作风,要求各级管理人员及一线人员强化执行力,确保年度考核起跑线公平,通过努力改善成绩。 \ No newline at end of file +4. **综合管理与作风整顿:** + - 强化全员执行力,杜绝“知道怎么做却不去做”的作风,各项工作需明确措施、确保成效。 + - 政企接待费用需统筹分摊,避免个别客户经理集中招待导致预算超支。 + - 提前摸排年度考核影响因素,特别是集团相关考核项,确保在公平竞争中取得改善,避免起跑线落后。 \ No newline at end of file diff --git a/frontend/assets/app.js b/frontend/assets/app.js index f991fd7..489a056 100644 --- a/frontend/assets/app.js +++ b/frontend/assets/app.js @@ -10,6 +10,7 @@ const state = { guideBusy: false, resultEditMode: false, rightEditMode: false, + processGuideEditMode: false, selectedTreeKey: "", rightResource: null, }; @@ -92,6 +93,12 @@ function meetingById(meetingId) { return state.meetings.find((item) => item.id === meetingId) || null; } +function patchMeeting(meetingId, partial) { + state.meetings = state.meetings.map((item) => ( + item.id === meetingId ? { ...item, ...partial } : item + )); +} + function templateMetaByName(name) { return state.templates.find((item) => item.name === name) || null; } @@ -152,6 +159,127 @@ function refreshActionButtons() { updateGuideButton(resource); } +function setProcessGuideEditMode(editMode) { + state.processGuideEditMode = editMode; + const editor = $("#process-guide-editor"); + const button = $("#btn-process-guide-edit"); + editor.readOnly = !editMode; + editor.classList.toggle("is-editing", editMode); + button.textContent = editMode ? "保存" : "编辑"; +} + +function syncProcessGuideConfirmLabel() { + const meeting = meetingById(state.meetingId); + $("#btn-process-guide-confirm").textContent = meeting?.has_summary ? "重总结" : "确认"; +} + +function showReparseGuidePrompt() { + $("#reparse-guide-notes").value = ""; + $("#reparse-guide-prompt").hidden = false; + $("#reparse-guide-notes").focus(); +} + +function hideReparseGuidePrompt() { + $("#reparse-guide-prompt").hidden = true; +} + +function resetReparseModalProgress() { + $("#reparse-stream-title").textContent = "正在重新解析模板说明..."; + $("#reparse-stream-content").textContent = ""; +} + +function openStandaloneReparseModal() { + $("#reparse-modal-title").textContent = `模板说明补充 · ${state.templateName}`; + $("#reparse-modal-subtitle").textContent = "可为空;会和系统解析模板一起用于重新解析"; + $("#reparse-modal-notes").value = ""; + $("#reparse-modal-form").hidden = false; + $("#reparse-modal-progress").hidden = true; + $("#btn-reparse-modal-cancel").disabled = false; + $("#btn-reparse-modal-confirm").disabled = false; + resetReparseModalProgress(); + openModal("modal-reparse-guide"); + $("#reparse-modal-notes").focus(); +} + +function setStandaloneReparseProcessing(active) { + $("#reparse-modal-form").hidden = active; + $("#reparse-modal-progress").hidden = !active; + $("#btn-reparse-modal-cancel").disabled = active; + $("#btn-reparse-modal-confirm").disabled = active; +} + +function pushStandaloneReparseLine(text) { + const box = $("#reparse-stream-content"); + const lines = box.textContent ? box.textContent.split("\n") : []; + lines.push(text); + box.textContent = lines.slice(-6).join("\n"); +} + +async function runStandaloneGuideReparseFlow() { + const templateName = state.templateName; + const userNotes = $("#reparse-modal-notes").value || ""; + + state.guideBusy = true; + setStatus("right", true, "解析中"); + setStandaloneReparseProcessing(true); + resetReparseModalProgress(); + pushStandaloneReparseLine(`模板:${templateName}`); + pushStandaloneReparseLine("已提交重解析请求,正在准备说明..."); + refreshActionButtons(); + + const hints = [ + "正在结合你的补充说明整理解析重点...", + "正在按解析模板抽取可执行的使用说明...", + "即将刷新右侧资源并打开最新说明...", + ]; + let hintIndex = 0; + const timer = window.setInterval(() => { + if (hintIndex < hints.length) { + pushStandaloneReparseLine(hints[hintIndex]); + hintIndex += 1; + } + }, 900); + + try { + const result = await runTemplateGuideReparse(templateName, userNotes); + pushStandaloneReparseLine("模板说明已更新。"); + closeModal("modal-reparse-guide"); + await openProcessGuideModal(); + $("#process-guide-editor").value = result.content || ""; + } finally { + window.clearInterval(timer); + state.guideBusy = false; + setStatus("right", false, "空闲"); + setStandaloneReparseProcessing(false); + refreshActionButtons(); + } +} + +async function refreshRightResourceAfterGuideReparse(templateName, content) { + const resource = state.rightResource; + if (!resource) { + return; + } + + const isTargetGuide = resource.type === "template-guide" && resource.templateName === templateName; + const isTargetTemplate = resource.type === "template" && resource.name === templateName; + if (!isTargetGuide && !isTargetTemplate) { + return; + } + + if (isTargetGuide) { + resource.content = content; + $("#side-editor").value = content; + if (!state.rightEditMode) { + renderSidePreview(resource); + } + return; + } + + const treeKey = resource.treeKey || `file:template_guides/${templateName}`; + await openTemplateGuide(templateName, treeKey); +} + function resetProcessingStream() { $("#stream-box").style.display = "none"; $("#stream-title").textContent = ""; @@ -759,7 +887,134 @@ async function ensureTemplateGuideBeforeProcess() { } } -function startMeetingProcess() { +async function loadTemplateGuideForProcess() { + const data = await api(`/api/templates/${encodeURIComponent(state.templateName)}/guide`); + $("#guide-modal-title").textContent = `模板说明 · ${state.templateName}`; + $("#guide-modal-subtitle").textContent = "确认后开始总结,也可以先补充说明或重解析"; + $("#process-guide-editor").value = data.content || ""; + if (!$("#process-extra-notes").dataset.keepValue) { + $("#process-extra-notes").value = ""; + } + setProcessGuideEditMode(false); + syncProcessGuideConfirmLabel(); +} + +async function saveProcessGuideFromModal() { + const content = $("#process-guide-editor").value; + await api(`/api/templates/${encodeURIComponent(state.templateName)}/guide`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content }), + }); + + state.templates = state.templates.map((item) => ( + item.name === state.templateName ? { ...item, has_guide: true } : item + )); + + if ( + state.rightResource && + state.rightResource.templateName === state.templateName && + state.rightResource.type === "template-guide" + ) { + state.rightResource.content = content; + } + if ( + state.rightResource && + state.rightResource.name === state.templateName && + state.rightResource.type === "template" + ) { + state.rightResource.hasGuide = true; + } + + setProcessGuideEditMode(false); + refreshActionButtons(); + toast("说明已保存"); +} + +async function openProcessGuideModal() { + openModal("modal-process-guide"); + hideReparseGuidePrompt(); + $("#guide-modal-title").textContent = `模板说明 · ${state.templateName}`; + $("#guide-modal-subtitle").textContent = "正在准备模板说明..."; + $("#process-guide-editor").value = "正在加载模板说明,请稍等..."; + $("#process-guide-editor").readOnly = true; + $("#process-guide-editor").classList.remove("is-editing"); + $("#process-extra-notes").dataset.keepValue = "1"; + $("#btn-process-guide-edit").disabled = true; + $("#btn-process-guide-reparse").disabled = true; + $("#btn-process-guide-confirm").disabled = true; + + try { + await ensureTemplateGuideBeforeProcess(); + await loadTemplateGuideForProcess(); + } catch (error) { + closeModal("modal-process-guide"); + throw error; + } finally { + $("#btn-process-guide-edit").disabled = false; + $("#btn-process-guide-reparse").disabled = false; + $("#btn-process-guide-confirm").disabled = false; + delete $("#process-extra-notes").dataset.keepValue; + } +} + +async function reparseTemplateGuideFromModal(userNotes = "") { + const templateName = state.templateName; + + state.guideBusy = true; + setStatus("right", true, "解析中"); + $("#btn-process-guide-edit").disabled = true; + $("#btn-process-guide-reparse").disabled = true; + $("#btn-process-guide-confirm").disabled = true; + $("#guide-modal-subtitle").textContent = "正在结合补充说明重新解析模板..."; + refreshActionButtons(); + + try { + if (state.processGuideEditMode) { + await saveProcessGuideFromModal(); + } + + const result = await runTemplateGuideReparse(templateName, userNotes); + + + $("#process-guide-editor").value = result.content || ""; + $("#guide-modal-title").textContent = `???? ? ${templateName}`; + $("#guide-modal-subtitle").textContent = "确认后开始总结,也可以先补充说明或重解析"; + setProcessGuideEditMode(false); + + toast(`说明已更新:${templateName}`); + } finally { + state.guideBusy = false; + setStatus("right", false, "空闲"); + $("#btn-process-guide-edit").disabled = false; + $("#btn-process-guide-reparse").disabled = false; + $("#btn-process-guide-confirm").disabled = false; + refreshActionButtons(); + } +} + +async function runTemplateGuideReparse(templateName, userNotes = "") { + const params = new URLSearchParams(); + if (userNotes.trim()) { + params.set("user_notes", userNotes.trim()); + } + const suffix = params.toString() ? `?${params.toString()}` : ""; + const result = await api(`/api/templates/${encodeURIComponent(templateName)}/guide/reparse${suffix}`, { + method: "POST", + }); + + state.templates = state.templates.map((item) => ( + item.name === templateName ? { ...item, has_guide: true } : item + )); + + if (state.rightResource?.name === templateName && state.rightResource.type === "template") { + state.rightResource.hasGuide = true; + } + await refreshRightResourceAfterGuideReparse(templateName, result.content || ""); + return result; +} + +function startMeetingProcess(userNotes = "") { state.processing = true; setStatus("left", true, "总结中"); refreshActionButtons(); @@ -768,9 +1023,12 @@ function startMeetingProcess() { $("#stream-title").textContent = "第一阶段:结构化主题..."; $("#stream-content").textContent = ""; - const source = new EventSource( - `/api/meetings/${state.meetingId}/process?template_name=${encodeURIComponent(state.templateName)}`, - ); + const params = new URLSearchParams({ template_name: state.templateName }); + if (userNotes.trim()) { + params.set("user_notes", userNotes.trim()); + } + + const source = new EventSource(`/api/meetings/${state.meetingId}/process?${params.toString()}`); let resultAcc = ""; let streamAcc = ""; @@ -808,6 +1066,8 @@ function startMeetingProcess() { if (payload.type === "done") { source.close(); state.processing = false; + patchMeeting(state.meetingId, { has_summary: true }); + renderMeetingStatus(meetingById(state.meetingId)); showResult(payload.data?.result || resultAcc || ""); toast("会议总结完成"); refreshActionButtons(); @@ -840,8 +1100,7 @@ $("#btn-process").addEventListener("click", async () => { } try { - await ensureTemplateGuideBeforeProcess(); - startMeetingProcess(); + await openProcessGuideModal(); } catch (error) { state.processing = false; state.guideBusy = false; @@ -851,6 +1110,106 @@ $("#btn-process").addEventListener("click", async () => { } }); +$("#btn-close-process-guide").addEventListener("click", () => { + hideReparseGuidePrompt(); + closeModal("modal-process-guide"); +}); + +$("#btn-process-guide-edit").addEventListener("click", async () => { + if (state.processing || state.guideBusy) { + return; + } + + if (!state.processGuideEditMode) { + setProcessGuideEditMode(true); + return; + } + + $("#btn-process-guide-edit").disabled = true; + try { + await saveProcessGuideFromModal(); + } catch (error) { + toast(error.message, "err"); + } finally { + $("#btn-process-guide-edit").disabled = false; + } +}); + +$("#btn-process-guide-reparse").addEventListener("click", () => { + if (state.processing || state.guideBusy) { + return; + } + + showReparseGuidePrompt(); +}); + +$("#btn-reparse-guide-cancel").addEventListener("click", () => { + hideReparseGuidePrompt(); +}); + +$("#btn-reparse-guide-confirm").addEventListener("click", async () => { + if (state.processing || state.guideBusy) { + return; + } + + $("#btn-reparse-guide-confirm").disabled = true; + try { + const userNotes = $("#reparse-guide-notes").value || ""; + await reparseTemplateGuideFromModal(userNotes); + hideReparseGuidePrompt(); + } catch (error) { + toast(error.message, "err"); + } finally { + $("#btn-reparse-guide-confirm").disabled = false; + } +}); + +$("#btn-close-reparse-guide").addEventListener("click", () => { + if (state.guideBusy) { + return; + } + closeModal("modal-reparse-guide"); +}); + +$("#btn-reparse-modal-cancel").addEventListener("click", () => { + if (state.guideBusy) { + return; + } + closeModal("modal-reparse-guide"); +}); + +$("#btn-reparse-modal-confirm").addEventListener("click", async () => { + if (state.processing || state.guideBusy) { + return; + } + + try { + await runStandaloneGuideReparseFlow(); + } catch (error) { + toast(error.message, "err"); + } +}); + +$("#btn-process-guide-confirm").addEventListener("click", async () => { + if (!state.meetingId || state.processing || state.guideBusy) { + return; + } + + $("#btn-process-guide-confirm").disabled = true; + try { + if (state.processGuideEditMode) { + await saveProcessGuideFromModal(); + } + const userNotes = $("#process-extra-notes").value || ""; + closeModal("modal-process-guide"); + startMeetingProcess(userNotes); + } catch (error) { + toast(error.message, "err"); + } finally { + $("#btn-process-guide-confirm").disabled = false; + } +}); + $("#btn-toggle-result-edit").addEventListener("click", async () => { if (!state.meetingId || state.processing || state.guideBusy) { return; @@ -868,6 +1227,8 @@ $("#btn-toggle-result-edit").addEventListener("click", async () => { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content }), }); + patchMeeting(state.meetingId, { has_summary: true }); + renderMeetingStatus(meetingById(state.meetingId)); showResult(content); toast("结果已保存"); }); @@ -927,24 +1288,9 @@ $("#btn-reparse-guide").addEventListener("click", async () => { } const templateName = resource.templateName || resource.name; - state.guideBusy = true; - setStatus("right", true, "解析中"); - refreshActionButtons(); + syncTemplateSelection(templateName); - try { - const result = await api(`/api/templates/${encodeURIComponent(templateName)}/guide/reparse`, { - method: "POST", - }); - state.templates = state.templates.map((item) => ( - item.name === templateName ? { ...item, has_guide: true } : item - )); - toast(`说明已更新:${templateName}`); - await openTemplateGuide(result.name); - } finally { - state.guideBusy = false; - setStatus("right", false, "空闲"); - refreshActionButtons(); - } + openStandaloneReparseModal(); }); $("#btn-import").addEventListener("click", () => { @@ -1013,9 +1359,12 @@ $$("[data-close]").forEach((button) => { button.addEventListener("click", () => closeModal(button.dataset.close)); }); -["modal-import", "modal-settings"].forEach((id) => { +["modal-import", "modal-settings", "modal-process-guide", "modal-reparse-guide"].forEach((id) => { document.getElementById(id).addEventListener("click", (event) => { if (event.target.id === id) { + if (id === "modal-reparse-guide" && state.guideBusy) { + return; + } closeModal(id); } }); diff --git a/frontend/assets/styles.css b/frontend/assets/styles.css index b9308db..46b4e8c 100644 --- a/frontend/assets/styles.css +++ b/frontend/assets/styles.css @@ -621,6 +621,7 @@ select.btn { place-items: center; background: rgba(17, 47, 84, 0.28); padding: 20px; + z-index: 9999; } .modal-mask.show { @@ -660,6 +661,161 @@ select.btn { background: #f9fcff; } +.form-field textarea { + width: 100%; + padding: 12px 14px; + border: 1px solid var(--line); + border-radius: 14px; + background: #f9fcff; + resize: vertical; + outline: none; + line-height: 1.65; + color: var(--text); +} + +.form-field textarea:focus, +.form-field input:focus { + border-color: rgba(47, 128, 237, 0.55); + box-shadow: 0 0 0 4px rgba(47, 128, 237, 0.1); +} + +.guide-modal { + width: min(760px, 100%); + padding: 24px; +} + +.guide-modal-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 16px; + margin-bottom: 16px; +} + +.guide-modal-head h3 { + margin: 0; +} + +.guide-modal-head p { + margin: 6px 0 0; + color: var(--muted); + font-size: 13px; +} + +.guide-modal-body { + display: grid; + gap: 14px; + position: relative; +} + +.guide-editor { + min-height: 260px; + font-family: var(--mono); +} + +.guide-editor:not(.is-editing) { + background: #f3f8ff; +} + +.guide-notes { + min-height: 110px; +} + +.guide-inline-prompt { + position: absolute; + inset: 62px 52px 164px; + display: grid; + place-items: center; + background: rgba(238, 245, 255, 0.72); + border-radius: 18px; + z-index: 2; +} + +.guide-inline-prompt-box { + width: min(620px, 100%); + background: rgba(255, 255, 255, 0.98); + border: 1px solid var(--line); + border-radius: 18px; + padding: 18px; + box-shadow: 0 18px 42px rgba(47, 128, 237, 0.16); +} + +.guide-inline-prompt-title { + font-weight: 700; + color: var(--text); +} + +.guide-inline-prompt-subtitle { + margin-top: 6px; + color: var(--muted); + font-size: 13px; +} + +.guide-inline-textarea { + width: 100%; + min-height: 140px; + margin-top: 14px; + padding: 12px 14px; + border: 1px solid var(--line); + border-radius: 14px; + background: #f9fcff; + resize: vertical; + outline: none; + line-height: 1.65; + color: var(--text); +} + +.guide-inline-actions { + margin-top: 14px; + display: flex; + justify-content: flex-end; + gap: 10px; +} + +.reparse-modal { + width: min(720px, 100%); + min-height: 430px; + display: flex; + flex-direction: column; +} + +.reparse-modal-body { + flex: 1 1 auto; + display: flex; + min-height: 0; +} + +.reparse-modal-textarea { + min-height: 190px; + height: 190px; + margin-top: 0; +} + +.reparse-stream-box { + width: 100%; + min-height: 240px; +} + +.reparse-stream-content { + min-height: 180px; + max-height: 180px; +} + +.icon-btn { + width: 38px; + height: 38px; + border: 1px solid var(--line); + border-radius: 12px; + background: rgba(248, 252, 255, 0.98); + color: var(--muted); + cursor: pointer; +} + +.icon-btn:hover { + color: var(--accent-strong); + border-color: rgba(47, 128, 237, 0.35); +} + .toast { position: fixed; top: 22px; diff --git a/frontend/index.html b/frontend/index.html index e20730e..36f7af1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@
确认后开始总结,可先补充一些关注要点
+可为空;会和系统解析模板一起用于重新解析
+