优化总结处理逻辑

new_test
Bifang 2026-05-11 10:00:40 +08:00
parent 4b9960a6bf
commit c8cdd3325c
9 changed files with 697 additions and 100 deletions

View File

@ -1,3 +1,3 @@
{
"active_meeting_id": "1778233001316"
"active_meeting_id": "1778221747643"
}

View File

@ -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”指标并每日报送日报。最后强调全员执行力提前摸排年度考核影响因素确保在公平竞争中取得改善。"
}
]
}

View File

@ -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. **年度考核与执行力建设:**
- 提前摸排四公司年度考核指标变化,主动了解可能影响考核的病毒、卡位点或政策调整,提前沟通争取有利条件,避免被动。
- 严厉批评“知道怎么做却不去做”的作风,要求各级管理人员及一线人员强化执行力,确保年度考核起跑线公平,通过努力改善成绩。
4. **综合管理与作风整顿:**
- 强化全员执行力,杜绝“知道怎么做却不去做”的作风,各项工作需明确措施、确保成效。
- 政企接待费用需统筹分摊,避免个别客户经理集中招待导致预算超支。
- 提前摸排年度考核影响因素,特别是集团相关考核项,确保在公平竞争中取得改善,避免起跑线落后。

View File

@ -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);
}
});

View File

@ -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;

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meeting Summary</title>
<link rel="stylesheet" href="/assets/styles.css">
<link rel="stylesheet" href="/assets/styles.css?v=20260511e">
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/13.0.3/marked.min.js"></script>
</head>
<body>
@ -79,7 +79,7 @@
<section class="panel" id="template-panel">
<div class="panel-header">
<div class="panel-heading">
<span>文档编辑</span>
<span>右侧编辑区</span>
<div class="status-block">
<div class="status-row">
<span class="status-label">当前状态</span>
@ -105,6 +105,69 @@
</main>
</div>
<div class="modal-mask" id="modal-process-guide">
<div class="modal-box guide-modal">
<div class="guide-modal-head">
<div>
<h3 id="guide-modal-title">模板使用说明</h3>
<p id="guide-modal-subtitle">确认后开始总结,可先补充一些关注要点</p>
</div>
<button class="icon-btn" id="btn-close-process-guide" aria-label="关闭">×</button>
</div>
<div class="guide-modal-body">
<label class="form-field">
<span>模板说明</span>
<textarea id="process-guide-editor" class="guide-editor" spellcheck="false"></textarea>
</label>
<div class="guide-inline-prompt" id="reparse-guide-prompt" hidden>
<div class="guide-inline-prompt-box">
<div class="guide-inline-prompt-title">&#27169;&#26495;&#35828;&#26126;&#34917;&#20805;</div>
<div class="guide-inline-prompt-subtitle">&#21487;&#20026;&#31354;&#65307;&#20250;&#21644;&#31995;&#32479;&#35299;&#26512;&#27169;&#26495;&#19968;&#36215;&#29992;&#20110;&#37325;&#26032;&#35299;&#26512;</div>
<textarea id="reparse-guide-notes" class="guide-inline-textarea" spellcheck="false" placeholder="&#20363;&#22914;&#65306;&#24378;&#35843;&#21738;&#20123;&#32467;&#26500;&#24517;&#39035;&#20445;&#30041;&#12289;&#21738;&#20123;&#27573;&#33853;&#38656;&#35201;&#37325;&#28857;&#35299;&#37322;&#12289;&#21738;&#20123;&#25514;&#36766;&#38656;&#35201;&#36991;&#20813;"></textarea>
<div class="guide-inline-actions">
<button class="btn" id="btn-reparse-guide-cancel">&#21462;&#28040;</button>
<button class="btn primary" id="btn-reparse-guide-confirm">&#30830;&#35748;&#37325;&#35299;&#26512;</button>
</div>
</div>
</div>
<label class="form-field">
<span>补充说明(可空)</span>
<textarea id="process-extra-notes" class="guide-notes" spellcheck="false" placeholder="例如:强调哪些结构要保留、哪些内容要重点解释、哪些措辞需要避免"></textarea>
</label>
</div>
<div class="modal-actions">
<button class="btn" id="btn-process-guide-edit">编辑</button>
<button class="btn" id="btn-process-guide-reparse">重解析</button>
<button class="btn primary" id="btn-process-guide-confirm">确认</button>
</div>
</div>
</div>
<div class="modal-mask" id="modal-reparse-guide">
<div class="modal-box reparse-modal">
<div class="guide-modal-head">
<div>
<h3 id="reparse-modal-title">模板说明补充</h3>
<p id="reparse-modal-subtitle">可为空;会和系统解析模板一起用于重新解析</p>
</div>
<button class="icon-btn" id="btn-close-reparse-guide" aria-label="关闭">×</button>
</div>
<div class="reparse-modal-body" id="reparse-modal-form">
<textarea id="reparse-modal-notes" class="guide-inline-textarea reparse-modal-textarea" spellcheck="false" placeholder="例如:强调哪些结构必须保留、哪些段落需要重点解释、哪些措辞需要避免"></textarea>
</div>
<div class="reparse-modal-body" id="reparse-modal-progress" hidden>
<div class="stream-box reparse-stream-box">
<div class="stream-title" id="reparse-stream-title">正在重新解析模板说明...</div>
<pre class="stream-content reparse-stream-content" id="reparse-stream-content"></pre>
</div>
</div>
<div class="modal-actions">
<button class="btn" id="btn-reparse-modal-cancel">取消</button>
<button class="btn primary" id="btn-reparse-modal-confirm">确认重解析</button>
</div>
</div>
</div>
<div class="modal-mask" id="modal-import">
<div class="modal-box">
<h3>导入会议转录</h3>
@ -146,6 +209,6 @@
</div>
<div class="toast" id="toast"></div>
<script type="module" src="/assets/app.js"></script>
<script type="module" src="/assets/app.js?v=20260511e"></script>
</body>
</html>

View File

@ -1,8 +1,9 @@
- 严格保留模板的Markdown标题层级与宏观结构# 会议记录 -> ## 会议内容 -> ### 各部门汇报/部署强调),不得增删或调整主标题顺序。
- 模板中的“X”、“XX”、“XXX”及具体年份、部门名均为占位示例生成时必须替换为会议真实数据与名称若实际会议未涉及某字段则直接省略该条目不强行补齐。
- “各部门汇报”小节需按实际汇报顺序提炼核心内容与进度数据,替换原长句模板;若无汇报内容,可省略该小节。
- “部署强调”部分采用“#### [领导职务/姓名]强调”作为独立标题块多个领导发言时按实际顺序并列生成替换示例中的“X总”。
- 领导强调下按实际业务领域划分子项(格式为“数字. **领域:**”),子项数量与领域名称严格按实际讲话内容动态增减,不得照搬模板示例的固定领域列表。
- 各领域下的具体指示需使用无序列表(`- `逐条列出替换示例“XXX”保持原有缩进层级确保要点清晰。
- 所有仅用于演示结构的说明性文字(如“按议程现场按顺序做汇报”“详见汇报材料”等)必须删除,仅输出实际会议提炼的实质内容。
- 严格遵循模板原有的排版分隔逻辑,各主要模块间保留“---”分隔线,维持整体版式节奏。
- 严格保留模板的Markdown标题层级体系# 总标题 > ## 内容主体 > ### 一级板块 > #### 发言人/细分板块),后续生成时不得随意更改层级关系。
- 顶部元数据议题、时间、地点、主持人、参加人必须基于实际会议信息填充所有占位符X、XX、XXX等需替换为真实数据严禁原样输出。
- “议程”区块需保留标题,其下列表项数量与内容严格按实际会议流程动态调整,无实际议程时省略该区块。
- “汇报通报”区块需替换具体部门名称与量化数据,若会议记录中无汇报环节,则整体省略该小节,不强行补齐。
- “领导部署/强调”区块需保留结构框架,将占位姓名替换为实际发言人;该区块下的发言人数量、业务分类标题(**XX方面**)及下级详情列表需与真实讲话内容严格对应,动态增减,无依据时直接省略。
- 所有示例性文字、演示性标题及占位内容必须在生成前全部替换为实际业务内容,禁止保留模板中的示例字符。
- 使用水平分割线(---)作为区块分隔符,依次分隔头部信息、议程、汇报板块与部署板块,保持文档结构清晰。
- 严格保留格式规范领导发言使用有序列表1. 2. 3.),业务分类使用加粗文本(**分类名称:**),具体指示或要点使用无序列表(- ),有内容时必须完整套用此层级格式。
- 遵循“无依据不填充”原则,模板中未被实际会议内容支撑的空白项、占位项或示例项必须直接删除,不得虚构或强行对齐模板结构。

View File

@ -135,7 +135,12 @@ def _collect_llm_content(client, model, system_prompt: str, user_prompt: str, ma
return "".join(content).strip()
def _parse_template_guide(template_name: str, template_content: str, cfg: dict | None = None) -> str:
def _parse_template_guide(
template_name: str,
template_content: str,
cfg: dict | None = None,
user_notes: str = "",
) -> str:
prompt = load_prompt("templatet_parser", "zh")
config = cfg or _load_config()
client = _get_llm_client(config)
@ -144,10 +149,18 @@ def _parse_template_guide(template_name: str, template_content: str, cfg: dict |
template_name=template_name,
template_content=template_content,
)
if user_notes.strip():
user_prompt += f"\n\n用户补充的模板使用说明(优先参考,可为空):\n{user_notes.strip()}"
return _collect_llm_content(client, config["model_name"], system_prompt, user_prompt)
def _ensure_template_guide(template_name: str, *, force: bool = False, cfg: dict | None = None) -> str:
def _ensure_template_guide(
template_name: str,
*,
force: bool = False,
cfg: dict | None = None,
user_notes: str = "",
) -> str:
template_path = _resolve_child(TEMPLATE_DIR, template_name)
if not template_path.exists():
raise HTTPException(404, f"Template not found: {template_name}")
@ -162,6 +175,7 @@ def _ensure_template_guide(template_name: str, *, force: bool = False, cfg: dict
template_name=template_name,
template_content=template_path.read_text(encoding="utf-8"),
cfg=cfg,
user_notes=user_notes,
)
guide_path.write_text(guide_content, encoding="utf-8")
return guide_content
@ -462,8 +476,8 @@ async def save_template_guide(name: str, payload: dict):
@app.post("/api/templates/{name}/guide/reparse")
async def reparse_template_guide(name: str):
content = _ensure_template_guide(name, force=True)
async def reparse_template_guide(name: str, user_notes: str = ""):
content = _ensure_template_guide(name, force=True, user_notes=user_notes)
return {"name": name, "content": content}
@ -517,7 +531,12 @@ async def save_meeting_summary(meeting_id: str, payload: dict):
@app.get("/api/meetings/{meeting_id}/process")
async def process_meeting(meeting_id: str, request: Request, template_name: str = "template1.md"):
async def process_meeting(
meeting_id: str,
request: Request,
template_name: str = "template1.md",
user_notes: str = "",
):
meeting_dir = DATA_DIR / meeting_id
if not meeting_dir.exists():
raise HTTPException(404, "Meeting not found")
@ -578,6 +597,8 @@ async def process_meeting(meeting_id: str, request: Request, template_name: str
article=transcript,
sub_topices=sub_topics,
)
if user_notes.strip():
user_prompt += f"\n\n用户补充要点(优先参考,可为空):\n{user_notes.strip()}"
result = ""
for chunk_type, chunk_content in _llm_stream(client, model_name, system_prompt, user_prompt):