优化总结处理逻辑
parent
4b9960a6bf
commit
c8cdd3325c
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"active_meeting_id": "1778233001316"
|
"active_meeting_id": "1778221747643"
|
||||||
}
|
}
|
||||||
|
|
@ -1,59 +1,44 @@
|
||||||
{
|
{
|
||||||
"sub_topics": [
|
"sub_topics": [
|
||||||
{
|
{
|
||||||
"title": "宽带业务上门量、转化率与退单管控",
|
"title": "宽带装维指标与业务转化率分析",
|
||||||
"time_interval": "00:00:01 - 00:03:51",
|
"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专线质量恶化与学校出口限速机制",
|
"title": "PCDN专线问题与网络质量巡检通报",
|
||||||
"time_interval": "00:03:53 - 00:05:31",
|
"time_interval": "00:03:53 - 00:07:56",
|
||||||
"overview": "会议重点讨论了PCDN专线流量持续恶化问题,主要集中于两所学校开学后的出口带宽占用。目前市公司已介入分析涉及学校的IP地址,正协同校方建立发现超限直接限速的管控机制。该问题因学校互联网带宽免费导致管控难度大,需加快限速策略落地以保障网络质量。"
|
"overview": "针对PCDN专线恶化问题,明确主要集中于学校出口带宽,已上报市公司分析IP并计划协同限速。网络质量方面,超频基站故障已及时处理,专线护航118条存量及新建线路巡检按计划推进,当周收到33件报障且光缆问题为主。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "无线网络质量优化与专线护航巡检进展",
|
"title": "故障通报、二级基站拆除与网络评估体系",
|
||||||
"time_interval": "00:05:33 - 00:07:56",
|
"time_interval": "00:07:57 - 00:10:00",
|
||||||
"overview": "网络质量方面,物业点检测因天线问题延期至4月初,唱厅工单需待智能体质检结束,5G基站正通过参数调整试点解决漏话问题。超频站点故障已及时恢复,旁站故障率低。专线护航巡检方面,118条存量专线已巡检56条,未验收工程45条全量巡检,预计月底可按时完成全量巡检任务。"
|
"overview": "通报当周网络故障情况,东环指标保持良好。重点汇报二级基站拆除进度,剩余75站预计两周内完成下电及服务费调整,以配合4月15日前完成专项调整。此外,已收到网络质量综合评估体系文件,下周将分解制定目标。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "当周故障通报与二级站拆除及网络评估体系",
|
"title": "综合部重点工作进展与资产优化推进",
|
||||||
"time_interval": "00:07:57 - 00:09:35",
|
"time_interval": "00:10:01 - 00:12:39",
|
||||||
"overview": "当周共收报障33件,其中9件网络相关故障以光缆问题为主。重点推进金沙管家二级站拆除工作,剩余75站预计两周内完成下电及电费调整,目标4月15日前全量完成。此外,内部已收到网络质量综合评估体系文件,下周将分解制定具体目标。"
|
"overview": "汇报建委框架清单、投资计划评估及终端占比措施等已完成事项,并就打印设备采购价格差异问题制定外派保障方案。资产优化方面形成清单周报每周沟通推进,数据中心包装、无人机需求等7项工作正按计划落实。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "综合部重点工作推进与跨部门协同事项",
|
"title": "工会经费公示、后勤改造与运动会筹备调度",
|
||||||
"time_interval": "00:09:46 - 00:12:39",
|
"time_interval": "00:12:40 - 00:19:34",
|
||||||
"overview": "综合部汇报了25年剩余两项工作进展,建委框架清单与投资计划已汇报完成,终端占比问题已制定五项措施。会议同步推进数据中心包装、基建宣传、渠道产能评估及资产优化等7项工作,部分事项因兼职或流程原因仍在梳理方案,预计本周内完成专题汇报。"
|
"overview": "通报工会经费压减情况,公示食堂改造(约29.8万)及下半年引入自饮机方案以节约成本。重点筹备周五第四届体育文化节,因主力选手肌肉拉伤缺席,需各部门抽调人员补齐方阵,并明确参与激励政策。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "工会经费管理、食堂改造与员工文体活动筹备",
|
"title": "主题教育开展、招待费管控与区级评优申报评估",
|
||||||
"time_interval": "00:12:40 - 00:18:54",
|
"time_interval": "00:19:36 - 00:27:28",
|
||||||
"overview": "工会经费因上级压减需严考严用,已公示并上报软性工程,食堂改造预算约29.8万元正进行审计与方案优化。同时规划下半年引入自饮机节约成本,并全年组织六场大型体育活动。针对周五第四届体育文化节,需各部门抽调人员补充方阵缺额,确定人员后将统一购衣并于周中排练。"
|
"overview": "通报主题教育首期简报及基层党组织学习情况,公示2025年招待费使用超预算14%(综合部超107%),强调政企接待需统筹分摊。同时评估河川区“担当作为”评优申报可行性,因缺乏明确推荐机制及竞争激烈,建议暂缓盲目投入。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "党建主题教育、招待费管控与区级荣誉申报",
|
"title": "三期拆迁增收、商客市场拓展与管理作风整顿",
|
||||||
"time_interval": "00:18:55 - 00:27:30",
|
"time_interval": "00:27:29 - 00:35:21",
|
||||||
"overview": "主题教育一期简报已发布,基层党组织学习已按期完成,招待费已按年度向管理层公示并报备纪检。25年招待费整体超预算14%,综合部超支严重,政企部需统筹对外招待避免客户经理个人承担。此外,正对接河川区担当作为集体/个人评选,因评选偏向政府机构且流程尚不明确,建议先摸清内部意向再决定是否申报。"
|
"overview": "汇报三期拆迁工作聚焦二次光改与价值提升,本周开展5场社区与单位营销但签约效果未达预期。商客市场2月收入88.5万同比增1万,价值拓展完成115。会议强调管理人员需养成“日清日结”习惯,杜绝工作拖延。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "商客市场收入拓展与社区/单位拆迁营销进展",
|
"title": "市场部季度收官、满意度考核攻坚与年度考核部署",
|
||||||
"time_interval": "00:30:23 - 00:32:04",
|
"time_interval": "00:35:22 - 00:46:16",
|
||||||
"overview": "商客市场2月收入88.5万元,较上期增加1万元,超改、城北和南京改增幅超2万元,价值拓展完成115户。拆迁营销主要聚焦通货场景价值提升,已完成1145户改造,通过社区清洗与单位集中服务相结合推进,本周开展5场活动但签约转化率未达预期,需重点加强落实签约板块。"
|
"overview": "要求市场部提前谋划二季度活动,本周内汇报招聘、农村渠道及营销方案进度。针对满意度测评拉分项,要求负责人亲自抓,按市公司规范操作“80/200/2000/20000”指标并每日报送日报。最后强调全员执行力,提前摸排年度考核影响因素,确保在公平竞争中取得改善。"
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "运动会筹备工作已明确时间节点,分管领导出差期间需提前视频汇报。会议重点强调四公司年度考核的提前摸排,要求各部门主动了解可能影响考核的指标变化,避免被动。领导指出当前部分工作存在知道怎么做却不去做的作风问题,要求各级人员强化执行力,确保年度考核起跑线公平,争取通过努力改善成绩。"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -2,13 +2,21 @@
|
||||||
|
|
||||||
# 会议记录
|
# 会议记录
|
||||||
|
|
||||||
议 题:合川分公司周例会(2026年第X期)
|
议 题:合川分公司周例会
|
||||||
|
|
||||||
时 间:2026年5月6日 13:37
|
时 间:2026年5月6日 13:37
|
||||||
|
|
||||||
|
地 点:分公司会议室
|
||||||
|
|
||||||
主持人:AlanPaine
|
主持人: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. **工作纪律与执行反馈:**
|
1. **网络与运维方面:**
|
||||||
- 各项回复工作必须做到日清日结、量化结果,严禁拖延或敷衍,未完成任务需主动沟通困难并推进。
|
- 强化“日清日结”工作习惯,杜绝工作拖延,未完成事项需及时沟通推进并量化结果。
|
||||||
- 针对“上课”指标,需结合专线助账客单位业务发展拿出具体可行方案,明确责任人确保达成。
|
- PCDN学校出口问题需尽快落实限速机制,保障专线质量。
|
||||||
2. **资源统筹与合规管理:**
|
- 二级基站拆除务必按期完成下电及服务费调整,配合专项工作节点。
|
||||||
- 招待费使用需统筹规范,政企部对外招待应合理分摊,避免由个别客户经理个人承担,严禁历史费用拖欠。
|
|
||||||
- 河川区荣誉申报需先摸清内部意向与流程,确认有推荐名额后再行申报,避免资源浪费。
|
|
||||||
|
|
||||||
---
|
2. **市场与商客方面:**
|
||||||
|
- 提前谋划二季度业务活动,打破淡季思维,全面造势提升业务活跃度。
|
||||||
|
- 本周内汇报招聘、农村渠道建设及营销方案进度;商客经理需按周统计基础业务,加强落实签约。
|
||||||
|
|
||||||
#### 市场及考核分管领导强调:
|
3. **满意度与考核攻坚:**
|
||||||
|
- 针对满意度测评拉分项,负责人必须亲自抓,严格按市公司规范操作“80/200/2000/20000”指标,每日微信报送日报。
|
||||||
|
- 明确KPI考核方向(工信部有责及网率),充分利用操作空间平衡指标,确保考核达标。
|
||||||
|
|
||||||
1. **市场收官与满意度整改:**
|
4. **综合管理与作风整顿:**
|
||||||
- 市场部需提前谋划二季度业务活动,克服淡季疲软思想,全面提振签约、商客及AI军团等业务活动声势。
|
- 强化全员执行力,杜绝“知道怎么做却不去做”的作风,各项工作需明确措施、确保成效。
|
||||||
- 满意度工作必须落实到位,主管需亲自抓,严格按四公司规定打造满意客户样板;对不满客户需利用时间节点和技巧进行干预,商务部每日需发送日报。
|
- 政企接待费用需统筹分摊,避免个别客户经理集中招待导致预算超支。
|
||||||
- 摸细KPI考核规则(工信部有责指标及网率考核),利用可控的操作动作平衡指标,确保考核成绩。
|
- 提前摸排年度考核影响因素,特别是集团相关考核项,确保在公平竞争中取得改善,避免起跑线落后。
|
||||||
2. **年度考核与执行力建设:**
|
|
||||||
- 提前摸排四公司年度考核指标变化,主动了解可能影响考核的病毒、卡位点或政策调整,提前沟通争取有利条件,避免被动。
|
|
||||||
- 严厉批评“知道怎么做却不去做”的作风,要求各级管理人员及一线人员强化执行力,确保年度考核起跑线公平,通过努力改善成绩。
|
|
||||||
|
|
@ -10,6 +10,7 @@ const state = {
|
||||||
guideBusy: false,
|
guideBusy: false,
|
||||||
resultEditMode: false,
|
resultEditMode: false,
|
||||||
rightEditMode: false,
|
rightEditMode: false,
|
||||||
|
processGuideEditMode: false,
|
||||||
selectedTreeKey: "",
|
selectedTreeKey: "",
|
||||||
rightResource: null,
|
rightResource: null,
|
||||||
};
|
};
|
||||||
|
|
@ -92,6 +93,12 @@ function meetingById(meetingId) {
|
||||||
return state.meetings.find((item) => item.id === meetingId) || null;
|
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) {
|
function templateMetaByName(name) {
|
||||||
return state.templates.find((item) => item.name === name) || null;
|
return state.templates.find((item) => item.name === name) || null;
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +159,127 @@ function refreshActionButtons() {
|
||||||
updateGuideButton(resource);
|
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() {
|
function resetProcessingStream() {
|
||||||
$("#stream-box").style.display = "none";
|
$("#stream-box").style.display = "none";
|
||||||
$("#stream-title").textContent = "";
|
$("#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;
|
state.processing = true;
|
||||||
setStatus("left", true, "总结中");
|
setStatus("left", true, "总结中");
|
||||||
refreshActionButtons();
|
refreshActionButtons();
|
||||||
|
|
@ -768,9 +1023,12 @@ function startMeetingProcess() {
|
||||||
$("#stream-title").textContent = "第一阶段:结构化主题...";
|
$("#stream-title").textContent = "第一阶段:结构化主题...";
|
||||||
$("#stream-content").textContent = "";
|
$("#stream-content").textContent = "";
|
||||||
|
|
||||||
const source = new EventSource(
|
const params = new URLSearchParams({ template_name: state.templateName });
|
||||||
`/api/meetings/${state.meetingId}/process?template_name=${encodeURIComponent(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 resultAcc = "";
|
||||||
let streamAcc = "";
|
let streamAcc = "";
|
||||||
|
|
@ -808,6 +1066,8 @@ function startMeetingProcess() {
|
||||||
if (payload.type === "done") {
|
if (payload.type === "done") {
|
||||||
source.close();
|
source.close();
|
||||||
state.processing = false;
|
state.processing = false;
|
||||||
|
patchMeeting(state.meetingId, { has_summary: true });
|
||||||
|
renderMeetingStatus(meetingById(state.meetingId));
|
||||||
showResult(payload.data?.result || resultAcc || "");
|
showResult(payload.data?.result || resultAcc || "");
|
||||||
toast("会议总结完成");
|
toast("会议总结完成");
|
||||||
refreshActionButtons();
|
refreshActionButtons();
|
||||||
|
|
@ -840,8 +1100,7 @@ $("#btn-process").addEventListener("click", async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ensureTemplateGuideBeforeProcess();
|
await openProcessGuideModal();
|
||||||
startMeetingProcess();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state.processing = false;
|
state.processing = false;
|
||||||
state.guideBusy = 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 () => {
|
$("#btn-toggle-result-edit").addEventListener("click", async () => {
|
||||||
if (!state.meetingId || state.processing || state.guideBusy) {
|
if (!state.meetingId || state.processing || state.guideBusy) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -868,6 +1227,8 @@ $("#btn-toggle-result-edit").addEventListener("click", async () => {
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ content }),
|
body: JSON.stringify({ content }),
|
||||||
});
|
});
|
||||||
|
patchMeeting(state.meetingId, { has_summary: true });
|
||||||
|
renderMeetingStatus(meetingById(state.meetingId));
|
||||||
showResult(content);
|
showResult(content);
|
||||||
toast("结果已保存");
|
toast("结果已保存");
|
||||||
});
|
});
|
||||||
|
|
@ -927,24 +1288,9 @@ $("#btn-reparse-guide").addEventListener("click", async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const templateName = resource.templateName || resource.name;
|
const templateName = resource.templateName || resource.name;
|
||||||
state.guideBusy = true;
|
syncTemplateSelection(templateName);
|
||||||
setStatus("right", true, "解析中");
|
|
||||||
refreshActionButtons();
|
|
||||||
|
|
||||||
try {
|
openStandaloneReparseModal();
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#btn-import").addEventListener("click", () => {
|
$("#btn-import").addEventListener("click", () => {
|
||||||
|
|
@ -1013,9 +1359,12 @@ $$("[data-close]").forEach((button) => {
|
||||||
button.addEventListener("click", () => closeModal(button.dataset.close));
|
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) => {
|
document.getElementById(id).addEventListener("click", (event) => {
|
||||||
if (event.target.id === id) {
|
if (event.target.id === id) {
|
||||||
|
if (id === "modal-reparse-guide" && state.guideBusy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
closeModal(id);
|
closeModal(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -621,6 +621,7 @@ select.btn {
|
||||||
place-items: center;
|
place-items: center;
|
||||||
background: rgba(17, 47, 84, 0.28);
|
background: rgba(17, 47, 84, 0.28);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-mask.show {
|
.modal-mask.show {
|
||||||
|
|
@ -660,6 +661,161 @@ select.btn {
|
||||||
background: #f9fcff;
|
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 {
|
.toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 22px;
|
top: 22px;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Meeting Summary</title>
|
<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>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/13.0.3/marked.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
<section class="panel" id="template-panel">
|
<section class="panel" id="template-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<span>文档编辑</span>
|
<span>右侧编辑区</span>
|
||||||
<div class="status-block">
|
<div class="status-block">
|
||||||
<div class="status-row">
|
<div class="status-row">
|
||||||
<span class="status-label">当前状态</span>
|
<span class="status-label">当前状态</span>
|
||||||
|
|
@ -105,6 +105,69 @@
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</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">模板说明补充</div>
|
||||||
|
<div class="guide-inline-prompt-subtitle">可为空;会和系统解析模板一起用于重新解析</div>
|
||||||
|
<textarea id="reparse-guide-notes" class="guide-inline-textarea" spellcheck="false" placeholder="例如:强调哪些结构必须保留、哪些段落需要重点解释、哪些措辞需要避免"></textarea>
|
||||||
|
<div class="guide-inline-actions">
|
||||||
|
<button class="btn" id="btn-reparse-guide-cancel">取消</button>
|
||||||
|
<button class="btn primary" id="btn-reparse-guide-confirm">确认重解析</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-mask" id="modal-import">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
<h3>导入会议转录</h3>
|
<h3>导入会议转录</h3>
|
||||||
|
|
@ -146,6 +209,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toast" id="toast"></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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
- 严格保留模板的Markdown标题层级与宏观结构(# 会议记录 -> ## 会议内容 -> ### 各部门汇报/部署强调),不得增删或调整主标题顺序。
|
- 严格保留模板的Markdown标题层级体系(# 总标题 > ## 内容主体 > ### 一级板块 > #### 发言人/细分板块),后续生成时不得随意更改层级关系。
|
||||||
- 模板中的“X”、“XX”、“XXX”及具体年份、部门名均为占位示例,生成时必须替换为会议真实数据与名称;若实际会议未涉及某字段,则直接省略该条目,不强行补齐。
|
- 顶部元数据(议题、时间、地点、主持人、参加人)必须基于实际会议信息填充,所有占位符(X、XX、XXX等)需替换为真实数据,严禁原样输出。
|
||||||
- “各部门汇报”小节需按实际汇报顺序提炼核心内容与进度数据,替换原长句模板;若无汇报内容,可省略该小节。
|
- “议程”区块需保留标题,其下列表项数量与内容严格按实际会议流程动态调整,无实际议程时省略该区块。
|
||||||
- “部署强调”部分采用“#### [领导职务/姓名]强调:”作为独立标题块,多个领导发言时按实际顺序并列生成,替换示例中的“X总”。
|
- “汇报通报”区块需替换具体部门名称与量化数据,若会议记录中无汇报环节,则整体省略该小节,不强行补齐。
|
||||||
- 领导强调下按实际业务领域划分子项(格式为“数字. **领域:**”),子项数量与领域名称严格按实际讲话内容动态增减,不得照搬模板示例的固定领域列表。
|
- “领导部署/强调”区块需保留结构框架,将占位姓名替换为实际发言人;该区块下的发言人数量、业务分类标题(**XX方面:**)及下级详情列表需与真实讲话内容严格对应,动态增减,无依据时直接省略。
|
||||||
- 各领域下的具体指示需使用无序列表(`- `)逐条列出,替换示例“XXX”;保持原有缩进层级,确保要点清晰。
|
- 所有示例性文字、演示性标题及占位内容必须在生成前全部替换为实际业务内容,禁止保留模板中的示例字符。
|
||||||
- 所有仅用于演示结构的说明性文字(如“按议程现场按顺序做汇报”“详见汇报材料”等)必须删除,仅输出实际会议提炼的实质内容。
|
- 使用水平分割线(---)作为区块分隔符,依次分隔头部信息、议程、汇报板块与部署板块,保持文档结构清晰。
|
||||||
- 严格遵循模板原有的排版分隔逻辑,各主要模块间保留“---”分隔线,维持整体版式节奏。
|
- 严格保留格式规范:领导发言使用有序列表(1. 2. 3.),业务分类使用加粗文本(**分类名称:**),具体指示或要点使用无序列表(- ),有内容时必须完整套用此层级格式。
|
||||||
|
- 遵循“无依据不填充”原则,模板中未被实际会议内容支撑的空白项、占位项或示例项必须直接删除,不得虚构或强行对齐模板结构。
|
||||||
Binary file not shown.
|
|
@ -135,7 +135,12 @@ def _collect_llm_content(client, model, system_prompt: str, user_prompt: str, ma
|
||||||
return "".join(content).strip()
|
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")
|
prompt = load_prompt("templatet_parser", "zh")
|
||||||
config = cfg or _load_config()
|
config = cfg or _load_config()
|
||||||
client = _get_llm_client(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_name=template_name,
|
||||||
template_content=template_content,
|
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)
|
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)
|
template_path = _resolve_child(TEMPLATE_DIR, template_name)
|
||||||
if not template_path.exists():
|
if not template_path.exists():
|
||||||
raise HTTPException(404, f"Template not found: {template_name}")
|
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_name=template_name,
|
||||||
template_content=template_path.read_text(encoding="utf-8"),
|
template_content=template_path.read_text(encoding="utf-8"),
|
||||||
cfg=cfg,
|
cfg=cfg,
|
||||||
|
user_notes=user_notes,
|
||||||
)
|
)
|
||||||
guide_path.write_text(guide_content, encoding="utf-8")
|
guide_path.write_text(guide_content, encoding="utf-8")
|
||||||
return guide_content
|
return guide_content
|
||||||
|
|
@ -462,8 +476,8 @@ async def save_template_guide(name: str, payload: dict):
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/templates/{name}/guide/reparse")
|
@app.post("/api/templates/{name}/guide/reparse")
|
||||||
async def reparse_template_guide(name: str):
|
async def reparse_template_guide(name: str, user_notes: str = ""):
|
||||||
content = _ensure_template_guide(name, force=True)
|
content = _ensure_template_guide(name, force=True, user_notes=user_notes)
|
||||||
return {"name": name, "content": content}
|
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")
|
@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
|
meeting_dir = DATA_DIR / meeting_id
|
||||||
if not meeting_dir.exists():
|
if not meeting_dir.exists():
|
||||||
raise HTTPException(404, "Meeting not found")
|
raise HTTPException(404, "Meeting not found")
|
||||||
|
|
@ -578,6 +597,8 @@ async def process_meeting(meeting_id: str, request: Request, template_name: str
|
||||||
article=transcript,
|
article=transcript,
|
||||||
sub_topices=sub_topics,
|
sub_topices=sub_topics,
|
||||||
)
|
)
|
||||||
|
if user_notes.strip():
|
||||||
|
user_prompt += f"\n\n用户补充要点(优先参考,可为空):\n{user_notes.strip()}"
|
||||||
|
|
||||||
result = ""
|
result = ""
|
||||||
for chunk_type, chunk_content in _llm_stream(client, model_name, system_prompt, user_prompt):
|
for chunk_type, chunk_content in _llm_stream(client, model_name, system_prompt, user_prompt):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue