处理了一些编码错误
parent
0560f56a90
commit
4c2629a0bf
|
|
@ -1,49 +1,48 @@
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"sub_topics": [
|
"sub_topics": [
|
||||||
{
|
{
|
||||||
"title": "宽带安装维护与网络指标汇报",
|
"title": "宽带装维进度与九零工程转化退单指标",
|
||||||
"time_interval": "00:00:01 - 00:02:40",
|
"time_interval": "00:00:01 - 00:03:06",
|
||||||
"overview": "本周汇报宽带上门安装量、维护质量及转化率指标。受天气影响安装量约400户,累计上门超1000户但距3000户目标进度靠后;弱光值降至0.51逼近目标,月度转化率87.35%接近90%,退单率控制在6.53%。"
|
"overview": "会议通报了宽带装维进度,上周上门量达580户,累计超1000户,但距离3000户目标进度偏后。九零工程月度转化率为87.35%,退单率为6.53%,主要受用户原因、天气及改约影响。PCTN蓝单当周转化率为56.82%,月度为75.29%,退单率达44.29%,需持续优化流程。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "退单原因分析与PCDN专线治理",
|
"title": "PCDN专线学校限速协调与审核流程优化",
|
||||||
"time_interval": "00:02:41 - 00:05:31",
|
"time_interval": "00:03:07 - 00:05:31",
|
||||||
"overview": "本周剖析宽带退单因素及PCDN专线网络质量恶化应对情况。退单主因为用户不安装及改约,兰单转化率当周56.82%、月度75.29%未达80%目标;PCDN专线因学校出口带宽问题恶化,已上报市公司分析IP并协调建立后台限速机制。"
|
"overview": "针对PCDN专线指标恶化问题,会议指出主要受开学后两所学校IP地址滥用影响,已报市公司分析并拟采取限速措施。同时提出优化审核流程,建议剔除一线审批同意的退单,并将该指标纳入综合运维考核。目前正协调全资建立后台自动限速机制以提升管控效率。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "基站攻坚、专线护航与故障通报",
|
"title": "网络质量攻坚、基站维护与二级站点拆除计划",
|
||||||
"time_interval": "00:05:33 - 00:07:56",
|
"time_interval": "00:05:33 - 00:09:35",
|
||||||
"overview": "本周通报基站攻坚、政企专线护航进度及当周故障情况。超频基站故障已处理恢复,5G参数调整试点持续观察;118条专线存量73条已巡检56条,剩余17条确保月底前完成;当周收故障33件,其中9件为光缆等网络问题。"
|
"overview": "网络质量方面,超频基站故障已及时处理恢复,5G基站参数调整试点将观察信号弱化改善效果。二级基站拆除工作进展顺利,剩余75站预计4月15日前全量完成下电与资费调整。此外,118条专线已按计划推进巡检,下周将针对新收到的网络质量综合评估体系分解目标。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "二级站点拆除与网络质量评估体系",
|
"title": "综合部行政事务推进、渠道评估与气象人员管理",
|
||||||
"time_interval": "00:07:57 - 00:09:35",
|
"time_interval": "00:09:46 - 00:12:39",
|
||||||
"overview": "本周通报当周故障、二级站点拆除进度及新评估体系部署。当周故障中网络相关9件,二级站点剩余75站计划两周内拆除,目标4月15日前全量调整资费;内部已提前收到网络质量综合评估体系文件,下周将分解指标制定目标。"
|
"overview": "综合部推进了建委清单整理、投资计划汇报及市场终端占比措施制定,并落实了招投标打印设备的保障方案。渠道产能评估已召开宣贯会并梳理方案,预计本周专题汇报。同时强调气象工作人员纪律管理,并指出基金工作清单与宣传工作因兼职原因有所滞后,需加快进度。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "综合部项目推进与行政事务协调",
|
"title": "工会经费管控、暖心工程升级与体育文化节筹备",
|
||||||
"time_interval": "00:09:46 - 00:12:39",
|
"time_interval": "00:12:40 - 00:19:50",
|
||||||
"overview": "本周汇报重点项目跟进、物资采购及渠道产能评估进展。花果山项目无变化,无人机验收本周推动;建委框架清单及投资计划已汇报完成;打印机采购拟采用外派点保障招投标需求;渠道产能评估宣贯会已开,方案本周专题汇报。"
|
"overview": "受经费压降影响,工会将推行节支增效,食堂改造升级及下半年直升机引水方案已获批。全年规划六场大型文体活动,重点筹备第四届体育文化节,当前方阵人员缺口9人,需各部门今日落实并安排下班后排练。参会者可获得绩效加分,以激励积极性。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "工会经费公示与体育文化节筹备",
|
"title": "主题教育落实、招待费通报与区级奖项申报策略",
|
||||||
"time_interval": "00:12:40 - 00:19:50",
|
"time_interval": "00:19:51 - 00:27:28",
|
||||||
"overview": "本周通报工会经费压降使用方案及第四届体育文化节筹备情况。今年工会经费全面压降,食堂改造及直升机引水方案已获批;体育文化节方阵需25人目前缺编9人,已决议由各部门抽调并于周二至周四排练,参与者享绩效加分。"
|
"overview": "主题教育已按要求完成基层学习,2025年招待费超预算14%,综合部超支明显,需统筹对外接待安排。针对河川区担当作为奖项申报,会议建议先向委办及社保局领导摸底意向,若入围希望不大则避免无效投入。目前申报材料已提交区领导,需持续跟进审批进度。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "招待费管控与区级评优申报",
|
"title": "商客市场复盘、查企进度与管理层执行力要求",
|
||||||
"time_interval": "00:19:51 - 00:26:58",
|
"time_interval": "00:27:29 - 00:35:15",
|
||||||
"overview": "本周通报2025年招待费预算执行情况及区级评优申报进展。2025年招待费整体超预算14%,综合部超支107%已调整预算,正气部招待需统筹分配;区国资委管辖企业评优已启动,委办与社保局联合经办,需提前对接名额分配以争取优势。"
|
"overview": "商客市场1月收入88.5万元,同比提升,草街等分局增幅超2万,价值拓展排名第6。查企工作聚焦低价值用户提值与融合套餐推广,本周开展5场活动但签约未达预期。管理层严厉强调工作执行力,要求养成“日事日清”习惯,并限期提交各项指标的具体保障方案。"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "查企商客业务通报与工作作风整顿",
|
"title": "满意度考核反思、客服指标管控与年度KPI部署",
|
||||||
"time_interval": "00:27:32 - 00:35:21",
|
"time_interval": "00:35:16 - 00:46:16",
|
||||||
"overview": "本周通报查企与商客市场业绩及工作作风纪律要求。查企完成6900万价值提升发展1110户,重点提升低价值宽带;商客收入88.5万环比增1万;领导强调需养成日事日清习惯,针对专线及在线指标需当日量化回复并落实措施。"
|
"overview": "针对近期满意度测评排名靠后,会议批评市场部与政企部思想松懈,要求主管亲自抓并落实客户会参与机制。客服端将强化80/20指标管控技巧,每日输出日报,并预警离网率将纳入今年KPI考核。最后强调需提前与市公司沟通年度考核口径,确保公平得分并提升整体执行力。"
|
||||||
},
|
}
|
||||||
{
|
]
|
||||||
"title": "市场客服指标复盘与年度考核部署",
|
}
|
||||||
"time_interval": "00:35:22 - 00:46:16",
|
```
|
||||||
"overview": "本周复盘市场客服指标并部署市公司年度考核工作。市场部需提前谋划二季度声势,本周汇报招聘及渠道进度;满意度暴露正气部管理松懈,80-20指标全市倒数第二需每日通报;考核明确工信部有责及离网率为KPI,要求提前沟通口径强化执行力。"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# 会议记录
|
# 会议记录
|
||||||
|
|
||||||
议 题:合川分公司周例会(2026年第19期)
|
议 题:合川分公司周例会(2026年第X期)
|
||||||
|
|
||||||
时 间:2026年5月8日 10:38—10:50
|
时 间:2026年5月8日 10:38—10:50
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
主持人:管理员
|
主持人:管理员
|
||||||
|
|
||||||
参加人:分公司领导、各部门经理、AI云数中心经理
|
参加人:分公司领导、各部门负责人及相关岗位人员
|
||||||
|
|
||||||
议程:
|
议程:
|
||||||
|
|
||||||
|
|
@ -22,34 +22,31 @@
|
||||||
|
|
||||||
## 会议内容
|
## 会议内容
|
||||||
|
|
||||||
### 一、各部门汇报
|
### 一、市场部、政企部、建维部、综合部︱党群纪检部按议程现场按顺序做汇报。综合部按照领导部署通报周例会领导部署工作推进完成情况:各部门按议程现场汇报工作推进情况:建维部通报宽带装维进度(上周上门580户,累计超1000户)、九零工程转化退单指标及二级站点拆除计划;综合部汇报建委清单整理、渠道产能评估、工会经费管控、体育文化节筹备及区级奖项申报策略;市场与政企部复盘商客市场收入与查企进度,并通报满意度测评情况。重点工作按节点推进,部分指标需优化落实。
|
||||||
|
|
||||||
建维部汇报宽带上门安装量及网络指标,受天气影响安装进度略缓,累计上门超1000户但距3000户目标进度靠后;弱光值降至0.51逼近目标,月度转化率87.35%接近90%,退单率6.53%;PCDN专线因学校出口带宽问题恶化,已上报市公司协调IP分析与后台限速机制;超频基站故障已恢复,118条专线存量巡检有序推进,剩余17条确保月底前完成;二级站点剩余75座计划两周内拆除,新网络质量综合评估体系下周将分解目标。综合部通报无人机验收及建委框架清单已汇报完成,打印机采购拟采用外派点保障招投标,工会经费全面压降,第四届体育文化节方阵缺编9人需各部门抽调,2025年招待费整体超预算14%需统筹管控,区级评优申报需提前对接争取名额。市场部与政企部通报查企完成6900万价值提升发展1110户,商客收入88.5万环比增长,二季度营销活动需提前谋划,满意度及80-20指标全市排名靠后需每日通报,市公司年度考核(工信部有责、离网率)需提前沟通口径并强化执行落实。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 二、部署强调
|
### 二、部署强调
|
||||||
|
|
||||||
#### 分公司领导强调:
|
#### 分管领导强调:
|
||||||
|
|
||||||
1. **网络与运维方面:**
|
1. **工作要求与执行力:**
|
||||||
- 弱光值与转化率指标需持续压降改善,月度转化率当前87.35%已接近90%目标,需纳入运维阶段性考核跟踪。
|
- 养成“日事日清、日清日结”工作习惯,阶段性汇报进展与困难,严禁工作拖延数月无反馈。
|
||||||
- PCDN专线网络质量恶化主因学校出口带宽,需协调市公司分析IP地址并建立后台自动限速机制,避免被动退单。
|
- 工作安排需第一时间量化回复领导,而非仅依赖周报记录,确保推进闭环。
|
||||||
- 二级站点拆除需抢抓进度,剩余75座争取两周内全部完成下电与资费调整,确保4月15日前全量完成。
|
2. **指标保障与方案落实:**
|
||||||
- 内部已提前收悉网络质量综合评估体系,下周须针对指标进行分解并制定具体目标。
|
- 限期提交专线、商客等核心指标的具体保障措施与责任人,拒绝空谈,确保目标达成。
|
||||||
|
- 针对满意度测评靠后问题,批评相关部门思想松懈,主管需亲自抓客户会参与机制,杜绝“亡羊补牢”式管理。
|
||||||
|
3. **客服管控与KPI预警:**
|
||||||
|
- 强化80/20指标管控技巧,市场部需每日输出日报至分管领导。
|
||||||
|
- 预警离网率将纳入今年KPI考核,要求摸透市公司考核口径与可控操作空间,提前布局。
|
||||||
|
|
||||||
2. **市场与客服方面:**
|
---
|
||||||
- 二季度营销活动需提前谋划造势,打破传统淡季思维,重点推进签约、3人融合及AI终端业务,避免业务推进疲软。
|
|
||||||
- 满意度及80-20指标目前全市排名靠后,市场部与政企部须吃透市公司考核技巧,每日输出日报通报进度。
|
|
||||||
- 正气部满意度管理存在松懈,主管须亲自抓客户会落实,严禁“亡羊补牢”式被动应对,满意度工作须常态化管控。
|
|
||||||
|
|
||||||
3. **综合与行政方面:**
|
#### 分公司主要领导强调:
|
||||||
- 工会经费全面压降,需以节电耗用更少的钱办更好的事,食堂改造及直升机引水方案已获批推进,下半年启用节约成本。
|
|
||||||
- 第四届体育文化节方阵目前缺编9人,需各部门于本周内抽调人员,周二至周四下班后集中排练,参与者享绩效加分。
|
|
||||||
- 2025年招待费整体超预算14%,综合部超支严重已调整预算,其他部门须合理规划时间进度,正气部对外招待需统筹分配客户经理资源。
|
|
||||||
- 区级担当作为评优已启动,需提前对接区委办与人力社保局了解名额分配机制,避免盲目申报浪费精力。
|
|
||||||
|
|
||||||
4. **考核与作风建设方面:**
|
1. **执行力与文化塑造:**
|
||||||
- 全体员工须养成“日事日清、日清日结”工作习惯,任务安排须当日量化回复并落实具体措施,严禁拖延数月无进展。
|
- 各级管理人员及一线员工必须具备强执行力,知晓方法却不落实的工作作风不可接受。
|
||||||
- 市公司年度考核指标已明确为工信部有责及离网率,各部门须提前沟通考核口径,避免因信息不对称在起跑线落后。
|
- 各部门需提前摸底市公司年度考核口径与潜在风险,提前沟通争取公平得分,避免起跑线落后。
|
||||||
- 强化执行力建设,管理层至一线员工须明确目标与路径,杜绝“知道怎么做却不去做”的作风问题,确保年度考核稳妥达标。
|
2. **协同联动与考核前置:**
|
||||||
|
- 加强分管领导与牵头部门联动,必要时共同向上沟通,确保考核工作前置。
|
||||||
|
- 面对考核指标需主动出击,通过努力改善得分,而非被动等待。
|
||||||
|
|
@ -224,7 +224,7 @@ function loadSettingsDraft(cfg) {
|
||||||
async function persistSettingsDraft() {
|
async function persistSettingsDraft() {
|
||||||
const payload = cloneSettingsDraft(state.settingsDraft || {});
|
const payload = cloneSettingsDraft(state.settingsDraft || {});
|
||||||
if (!payload.api_profiles.length) {
|
if (!payload.api_profiles.length) {
|
||||||
throw new Error("??????????");
|
throw new Error("至少保留一个模型配置");
|
||||||
}
|
}
|
||||||
await api("/api/settings", {
|
await api("/api/settings", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
|
@ -1133,7 +1133,7 @@ async function reparseTemplateGuideFromModal(userNotes = "") {
|
||||||
|
|
||||||
|
|
||||||
$("#process-guide-editor").value = result.content || "";
|
$("#process-guide-editor").value = result.content || "";
|
||||||
$("#guide-modal-title").textContent = `???? ? ${templateName}`;
|
$("#guide-modal-title").textContent = `模板说明 · ${templateName}`;
|
||||||
$("#guide-modal-subtitle").textContent = "确认后开始总结,也可以先补充说明或重解析";
|
$("#guide-modal-subtitle").textContent = "确认后开始总结,也可以先补充说明或重解析";
|
||||||
setProcessGuideEditMode(false);
|
setProcessGuideEditMode(false);
|
||||||
|
|
||||||
|
|
@ -1450,6 +1450,416 @@ $("#btn-reparse-guide").addEventListener("click", async () => {
|
||||||
openStandaloneReparseModal();
|
openStandaloneReparseModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
renderMeetingStatus = function(meeting) {
|
||||||
|
const name = $("#sidebar-meeting-name");
|
||||||
|
const meta = $("#sidebar-meeting-meta");
|
||||||
|
const tip = $("#selected-meeting-tip");
|
||||||
|
const summaryBadge = $("#badge-summary");
|
||||||
|
const topicsBadge = $("#badge-topics");
|
||||||
|
|
||||||
|
if (!meeting) {
|
||||||
|
name.textContent = "当前会议:未选择";
|
||||||
|
meta.textContent = "请从左侧选择一个会议开始处理。";
|
||||||
|
tip.textContent = "未选择会议";
|
||||||
|
summaryBadge.textContent = "未生成总结";
|
||||||
|
topicsBadge.textContent = "未生成主题 JSON";
|
||||||
|
summaryBadge.className = "badge muted";
|
||||||
|
topicsBadge.className = "badge muted";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
name.textContent = `当前会议:${meeting.name}`;
|
||||||
|
meta.textContent =
|
||||||
|
`ID: ${meeting.id} · 导入时间:${meeting.created_at || "未知"} · 原始文件:` +
|
||||||
|
`${meeting.original_filename || meeting.transcript_filename || "未知"}`;
|
||||||
|
tip.textContent = `当前处理会议:${meeting.name} (${meeting.id})`;
|
||||||
|
summaryBadge.textContent = meeting.has_summary ? "已生成总结" : "未生成总结";
|
||||||
|
topicsBadge.textContent = meeting.has_topics ? "已生成主题 JSON" : "未生成主题 JSON";
|
||||||
|
summaryBadge.className = meeting.has_summary ? "badge" : "badge muted";
|
||||||
|
topicsBadge.className = meeting.has_topics ? "badge" : "badge muted";
|
||||||
|
};
|
||||||
|
|
||||||
|
refreshActionButtons = function() {
|
||||||
|
const canProcess = Boolean(state.meetingId) && !state.processing && !state.guideBusy;
|
||||||
|
const currentMeeting = meetingById(state.meetingId);
|
||||||
|
$("#btn-process").disabled = !canProcess;
|
||||||
|
$("#btn-process").textContent = state.processing
|
||||||
|
? "总结中"
|
||||||
|
: state.guideBusy
|
||||||
|
? "等待"
|
||||||
|
: currentMeeting?.has_summary
|
||||||
|
? "重总结"
|
||||||
|
: "总结";
|
||||||
|
|
||||||
|
const canEditResult = Boolean(state.meetingId) && !state.processing && !state.guideBusy;
|
||||||
|
$("#btn-toggle-result-edit").disabled = !canEditResult;
|
||||||
|
|
||||||
|
const resource = state.rightResource;
|
||||||
|
const canEditSide = Boolean(resource?.editable) && !state.processing && !state.guideBusy;
|
||||||
|
$("#btn-toggle-side-edit").disabled = !canEditSide;
|
||||||
|
|
||||||
|
updateGuideButton(resource);
|
||||||
|
};
|
||||||
|
|
||||||
|
setProcessGuideEditMode = function(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 ? "保存" : "编辑";
|
||||||
|
};
|
||||||
|
|
||||||
|
syncProcessGuideConfirmLabel = function() {
|
||||||
|
const meeting = meetingById(state.meetingId);
|
||||||
|
$("#btn-process-guide-confirm").textContent = meeting?.has_summary ? "重总结" : "确认";
|
||||||
|
};
|
||||||
|
|
||||||
|
persistSettingsDraft = async function() {
|
||||||
|
const payload = cloneSettingsDraft(state.settingsDraft || {});
|
||||||
|
if (!payload.api_profiles.length) {
|
||||||
|
throw new Error("至少保留一个模型配置");
|
||||||
|
}
|
||||||
|
await api("/api/settings", {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
resetReparseModalProgress = function() {
|
||||||
|
$("#reparse-stream-title").textContent = "正在重新解析模板说明...";
|
||||||
|
$("#reparse-stream-content").textContent = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
openStandaloneReparseModal = function() {
|
||||||
|
$("#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();
|
||||||
|
};
|
||||||
|
|
||||||
|
runStandaloneGuideReparseFlow = async function() {
|
||||||
|
const templateName = state.templateName;
|
||||||
|
const userNotes = $("#reparse-modal-notes").value || "";
|
||||||
|
|
||||||
|
state.guideBusy = true;
|
||||||
|
setStatus("right", true, "解析中");
|
||||||
|
setStandaloneReparseProcessing(true);
|
||||||
|
resetReparseModalProgress();
|
||||||
|
pushStandaloneReparseLine(`模板:${templateName}`);
|
||||||
|
pushStandaloneReparseLine("已提交重解析请求,正在准备说明...");
|
||||||
|
refreshActionButtons();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (userNotes.trim()) {
|
||||||
|
params.set("user_notes", userNotes.trim());
|
||||||
|
}
|
||||||
|
const source = new EventSource(
|
||||||
|
`/api/templates/${encodeURIComponent(templateName)}/guide/reparse/stream?${params.toString()}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
let contentAcc = "";
|
||||||
|
|
||||||
|
source.onmessage = (event) => {
|
||||||
|
if (!event.data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const payload = JSON.parse(event.data);
|
||||||
|
if (payload.type === "status") {
|
||||||
|
$("#reparse-stream-title").textContent = "正在重新解析模板说明...";
|
||||||
|
pushStandaloneReparseLine("已连接模型,开始解析...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.type === "chunk") {
|
||||||
|
const chunk = payload.data?.text || "";
|
||||||
|
if (chunk) {
|
||||||
|
contentAcc += chunk;
|
||||||
|
$("#reparse-stream-content").textContent = contentAcc
|
||||||
|
.replace(/\r\n/g, "\n")
|
||||||
|
.split("\n")
|
||||||
|
.slice(-6)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.type === "done") {
|
||||||
|
source.close();
|
||||||
|
resolve(payload.data || { name: templateName, content: contentAcc });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.type === "error") {
|
||||||
|
source.close();
|
||||||
|
reject(new Error(payload.data || "解析失败"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
source.onerror = () => {
|
||||||
|
source.close();
|
||||||
|
reject(new Error("解析连接中断"));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
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 || "");
|
||||||
|
closeModal("modal-reparse-guide");
|
||||||
|
await openProcessGuideModal();
|
||||||
|
$("#process-guide-editor").value = result.content || "";
|
||||||
|
} finally {
|
||||||
|
state.guideBusy = false;
|
||||||
|
setStatus("right", false, "空闲");
|
||||||
|
setStandaloneReparseProcessing(false);
|
||||||
|
refreshActionButtons();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resetProcessingStream = function() {
|
||||||
|
$("#stream-box").style.display = "none";
|
||||||
|
$("#stream-title").textContent = "";
|
||||||
|
$("#stream-content").textContent = "";
|
||||||
|
setStatus("left", false, "空闲");
|
||||||
|
};
|
||||||
|
|
||||||
|
ensureTemplateGuideBeforeProcess = async function() {
|
||||||
|
const templateMeta = templateMetaByName(state.templateName);
|
||||||
|
if (templateMeta?.has_guide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast("当前模板还没有解析说明,先为你解析模板。");
|
||||||
|
state.guideBusy = true;
|
||||||
|
setStatus("left", true, "解析中");
|
||||||
|
refreshActionButtons();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await api(`/api/templates/${encodeURIComponent(state.templateName)}/guide/reparse`, {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
state.templates = state.templates.map((item) => (
|
||||||
|
item.name === state.templateName ? { ...item, has_guide: true } : item
|
||||||
|
));
|
||||||
|
if (
|
||||||
|
state.rightResource &&
|
||||||
|
state.rightResource.templateName === result.name &&
|
||||||
|
state.rightResource.type === "template"
|
||||||
|
) {
|
||||||
|
state.rightResource.hasGuide = true;
|
||||||
|
refreshActionButtons();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
state.guideBusy = false;
|
||||||
|
setStatus("left", false, "空闲");
|
||||||
|
refreshActionButtons();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadTemplateGuideForProcess = async function() {
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
saveProcessGuideFromModal = async function() {
|
||||||
|
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("说明已保存");
|
||||||
|
};
|
||||||
|
|
||||||
|
openProcessGuideModal = async function() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reparseTemplateGuideFromModal = async function(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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
runTemplateGuideReparse = async function(templateName, userNotes = "") {
|
||||||
|
return streamTemplateGuideReparse(templateName, userNotes, {
|
||||||
|
onStatus() {
|
||||||
|
$("#guide-modal-subtitle").textContent = "正在结合补充说明重新解析模板...";
|
||||||
|
},
|
||||||
|
onChunk(_chunk, contentAcc) {
|
||||||
|
$("#process-guide-editor").value = contentAcc;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
startMeetingProcess = function(userNotes = "") {
|
||||||
|
state.processing = true;
|
||||||
|
setStatus("left", true, "总结中");
|
||||||
|
refreshActionButtons();
|
||||||
|
showProcessingView();
|
||||||
|
$("#stream-box").style.display = "block";
|
||||||
|
$("#stream-title").textContent = "第一阶段:结构化主题...";
|
||||||
|
$("#stream-content").textContent = "";
|
||||||
|
|
||||||
|
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 = "";
|
||||||
|
|
||||||
|
source.onmessage = async (event) => {
|
||||||
|
if (!event.data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = JSON.parse(event.data);
|
||||||
|
|
||||||
|
if (payload.type === "status") {
|
||||||
|
if (payload.data === "preprocessing") {
|
||||||
|
$("#stream-title").textContent = "第一阶段:结构化主题...";
|
||||||
|
} else if (payload.data === "preprocessing_done") {
|
||||||
|
$("#stream-title").textContent = "主题提取完成,开始生成会议总结...";
|
||||||
|
} else if (payload.data === "summarizing") {
|
||||||
|
$("#stream-title").textContent = "第二阶段:生成会议总结...";
|
||||||
|
streamAcc = "";
|
||||||
|
$("#stream-content").textContent = "";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.type === "chunk") {
|
||||||
|
const { data } = payload;
|
||||||
|
streamAcc += data.text || "";
|
||||||
|
$("#stream-content").textContent = streamAcc.replace(/\r\n/g, "\n").split("\n").slice(-4).join("\n");
|
||||||
|
if (data.stage === 2 && data.chunk_type === "content") {
|
||||||
|
resultAcc += data.text || "";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.type === "error") {
|
||||||
|
source.close();
|
||||||
|
state.processing = false;
|
||||||
|
resetProcessingStream();
|
||||||
|
$("#processing-indicator").hidden = true;
|
||||||
|
refreshActionButtons();
|
||||||
|
toast(`处理失败:${payload.data}`, "err");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
source.onerror = () => {
|
||||||
|
source.close();
|
||||||
|
state.processing = false;
|
||||||
|
resetProcessingStream();
|
||||||
|
$("#processing-indicator").hidden = true;
|
||||||
|
refreshActionButtons();
|
||||||
|
toast("处理连接中断", "err");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
$("#btn-import").addEventListener("click", () => {
|
$("#btn-import").addEventListener("click", () => {
|
||||||
$("#import-name").value = "";
|
$("#import-name").value = "";
|
||||||
$("#import-file").value = "";
|
$("#import-file").value = "";
|
||||||
|
|
@ -1466,11 +1876,11 @@ $("#btn-confirm-import").addEventListener("click", async () => {
|
||||||
const name = $("#import-name").value.trim();
|
const name = $("#import-name").value.trim();
|
||||||
const file = $("#import-file").files[0];
|
const file = $("#import-file").files[0];
|
||||||
if (!name) {
|
if (!name) {
|
||||||
toast("???????", "err");
|
toast("请填写会议名称", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
toast("???????", "err");
|
toast("请选择会议文件", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1481,13 +1891,13 @@ $("#btn-confirm-import").addEventListener("click", async () => {
|
||||||
const result = await fetch("/api/meetings/import", { method: "POST", body: formData });
|
const result = await fetch("/api/meetings/import", { method: "POST", body: formData });
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
const detail = await result.json().catch(() => ({ detail: "Import failed" }));
|
const detail = await result.json().catch(() => ({ detail: "Import failed" }));
|
||||||
toast(`?????${detail.detail}`, "err");
|
toast(`导入失败:${detail.detail}`, "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = await result.json();
|
const payload = await result.json();
|
||||||
closeModal("modal-import");
|
closeModal("modal-import");
|
||||||
toast(`?????${name}`);
|
toast(`已导入会议:${name}`);
|
||||||
await refresh();
|
await refresh();
|
||||||
await selectMeeting(payload.id);
|
await selectMeeting(payload.id);
|
||||||
});
|
});
|
||||||
|
|
@ -1496,11 +1906,11 @@ $("#btn-confirm-import-template").addEventListener("click", async () => {
|
||||||
const name = $("#import-template-name").value.trim();
|
const name = $("#import-template-name").value.trim();
|
||||||
const file = $("#import-template-file").files[0];
|
const file = $("#import-template-file").files[0];
|
||||||
if (!name) {
|
if (!name) {
|
||||||
toast("???????", "err");
|
toast("请填写模板名称", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
toast("???????", "err");
|
toast("请选择模板文件", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1511,13 +1921,13 @@ $("#btn-confirm-import-template").addEventListener("click", async () => {
|
||||||
const result = await fetch("/api/templates/import", { method: "POST", body: formData });
|
const result = await fetch("/api/templates/import", { method: "POST", body: formData });
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
const detail = await result.json().catch(() => ({ detail: "Import failed" }));
|
const detail = await result.json().catch(() => ({ detail: "Import failed" }));
|
||||||
toast(`?????${detail.detail}`, "err");
|
toast(`导入失败:${detail.detail}`, "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = await result.json();
|
const payload = await result.json();
|
||||||
closeModal("modal-import-template");
|
closeModal("modal-import-template");
|
||||||
toast(`??????${payload.name}`);
|
toast(`已导入模板:${payload.name}`);
|
||||||
await refresh();
|
await refresh();
|
||||||
syncTemplateSelection(payload.name);
|
syncTemplateSelection(payload.name);
|
||||||
await openTemplate(payload.name);
|
await openTemplate(payload.name);
|
||||||
|
|
@ -1540,7 +1950,7 @@ $("#cfg-key-select").addEventListener("change", async (event) => {
|
||||||
renderSettingsKeyOptions();
|
renderSettingsKeyOptions();
|
||||||
try {
|
try {
|
||||||
await persistSettingsDraft();
|
await persistSettingsDraft();
|
||||||
toast(`??????${$("#cfg-current-model").value}`);
|
toast(`已切换模型:${$("#cfg-current-model").value}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast(error.message, "err");
|
toast(error.message, "err");
|
||||||
}
|
}
|
||||||
|
|
@ -1553,7 +1963,7 @@ $("#btn-save-settings").addEventListener("click", async () => {
|
||||||
const currentName = $("#cfg-key-select").value;
|
const currentName = $("#cfg-key-select").value;
|
||||||
const maxTokens = Number($("#cfg-max-tokens").value || 64000);
|
const maxTokens = Number($("#cfg-max-tokens").value || 64000);
|
||||||
if (!Number.isFinite(maxTokens) || maxTokens < 1) {
|
if (!Number.isFinite(maxTokens) || maxTokens < 1) {
|
||||||
toast("max_tokens ????? 0 ???", "err");
|
toast("max_tokens 必须大于 0", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.settingsDraft.api_profiles = state.settingsDraft.api_profiles.map((item) => (
|
state.settingsDraft.api_profiles = state.settingsDraft.api_profiles.map((item) => (
|
||||||
|
|
@ -1562,7 +1972,7 @@ $("#btn-save-settings").addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await persistSettingsDraft();
|
await persistSettingsDraft();
|
||||||
renderSettingsKeyOptions();
|
renderSettingsKeyOptions();
|
||||||
toast(`??????${$("#cfg-current-model").value}`);
|
toast(`已保存模型配置:${$("#cfg-current-model").value}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast(error.message, "err");
|
toast(error.message, "err");
|
||||||
}
|
}
|
||||||
|
|
@ -1585,11 +1995,11 @@ $("#btn-confirm-add-model").addEventListener("click", async () => {
|
||||||
const apiKey = $("#cfg-add-api-key").value.trim();
|
const apiKey = $("#cfg-add-api-key").value.trim();
|
||||||
const maxTokens = Number($("#cfg-add-max-tokens").value || 64000);
|
const maxTokens = Number($("#cfg-add-max-tokens").value || 64000);
|
||||||
if (!modelName || !apiBaseUrl || !apiKey || !Number.isFinite(maxTokens) || maxTokens < 1) {
|
if (!modelName || !apiBaseUrl || !apiKey || !Number.isFinite(maxTokens) || maxTokens < 1) {
|
||||||
toast("??????????", "err");
|
toast("请完整填写模型配置", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state.settingsDraft.api_profiles.some((item) => item.name === modelName)) {
|
if (state.settingsDraft.api_profiles.some((item) => item.name === modelName)) {
|
||||||
toast("???????", "err");
|
toast("模型名称已存在", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1606,7 +2016,7 @@ $("#btn-confirm-add-model").addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await persistSettingsDraft();
|
await persistSettingsDraft();
|
||||||
closeModal("modal-add-model");
|
closeModal("modal-add-model");
|
||||||
toast(`??????${modelName}`);
|
toast(`已新增模型:${modelName}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast(error.message, "err");
|
toast(error.message, "err");
|
||||||
}
|
}
|
||||||
|
|
@ -1623,7 +2033,7 @@ $("#btn-key-delete").addEventListener("click", async () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await persistSettingsDraft();
|
await persistSettingsDraft();
|
||||||
toast(`??????${targetName}`);
|
toast(`已删除模型:${targetName}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast(error.message, "err");
|
toast(error.message, "err");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -694,6 +694,9 @@ select.btn {
|
||||||
.guide-modal {
|
.guide-modal {
|
||||||
width: min(760px, 100%);
|
width: min(760px, 100%);
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-modal-head {
|
.guide-modal-head {
|
||||||
|
|
@ -701,7 +704,6 @@ select.btn {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-modal-head h3 {
|
.guide-modal-head h3 {
|
||||||
|
|
@ -718,6 +720,7 @@ select.btn {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guide-editor {
|
.guide-editor {
|
||||||
|
|
@ -786,9 +789,10 @@ select.btn {
|
||||||
|
|
||||||
.reparse-modal {
|
.reparse-modal {
|
||||||
width: min(720px, 100%);
|
width: min(720px, 100%);
|
||||||
min-height: 430px;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reparse-modal-body {
|
.reparse-modal-body {
|
||||||
|
|
@ -798,19 +802,34 @@ select.btn {
|
||||||
}
|
}
|
||||||
|
|
||||||
.reparse-modal-textarea {
|
.reparse-modal-textarea {
|
||||||
min-height: 190px;
|
min-height: 176px;
|
||||||
height: 190px;
|
height: 176px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reparse-stream-box {
|
.reparse-stream-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 240px;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reparse-stream-content {
|
.reparse-stream-content {
|
||||||
min-height: 180px;
|
min-height: 152px;
|
||||||
max-height: 180px;
|
max-height: 152px;
|
||||||
|
overflow: hidden;
|
||||||
|
scrollbar-width: none;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reparse-stream-content::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal-process-guide .modal-actions,
|
||||||
|
#modal-reparse-guide .modal-actions {
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px solid rgba(199, 220, 248, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
|
|
|
||||||
|
|
@ -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?v=20260511l">
|
<link rel="stylesheet" href="/assets/styles.css?v=20260511m">
|
||||||
<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>
|
||||||
|
|
@ -25,13 +25,13 @@
|
||||||
<aside class="panel" id="sidebar">
|
<aside class="panel" id="sidebar">
|
||||||
<div class="panel-header sidebar-header">
|
<div class="panel-header sidebar-header">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<span>资源管理器</span>
|
<span>资源管理器</span>
|
||||||
<small id="sidebar-meeting-name">当前会议:未选择</small>
|
<small id="sidebar-meeting-name">当前会议:未选择</small>
|
||||||
<small id="sidebar-meeting-meta">请从左侧选择一个会议开始处理。</small>
|
<small id="sidebar-meeting-meta">请从左侧选择一个会议开始处理。</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-badges">
|
<div class="header-badges">
|
||||||
<span class="badge muted" id="badge-summary">未生成总结</span>
|
<span class="badge muted" id="badge-summary">未生成总结</span>
|
||||||
<span class="badge muted" id="badge-topics">未生成主题 JSON</span>
|
<span class="badge muted" id="badge-topics">未生成主题 JSON</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
@ -44,25 +44,25 @@
|
||||||
<section class="panel panel-main" id="result-panel">
|
<section class="panel panel-main" id="result-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>
|
||||||
<span class="status-light idle" id="left-status-light"></span>
|
<span class="status-light idle" id="left-status-light"></span>
|
||||||
<span class="status-text" id="left-status-text">空闲</span>
|
<span class="status-text" id="left-status-text">空闲</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-meta" id="selected-meeting-tip">未选择会议</div>
|
<div class="status-meta" id="selected-meeting-tip">未选择会议</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<button class="btn primary sm" id="btn-process" disabled>总结</button>
|
<button class="btn primary sm" id="btn-process" disabled>总结</button>
|
||||||
<button class="btn sm" id="btn-toggle-result-edit" disabled>编辑</button>
|
<button class="btn sm" id="btn-toggle-result-edit" disabled>编辑</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body panel-scroll" id="result-body">
|
<div class="panel-body panel-scroll" id="result-body">
|
||||||
<div id="result-empty" class="empty-state">
|
<div id="result-empty" class="empty-state">
|
||||||
<div class="empty-icon">M</div>
|
<div class="empty-icon">M</div>
|
||||||
<p>选择会议后即可查看结果,或直接开始总结。</p>
|
<p>选择会议后即可查看结果,或直接开始总结。</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="processing-indicator" class="processing" hidden>
|
<div id="processing-indicator" class="processing" hidden>
|
||||||
<div class="stream-box" id="stream-box">
|
<div class="stream-box" id="stream-box">
|
||||||
|
|
@ -80,21 +80,21 @@
|
||||||
<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>
|
||||||
<span class="status-light idle" id="right-status-light"></span>
|
<span class="status-light idle" id="right-status-light"></span>
|
||||||
<span class="status-text" id="right-status-text">空闲</span>
|
<span class="status-text" id="right-status-text">空闲</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-meta" id="editor-resource-label">当前资源:模板</div>
|
<div class="status-meta" id="editor-resource-label">当前资源:模板</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<label class="inline-label" for="tpl-select">模板</label>
|
<label class="inline-label" for="tpl-select">模板</label>
|
||||||
<select class="btn sm" id="tpl-select"></select>
|
<select class="btn sm" id="tpl-select"></select>
|
||||||
<button class="btn sm" id="btn-reparse-guide" disabled>解析</button>
|
<button class="btn sm" id="btn-reparse-guide" disabled>解析</button>
|
||||||
<button class="btn sm" id="btn-toggle-side-edit" disabled>编辑</button>
|
<button class="btn sm" id="btn-toggle-side-edit" disabled>编辑</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body panel-scroll" id="template-body">
|
<div class="panel-body panel-scroll" id="template-body">
|
||||||
|
|
@ -110,14 +110,14 @@
|
||||||
<div class="modal-box guide-modal">
|
<div class="modal-box guide-modal">
|
||||||
<div class="guide-modal-head">
|
<div class="guide-modal-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 id="guide-modal-title">模板使用说明</h3>
|
<h3 id="guide-modal-title">模板使用说明</h3>
|
||||||
<p id="guide-modal-subtitle">确认后开始总结,可先补充一些关注要点</p>
|
<p id="guide-modal-subtitle">确认后开始总结,可先补充一些关注要点</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="icon-btn" id="btn-close-process-guide" aria-label="关闭">×</button>
|
<button class="icon-btn" id="btn-close-process-guide" aria-label="关闭">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="guide-modal-body">
|
<div class="guide-modal-body">
|
||||||
<label class="form-field">
|
<label class="form-field">
|
||||||
<span>模板说明</span>
|
<span>模板说明</span>
|
||||||
<textarea id="process-guide-editor" class="guide-editor" spellcheck="false"></textarea>
|
<textarea id="process-guide-editor" class="guide-editor" spellcheck="false"></textarea>
|
||||||
</label>
|
</label>
|
||||||
<div class="guide-inline-prompt" id="reparse-guide-prompt" hidden>
|
<div class="guide-inline-prompt" id="reparse-guide-prompt" hidden>
|
||||||
|
|
@ -132,14 +132,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="form-field">
|
<label class="form-field">
|
||||||
<span>补充说明(可空)</span>
|
<span>补充说明(可空)</span>
|
||||||
<textarea id="process-extra-notes" class="guide-notes" spellcheck="false" placeholder="例如:强调哪些结构要保留、哪些内容要重点解释、哪些措辞需要避免"></textarea>
|
<textarea id="process-extra-notes" class="guide-notes" spellcheck="false" placeholder="例如:强调哪些结构要保留、哪些内容要重点解释、哪些措辞需要避免"></textarea>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn" id="btn-process-guide-edit">编辑</button>
|
<button class="btn" id="btn-process-guide-edit">编辑</button>
|
||||||
<button class="btn" id="btn-process-guide-reparse">重解析</button>
|
<button class="btn" id="btn-process-guide-reparse">重解析</button>
|
||||||
<button class="btn primary" id="btn-process-guide-confirm">确认</button>
|
<button class="btn primary" id="btn-process-guide-confirm">确认</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -148,23 +148,23 @@
|
||||||
<div class="modal-box reparse-modal">
|
<div class="modal-box reparse-modal">
|
||||||
<div class="guide-modal-head">
|
<div class="guide-modal-head">
|
||||||
<div>
|
<div>
|
||||||
<h3 id="reparse-modal-title">模板使用说明补充</h3>
|
<h3 id="reparse-modal-title">模板使用说明补充</h3>
|
||||||
<p id="reparse-modal-subtitle">用于固定模板使用规格,优化最终输出结果(可不填写)</p>
|
<p id="reparse-modal-subtitle">用于固定模板使用规格,优化最终输出结果(可不填写)</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="icon-btn" id="btn-close-reparse-guide" aria-label="关闭">×</button>
|
<button class="icon-btn" id="btn-close-reparse-guide" aria-label="关闭">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="reparse-modal-body" id="reparse-modal-form">
|
<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>
|
<textarea id="reparse-modal-notes" class="guide-inline-textarea reparse-modal-textarea" spellcheck="false" placeholder="例如:强调哪些结构必须保留、哪些段落需要重点解释、哪些措辞需要避免"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="reparse-modal-body" id="reparse-modal-progress" hidden>
|
<div class="reparse-modal-body" id="reparse-modal-progress" hidden>
|
||||||
<div class="stream-box reparse-stream-box">
|
<div class="stream-box reparse-stream-box">
|
||||||
<div class="stream-title" id="reparse-stream-title">正在重新解析模板说明...</div>
|
<div class="stream-title" id="reparse-stream-title">正在重新解析模板说明...</div>
|
||||||
<pre class="stream-content reparse-stream-content" id="reparse-stream-content"></pre>
|
<pre class="stream-content reparse-stream-content" id="reparse-stream-content"></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn" id="btn-reparse-modal-cancel">取消</button>
|
<button class="btn" id="btn-reparse-modal-cancel">取消</button>
|
||||||
<button class="btn primary" id="btn-reparse-modal-confirm">确认解析</button>
|
<button class="btn primary" id="btn-reparse-modal-confirm">确认解析</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -256,6 +256,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
<script type="module" src="/assets/app.js?v=20260511l"></script>
|
<script type="module" src="/assets/app.js?v=20260511m"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
|
|
||||||
|
|
||||||
- 宏观结构可保留,但二级标题模块及三级子项允许按实际内容动态增删,严禁为填充模板强行保留无依据的空白示例项。
|
- 整体结构需严格保留六大核心模块层级(概述、核心议题、关键问答、重要观点、开发决策、后续行动),标题使用 Markdown `##` 与 `###` 规范。
|
||||||
- 所有 `[...]` 占位符、示例性标题(如议题1/2/3、关于产品定位等)及示例描述必须替换为会议真实信息,禁止原样输出。
|
- 所有 `[占位提示]`、示例性文字及括号内的说明语必须在生成前替换为真实会议内容;若无对应信息,直接省略该占位项,严禁原样输出或保留空括号。
|
||||||
- 示例中的固定子标签(如需求分析、技术评估、开发进度等)仅为参考模板,需根据实际讨论维度灵活调整或替换为通用分类。
|
- “核心议题”与“重要观点”下的子标题为动态结构,须根据实际讨论内容灵活增减数量与命名,禁止强制凑齐模板示例的固定数量。
|
||||||
- 问答内容必须严格遵循 `Q: [问题] \n A: [回答]` 的配对结构,支持多条问答独立成组,问答对之间可保留分隔线。
|
- “关键问答”模块需严格保留 `Q:` 与 `A:` 的问答对格式;若包含多个问答对,每组之间必须使用 `---` 水平线分隔。
|
||||||
- 重要观点需统一使用 Markdown 引用块(`> `)格式输出,每个观点独立成段,小标题应随实际议题动态生成。
|
- “重要观点”模块下的每条内容必须使用 Markdown 引用块 `> ` 格式包裹,确保观点与决策的视觉层级独立。
|
||||||
- 后续行动项必须使用 Markdown 复选框列表(`- [ ]`)格式,条目数量严格对应实际待办任务,无明确行动项时该整节应省略。
|
- “后续行动”模块必须使用 Markdown 复选框 `- [ ] ` 格式列出任务项;任务数量与具体内容按实际分配动态生成,不可沿用模板示例条数。
|
||||||
- 决策项、观点项等列表内容允许数量增减,需保持缩进层级与符号一致性,不强制限定固定条目数。
|
- 各模块下的细分条目需保留列表与子项结构,但具体标签名称可根据实际业务语境进行合理替换或泛化,确保语义准确。
|
||||||
- 输出需严格遵循 Markdown 排版规范,确保标题层级、列表、引用块、复选框等具有结构意义的格式在有内容时完整保留。
|
- 模板中未提及或会议内容未覆盖的空白小节、占位示例,一律按“无依据不强行补齐”原则直接删除,不输出空标题、空列表或残留占位符。
|
||||||
|
- 标题中的 Emoji 装饰符号需保留以维持模板风格一致性;若实际场景不适用,可替换为通用符号或仅保留纯文本,但需全篇保持统一。
|
||||||
|
- 占位符替换完成后需进行逻辑校验,确保每个标题/标签与其下方的具体内容严格对应,禁止出现格式错位或内容张冠李戴。
|
||||||
Loading…
Reference in New Issue