diff --git a/data/results/json/1778233001316/sub_topic.json b/data/results/json/1778233001316/sub_topic.json index 7cf496b..6afcc73 100644 --- a/data/results/json/1778233001316/sub_topic.json +++ b/data/results/json/1778233001316/sub_topic.json @@ -1,44 +1,49 @@ { "sub_topics": [ { - "title": "宽带安装维护与核心运营指标通报", - "time_interval": "00:00:01 - 00:03:51", - "overview": "会议通报了宽带安装与上门服务的进度,受天气影响上周上门量达580,累计进度滞后于3000户目标。重点分析了弱光(0.51)、三代终端(年度目标5.5)、转化率(87.35%)及退单率(6.53%)等关键指标,指出退单主要受用户原因、覆盖不足及天气影响。会议建议将阶段性目标纳入运维考核,并优化审核流程以降低退单率。" + "title": "宽带装维进度与关键指标汇报", + "time_interval": "00:00:01 - 00:02:06", + "overview": "会议首先汇报了宽带装维上门量与安装进度,受天气影响当周上门量约580户,累计进度35%,距离3000户目标仍存差距。同时通报弱光指数为0.51(目标0.5)、三代终端目标压降至5.5,FPTR指标已达标,主动过境指标相对靠后。" }, { - "title": "专线护航、网络质量优化与基站攻坚进展", + "title": "九零工程转化与退单率分析", + "time_interval": "00:02:07 - 00:03:51", + "overview": "针对九零工程转化率(当前87.35%,目标90%)与退单率(当前6.53%,目标低于7.5%)进行拆解,指出天气、用户原因及覆盖问题是影响转化与退单的主要因素。会议提出需优化B to C协调流程,加强退单审核意识,并将该指标纳入综合运维考核。" + }, + { + "title": "专线护航、PCDN治理与基站运维", "time_interval": "00:03:53 - 00:08:04", - "overview": "针对PCDN专线恶化问题,已上报市公司分析学校出口IP并协同限速,同时推进5G基站参数调整试点以解决漏话问题。专线护航方面,118条专线中存量73条已完成56条巡检,剩余17条确保月底全量完成;上周共收报障33件,其中9件为网络相关(主要为光缆问题)。超频基站攻坚发现3处故障,已分别通过修复铁塔开关电源和更换传输光纤恢复。" + "overview": "重点汇报118条专线巡检进度(已完成56条,剩余17条月底清零),并指出PCDN在学校端持续恶化,正协同学校进行IP限速分析。同时通报超频基站故障已恢复,当周收到报障33件(网络相关9件,以光缆问题为主)。" }, { - "title": "二级站点拆除计划与网络评估体系分解", - "time_interval": "00:08:07 - 00:09:35", - "overview": "金沙管家二级站点拆除工作推进顺利,剩余75站预计于4月15日前完成全量拆除、下电及电费调整。工程下层项目整体正常开展,综合网络运维评估体系已内部收悉,计划下周进行指标分解与目标制定。东环网络质量保持良好水平,并在全市范围内进行了经验交流。" + "title": "二级基站拆除与网络质量评估体系落地", + "time_interval": "00:08:07 - 00:10:00", + "overview": "通报二级基站拆除进展,剩余121站中已拆除46站,预计4月15日前完成剩余75站拆除及电费调整。会议提及网络质量综合评估体系已内部下发,下周将分解制定目标,并同步推进花果山项目与微托无人机验收等2025年剩余工作。" }, { - "title": "综合部专项工作推进与后勤设施改造", - "time_interval": "00:09:46 - 00:12:39", - "overview": "综合部汇报了25年剩余2项重点工作的推进情况,上周已完成建委框架清单整理、投资计划评估及收入地图提交等5项工作。后勤方面,因内部打印机报价偏差过大,正洽谈外部打印店以保障招投标需求;同时推进食堂改造(预算约29.8万)及下半年引入自饮机以节约成本。工会经费面临压减,需以更优方式落实软性工程改造。" + "title": "综合部重点工作与后勤保障安排", + "time_interval": "00:10:01 - 00:14:53", + "overview": "汇报上周完成5项工作(含框架清单、投资评估、收入地图等)及7项推进中工作,重点说明因内外部打印价格差异大,拟采用外派打印保障招投标。同时通报工会经费压减背景下,推进食堂改造(约29.8万元)与自饮机引入,并筹备第四届体育文化节。" }, { - "title": "年度工会活动规划与第四届体育文化节筹备", - "time_interval": "00:12:40 - 00:18:54", - "overview": "会议规划了全年六场大型体育活动,并重点部署了5月底举办的第四届体育文化节,主题为“河川佳节,穿越巅峰”。因两名主力选手肌肉拉伤无法参赛,现需各部门协调补充人员,计划从一线生产部门及AI运输中心抽调共计9人组成25人方阵。参会人员将获得绩效加分,需于今日确定名单以便统一采购服装并开展排练。" + "title": "主题教育开展与招待费管控通报", + "time_interval": "00:14:54 - 00:22:49", + "overview": "通报主题教育第一期简报发布及基层党组织学习情况,完成年度招待费信息公开报备。分析2025年招待费超年初预算14%,其中综合部去年超支107%,2026年已调整预算。会议要求政企部统筹对外招待,避免过度依赖单一客户经理,并加快历史费用报销。" }, { - "title": "主题教育落实、招待费管控与区级评优申报", - "time_interval": "00:18:55 - 00:26:52", - "overview": "主题教育首期简报已发布,基层党组织学习工作已按期完成;2025年招待费超年初预算14%,其中综合部历史超支明显,2026年已调整预算并强调对外招待需统筹分配。会议讨论了申报“河川区担当作为先进集体/个人”的可行性,指出该评选竞争激烈且需推荐,建议提前核实资格避免资源浪费。" + "title": "区级评优申报推进与商客市场经营汇报", + "time_interval": "00:22:50 - 00:31:43", + "overview": "跟进河川区担当作为评优申报流程,明确需对接区委办与人力社保局确认资格,截止3月31日。商客市场方面,2月营收88.5万(环比增1万),价值拓展完成115,但个别商客经理周基础业务仅2笔,需加强签约落实与价值拓展。" }, { - "title": "商客市场经营分析与二期项目拆迁进展", - "time_interval": "00:26:55 - 00:32:04", - "overview": "商客市场2月收入达88.5万元,环比增长1万元,城北和南溪改增幅超2万元,但价值拓展仅完成115,两名商客经理基础业务量偏低。二期项目拆迁方面,已完成1145户,重点通过二次融合套餐将低价值合约提升至50元档位;本周开展5场社区及单位营销,但签约转化未达预期,需加强后续落实。" + "title": "考核指标督办与市场满意度整改要求", + "time_interval": "00:31:44 - 00:42:25", + "overview": "强调工作回复需量化且日清日结,要求下周内提交“上课”指标具体保障措施。针对满意度测评结果批评相关部门思想松懈,要求市场部本周内汇报招聘、农村渠道及营销方案进度,并严格落实“80/20两率”3月底目标,每日微信报送日报。" }, { - "title": "工作执行力整顿与满意度/KPI考核部署", - "time_interval": "00:32:05 - 00:46:16", - "overview": "领导严厉批评部分工作拖延及缺乏具体保障措施的现象,要求养成“日清日结”习惯并量化汇报结果。针对满意度测评,指出政企部前期工作松懈导致指标落后,要求市场部立即制定挽单方案并按市公司规范执行。会议明确今年KPI将挂钩工信部有责投诉及离网率,要求各部门提前摸排考核风险,强化执行力以确保年度考核达标。" + "title": "运动会筹备与年度考核工作部署", + "time_interval": "00:42:26 - 00:46:16", + "overview": "确认体育文化节方阵人员调配与排练安排,强调执行力与结果导向。领导着重部署四季度年度考核工作,要求各部门提前摸排考核规则(如阿普提升等指标),主动与市公司沟通对齐,避免起跑落后,统筹资源确保年度考核公平达标。" } ] } \ No newline at end of file diff --git a/frontend/assets/app.js b/frontend/assets/app.js index f2c8534..2670124 100644 --- a/frontend/assets/app.js +++ b/frontend/assets/app.js @@ -91,18 +91,10 @@ function meetingById(meetingId) { return state.meetings.find((item) => item.id === meetingId) || null; } -function templateMetaByName(name) { - return state.templates.find((item) => item.name === name) || null; -} - function isMarkdownFile(name = "") { return name.toLowerCase().endsWith(".md"); } -function templateNameFromGuideName(name) { - return name; -} - function renderMeetingStatus(meeting) { const name = $("#sidebar-meeting-name"); const meta = $("#sidebar-meeting-meta"); @@ -132,16 +124,35 @@ function renderMeetingStatus(meeting) { topicsBadge.className = meeting.has_topics ? "badge" : "badge muted"; } +function setRightStatus(isBusy, text = "空闲") { + const light = $("#side-status-light"); + const textEl = $("#side-status-text"); + light.classList.toggle("idle", !isBusy); + light.classList.toggle("busy", isBusy); + textEl.textContent = text; +} + +function resetRightStream() { + $("#side-status-stream").textContent = ""; +} + +function updateRightStream(text) { + const lines = text.replace(/\r\n/g, "\n").split("\n").filter((line) => line.trim() !== ""); + $("#side-status-stream").textContent = lines.slice(-2).join("\n"); +} + function resetProcessingStream() { $("#stream-box").style.display = "none"; $("#stream-title").textContent = ""; $("#stream-content").textContent = ""; + setRightStatus(false, "空闲"); + resetRightStream(); } function showResultEmpty() { state.resultEditMode = false; $("#btn-toggle-result-edit").disabled = true; - $("#btn-toggle-result-edit").textContent = "编辑结果"; + $("#btn-toggle-result-edit").textContent = "编辑"; $("#result-editor").style.display = "none"; $("#result-md").style.display = "none"; $("#processing-indicator").hidden = true; @@ -152,7 +163,7 @@ function setResultEditMode(editMode) { state.resultEditMode = editMode; $("#result-editor").style.display = editMode ? "block" : "none"; $("#result-md").style.display = editMode ? "none" : "block"; - $("#btn-toggle-result-edit").textContent = editMode ? "保存结果" : "编辑结果"; + $("#btn-toggle-result-edit").textContent = editMode ? "保存" : "编辑"; } function showResult(markdown) { @@ -374,13 +385,13 @@ function updateGuideButton(resource) { const button = $("#btn-reparse-guide"); if (!resource || (resource.type !== "template" && resource.type !== "template-guide")) { button.disabled = true; - button.textContent = "生成解析说明"; + button.textContent = "解析"; return; } const hasGuide = resource.type === "template-guide" || Boolean(resource.hasGuide); button.disabled = false; - button.textContent = hasGuide ? "重新生成说明" : "生成解析说明"; + button.textContent = hasGuide ? "重解析" : "解析"; } function applyRightResource(resource) { @@ -388,7 +399,7 @@ function applyRightResource(resource) { $("#editor-resource-label").textContent = `当前资源:${resource.label}`; $("#side-editor").value = resource.content || ""; $("#btn-toggle-side-edit").disabled = !resource.editable; - $("#btn-toggle-side-edit").textContent = resource.editable ? "编辑资源" : "只读资源"; + $("#btn-toggle-side-edit").textContent = resource.editable ? "编辑" : "只读"; updateGuideButton(resource); state.rightEditMode = false; renderSidePreview(resource); @@ -406,7 +417,7 @@ function setRightEditMode(editMode) { return; } - $("#btn-toggle-side-edit").textContent = editMode ? "保存资源" : "编辑资源"; + $("#btn-toggle-side-edit").textContent = editMode ? "保存" : "编辑"; if (editMode) { $("#side-preview").style.display = "none"; $("#side-plain-preview").style.display = "none"; @@ -439,13 +450,12 @@ async function openTemplate(name, treeKey = `file:templates/${name}`) { } async function openTemplateGuide(name, treeKey = `file:template_guides/${name}`) { - const templateName = templateNameFromGuideName(name); - const data = await api(`/api/templates/${encodeURIComponent(templateName)}/guide`); + const data = await api(`/api/templates/${encodeURIComponent(name)}/guide`); await openRightResource({ type: "template-guide", name, - templateName, - label: `模板说明 / ${templateName}`, + templateName: name, + label: `模板说明 / ${name}`, content: data.content, hasGuide: true, editable: true, @@ -717,10 +727,13 @@ $("#btn-process").addEventListener("click", () => { state.processing = true; $("#btn-process").disabled = true; + $("#btn-process").textContent = "处理中"; showProcessingView(); $("#stream-box").style.display = "block"; $("#stream-title").textContent = "第一阶段:结构化主题..."; $("#stream-content").textContent = ""; + setRightStatus(true, "工作中"); + resetRightStream(); const source = new EventSource( `/api/meetings/${state.meetingId}/process?template_name=${encodeURIComponent(state.templateName)}`, @@ -739,13 +752,13 @@ $("#btn-process").addEventListener("click", () => { if (payload.type === "status") { if (payload.data === "preprocessing") { $("#stream-title").textContent = "第一阶段:结构化主题..."; - streamAcc = ""; } else if (payload.data === "preprocessing_done") { $("#stream-title").textContent = "主题提取完成,开始生成会议总结..."; } else if (payload.data === "summarizing") { $("#stream-title").textContent = "第二阶段:生成会议总结..."; streamAcc = ""; $("#stream-content").textContent = ""; + resetRightStream(); } return; } @@ -754,6 +767,7 @@ $("#btn-process").addEventListener("click", () => { const { data } = payload; streamAcc += data.text || ""; $("#stream-content").textContent = streamAcc.replace(/\r\n/g, "\n").split("\n").slice(-4).join("\n"); + updateRightStream(streamAcc); if (data.stage === 2 && data.chunk_type === "content") { resultAcc += data.text || ""; } @@ -764,6 +778,7 @@ $("#btn-process").addEventListener("click", () => { source.close(); state.processing = false; $("#btn-process").disabled = false; + $("#btn-process").textContent = "处理"; showResult(payload.data?.result || resultAcc || ""); await refresh(); toast("会议处理完成"); @@ -774,6 +789,7 @@ $("#btn-process").addEventListener("click", () => { source.close(); state.processing = false; $("#btn-process").disabled = false; + $("#btn-process").textContent = "处理"; resetProcessingStream(); $("#processing-indicator").hidden = true; toast(`处理失败:${payload.data}`, "err"); @@ -784,6 +800,7 @@ $("#btn-process").addEventListener("click", () => { source.close(); state.processing = false; $("#btn-process").disabled = false; + $("#btn-process").textContent = "处理"; resetProcessingStream(); $("#processing-indicator").hidden = true; toast("处理连接中断", "err"); @@ -801,14 +818,15 @@ $("#btn-toggle-result-edit").addEventListener("click", async () => { } const content = $("#result-editor").value; + $("#btn-toggle-result-edit").disabled = true; await api(`/api/meetings/${state.meetingId}/summary`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content }), }); showResult(content); - await refresh(); - toast("会议结果已保存"); + $("#btn-toggle-result-edit").disabled = false; + toast("结果已保存"); }); $("#btn-toggle-side-edit").addEventListener("click", async () => { @@ -823,6 +841,8 @@ $("#btn-toggle-side-edit").addEventListener("click", async () => { } const content = $("#side-editor").value; + $("#btn-toggle-side-edit").disabled = true; + if (resource.type === "template") { await api(`/api/templates/${encodeURIComponent(resource.name)}`, { method: "PUT", @@ -845,6 +865,7 @@ $("#btn-toggle-side-edit").addEventListener("click", async () => { resource.content = content; setRightEditMode(false); + $("#btn-toggle-side-edit").disabled = false; toast("资源已保存"); }); @@ -853,13 +874,6 @@ $("#tpl-select").addEventListener("change", async (event) => { await openTemplate(event.target.value); }); -$("#btn-open-template").addEventListener("click", async () => { - if (!state.templateName) { - return; - } - await openTemplate(state.templateName); -}); - $("#btn-reparse-guide").addEventListener("click", async () => { const resource = state.rightResource; if (!resource || (resource.type !== "template" && resource.type !== "template-guide")) { @@ -867,10 +881,17 @@ $("#btn-reparse-guide").addEventListener("click", async () => { } const templateName = resource.templateName || resource.name; + $("#btn-reparse-guide").disabled = true; + setRightStatus(true, "工作中"); + resetRightStream(); + const result = await api(`/api/templates/${encodeURIComponent(templateName)}/guide/reparse`, { method: "POST", }); - toast(`模板说明已重新解析:${templateName}`); + + setRightStatus(false, "空闲"); + $("#btn-reparse-guide").disabled = false; + toast(`说明已更新:${templateName}`); await refresh(); await openTemplateGuide(result.name); }); @@ -957,6 +978,8 @@ document.addEventListener("DOMContentLoaded", async () => { applySavedLayout(); initResize("gutter-1", "sidebar", "result-panel"); initResize("gutter-2", "result-panel", "template-panel"); + setRightStatus(false, "空闲"); + resetRightStream(); try { await refresh(); diff --git a/frontend/assets/styles.css b/frontend/assets/styles.css index 6077ba5..3327b7e 100644 --- a/frontend/assets/styles.css +++ b/frontend/assets/styles.css @@ -56,6 +56,64 @@ textarea { font-size: 13px; } +.side-status { + margin-top: 6px; + display: grid; + gap: 4px; +} + +.side-status-row { + display: flex; + align-items: center; + gap: 8px; +} + +.side-status-label, +.side-status-meta { + color: var(--muted); + font-size: 13px; +} + +.side-status-text { + font-size: 13px; + color: var(--text); +} + +.status-light { + width: 10px; + height: 10px; + border-radius: 999px; + display: inline-block; + flex: 0 0 auto; +} + +.status-light.idle { + background: #2db36c; + box-shadow: 0 0 0 4px rgba(45, 179, 108, 0.14); +} + +.status-light.busy { + width: 52px; + height: 10px; + border-radius: 999px; + background: + linear-gradient(90deg, rgba(47, 128, 237, 0.18), rgba(47, 128, 237, 0.55), rgba(47, 128, 237, 0.18)); + background-size: 200% 100%; + box-shadow: inset 0 0 0 1px rgba(47, 128, 237, 0.12); + animation: status-marquee 1.2s linear infinite; +} + +.side-status-stream { + margin: 2px 0 0; + min-height: 2.9em; + max-width: 100%; + color: #557cad; + white-space: pre-wrap; + line-height: 1.45; + font-size: 12px; + font-family: var(--mono); +} + .app-shell { height: var(--app-h); padding: 24px; @@ -540,6 +598,15 @@ select.btn { padding: 18px 20px; } +@keyframes status-marquee { + from { + background-position: 200% 0; + } + to { + background-position: 0 0; + } +} + #result-md, #side-preview, #side-plain-preview, diff --git a/frontend/index.html b/frontend/index.html index a710ac7..c18a94f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -47,8 +47,8 @@ 未选择会议
- - + +
@@ -73,14 +73,21 @@
右侧编辑区 - 当前资源:模板 +
+
+ 当前状态 + + 空闲 +
+
当前资源:模板
+

+            
- - - + +
diff --git a/template_guides/template2.md b/template_guides/template2.md index 38c16b0..9be938e 100644 --- a/template_guides/template2.md +++ b/template_guides/template2.md @@ -1,7 +1,9 @@ -- 保持顶层框架(主标题、核心内容模块)固定,议题数量及中层及以下标题、子项、列表按实际内容动态增删,不强制匹配示例条目数。 -- 模板中的 `X`、`XX`、`XXX` 等占位符需优先替换为真实信息;若会议材料未提供对应内容,则原样保留占位符,严禁编造或强行替换。 -- 无事实依据支撑的空白小节、示例问答、决策或行动项必须直接省略,严禁输出“无相关内容”等凑数文字。 -- 领导强调/部署类内容需统一采用“发言人/负责人 -> 分类加粗项 -> 要点列表”的层级格式,且要点必须提炼为精简词组,避免长句。 -- 部门汇报类内容需整合为一段连贯的总结性文字,按实际汇报顺序提取关键信息与数据,不展开为多级列表。 -- 严格保留 Markdown 标题层级(`#`、`##` 等)与段落分隔符(`---`)的结构意义,确保输出排版层级清晰。 -- 输出时自动剔除模板自带的“使用说明”“提示语”等元数据,仅生成最终会议纪要正文。 \ No newline at end of file +- 严格保留模板的Markdown标题层级结构(`#`→`##`→`###`→`####`),不得跨级或打乱顺序。 +- 模板头部的基础信息字段(议题、时间、地点、主持人、参加人)必须保留,其中的 `X`、`XX`、`XXX` 及示例年份/公司名称需替换为真实会议信息。 +- “议程”部分保留固定条目结构,内容替换为实际讨论顺序,若无议程变动可维持原结构。 +- “会议内容”下的部门汇报部分需保留进度通报与数据跟踪的句式结构,替换其中的部门名称与量化数据占位符,无实际汇报内容时省略该小节。 +- “部署强调”部分必须保留 `#### [发言人/领导]强调:` 的标题格式,严禁保留示例中的“X总”。 +- 严格保留领导发言下的列表嵌套格式:使用数字编号(`1.`)引导业务方向,方向名称加粗(`**XX方面:**`),其下使用短横线(`- `)缩进列出具体的发言要点或行动项。 +- 所有占位符(如 `XXX`)及模板示例文本在生成时必须替换为真实会议内容,严禁原样输出占位或演示文字。 +- “部署强调”板块数量及每个领导下的业务方向子项数量应随实际会议内容动态增减,无依据支撑的空白项、示例分组必须省略,不强行补齐。 +- 保留各主要章节间的 `---` 分隔线,用于清晰划分汇报环节与领导部署区块。 \ No newline at end of file