新增状态栏

new_test
Bifang 2026-05-09 17:08:32 +08:00
parent f5a22c0a7f
commit f0add35030
5 changed files with 169 additions and 65 deletions

View File

@ -1,44 +1,49 @@
{
"sub_topics": [
{
"title": "宽带安装维护与核心运营指标通报",
"time_interval": "00:00:01 - 00:03:51",
"overview": "会议通报了宽带安装与上门服务的进度受天气影响上周上门量达580累计进度滞后于3000户目标。重点分析了弱光0.51、三代终端年度目标5.5、转化率87.35%及退单率6.53%)等关键指标,指出退单主要受用户原因、覆盖不足及天气影响。会议建议将阶段性目标纳入运维考核,并优化审核流程以降低退单率。"
"title": "宽带装维进度与关键指标汇报",
"time_interval": "00:00:01 - 00:02:06",
"overview": "会议首先汇报了宽带装维上门量与安装进度受天气影响当周上门量约580户累计进度35%距离3000户目标仍存差距。同时通报弱光指数为0.51目标0.5、三代终端目标压降至5.5FPTR指标已达标主动过境指标相对靠后。"
},
{
"title": "专线护航、网络质量优化与基站攻坚进展",
"title": "九零工程转化与退单率分析",
"time_interval": "00:02:07 - 00:03:51",
"overview": "针对九零工程转化率当前87.35%目标90%与退单率当前6.53%目标低于7.5%进行拆解指出天气、用户原因及覆盖问题是影响转化与退单的主要因素。会议提出需优化B to C协调流程加强退单审核意识并将该指标纳入综合运维考核。"
},
{
"title": "专线护航、PCDN治理与基站运维",
"time_interval": "00:03:53 - 00:08:04",
"overview": "针对PCDN专线恶化问题已上报市公司分析学校出口IP并协同限速同时推进5G基站参数调整试点以解决漏话问题。专线护航方面118条专线中存量73条已完成56条巡检剩余17条确保月底全量完成上周共收报障33件其中9件为网络相关主要为光缆问题。超频基站攻坚发现3处故障已分别通过修复铁塔开关电源和更换传输光纤恢复。"
"overview": "重点汇报118条专线巡检进度已完成56条剩余17条月底清零并指出PCDN在学校端持续恶化正协同学校进行IP限速分析。同时通报超频基站故障已恢复当周收到报障33件网络相关9件以光缆问题为主。"
},
{
"title": "二级站点拆除计划与网络评估体系分解",
"time_interval": "00:08:07 - 00:09:35",
"overview": "金沙管家二级站点拆除工作推进顺利剩余75站预计于4月15日前完成全量拆除、下电及电费调整。工程下层项目整体正常开展综合网络运维评估体系已内部收悉计划下周进行指标分解与目标制定。东环网络质量保持良好水平并在全市范围内进行了经验交流。"
"title": "二级基站拆除与网络质量评估体系落地",
"time_interval": "00:08:07 - 00:10:00",
"overview": "通报二级基站拆除进展剩余121站中已拆除46站预计4月15日前完成剩余75站拆除及电费调整。会议提及网络质量综合评估体系已内部下发下周将分解制定目标并同步推进花果山项目与微托无人机验收等2025年剩余工作。"
},
{
"title": "综合部专项工作推进与后勤设施改造",
"time_interval": "00:09:46 - 00:12:39",
"overview": "综合部汇报了25年剩余2项重点工作的推进情况上周已完成建委框架清单整理、投资计划评估及收入地图提交等5项工作。后勤方面因内部打印机报价偏差过大正洽谈外部打印店以保障招投标需求同时推进食堂改造预算约29.8万)及下半年引入自饮机以节约成本。工会经费面临压减,需以更优方式落实软性工程改造。"
"title": "综合部重点工作与后勤保障安排",
"time_interval": "00:10:01 - 00:14:53",
"overview": "汇报上周完成5项工作含框架清单、投资评估、收入地图等及7项推进中工作重点说明因内外部打印价格差异大拟采用外派打印保障招投标。同时通报工会经费压减背景下推进食堂改造约29.8万元)与自饮机引入,并筹备第四届体育文化节。"
},
{
"title": "年度工会活动规划与第四届体育文化节筹备",
"time_interval": "00:12:40 - 00:18:54",
"overview": "会议规划了全年六场大型体育活动并重点部署了5月底举办的第四届体育文化节主题为“河川佳节穿越巅峰”。因两名主力选手肌肉拉伤无法参赛现需各部门协调补充人员计划从一线生产部门及AI运输中心抽调共计9人组成25人方阵。参会人员将获得绩效加分需于今日确定名单以便统一采购服装并开展排练。"
"title": "主题教育开展与招待费管控通报",
"time_interval": "00:14:54 - 00:22:49",
"overview": "通报主题教育第一期简报发布及基层党组织学习情况完成年度招待费信息公开报备。分析2025年招待费超年初预算14%其中综合部去年超支107%2026年已调整预算。会议要求政企部统筹对外招待避免过度依赖单一客户经理并加快历史费用报销。"
},
{
"title": "主题教育落实、招待费管控与区级评优申报",
"time_interval": "00:18:55 - 00:26:52",
"overview": "主题教育首期简报已发布基层党组织学习工作已按期完成2025年招待费超年初预算14%其中综合部历史超支明显2026年已调整预算并强调对外招待需统筹分配。会议讨论了申报“河川区担当作为先进集体/个人”的可行性,指出该评选竞争激烈且需推荐,建议提前核实资格避免资源浪费。"
"title": "区级评优申报推进与商客市场经营汇报",
"time_interval": "00:22:50 - 00:31:43",
"overview": "跟进河川区担当作为评优申报流程明确需对接区委办与人力社保局确认资格截止3月31日。商客市场方面2月营收88.5万环比增1万价值拓展完成115但个别商客经理周基础业务仅2笔需加强签约落实与价值拓展。"
},
{
"title": "商客市场经营分析与二期项目拆迁进展",
"time_interval": "00:26:55 - 00:32:04",
"overview": "商客市场2月收入达88.5万元环比增长1万元城北和南溪改增幅超2万元但价值拓展仅完成115两名商客经理基础业务量偏低。二期项目拆迁方面已完成1145户重点通过二次融合套餐将低价值合约提升至50元档位本周开展5场社区及单位营销但签约转化未达预期需加强后续落实。"
"title": "考核指标督办与市场满意度整改要求",
"time_interval": "00:31:44 - 00:42:25",
"overview": "强调工作回复需量化且日清日结要求下周内提交“上课”指标具体保障措施。针对满意度测评结果批评相关部门思想松懈要求市场部本周内汇报招聘、农村渠道及营销方案进度并严格落实“80/20两率”3月底目标每日微信报送日报。"
},
{
"title": "工作执行力整顿与满意度/KPI考核部署",
"time_interval": "00:32:05 - 00:46:16",
"overview": "领导严厉批评部分工作拖延及缺乏具体保障措施的现象要求养成“日清日结”习惯并量化汇报结果。针对满意度测评指出政企部前期工作松懈导致指标落后要求市场部立即制定挽单方案并按市公司规范执行。会议明确今年KPI将挂钩工信部有责投诉及离网率要求各部门提前摸排考核风险强化执行力以确保年度考核达标。"
"title": "运动会筹备与年度考核工作部署",
"time_interval": "00:42:26 - 00:46:16",
"overview": "确认体育文化节方阵人员调配与排练安排,强调执行力与结果导向。领导着重部署四季度年度考核工作,要求各部门提前摸排考核规则(如阿普提升等指标),主动与市公司沟通对齐,避免起跑落后,统筹资源确保年度考核公平达标。"
}
]
}

View File

@ -91,18 +91,10 @@ function meetingById(meetingId) {
return state.meetings.find((item) => item.id === meetingId) || null;
}
function templateMetaByName(name) {
return state.templates.find((item) => item.name === name) || null;
}
function isMarkdownFile(name = "") {
return name.toLowerCase().endsWith(".md");
}
function templateNameFromGuideName(name) {
return name;
}
function renderMeetingStatus(meeting) {
const name = $("#sidebar-meeting-name");
const meta = $("#sidebar-meeting-meta");
@ -132,16 +124,35 @@ function renderMeetingStatus(meeting) {
topicsBadge.className = meeting.has_topics ? "badge" : "badge muted";
}
function setRightStatus(isBusy, text = "空闲") {
const light = $("#side-status-light");
const textEl = $("#side-status-text");
light.classList.toggle("idle", !isBusy);
light.classList.toggle("busy", isBusy);
textEl.textContent = text;
}
function resetRightStream() {
$("#side-status-stream").textContent = "";
}
function updateRightStream(text) {
const lines = text.replace(/\r\n/g, "\n").split("\n").filter((line) => line.trim() !== "");
$("#side-status-stream").textContent = lines.slice(-2).join("\n");
}
function resetProcessingStream() {
$("#stream-box").style.display = "none";
$("#stream-title").textContent = "";
$("#stream-content").textContent = "";
setRightStatus(false, "空闲");
resetRightStream();
}
function showResultEmpty() {
state.resultEditMode = false;
$("#btn-toggle-result-edit").disabled = true;
$("#btn-toggle-result-edit").textContent = "编辑结果";
$("#btn-toggle-result-edit").textContent = "编辑";
$("#result-editor").style.display = "none";
$("#result-md").style.display = "none";
$("#processing-indicator").hidden = true;
@ -152,7 +163,7 @@ function setResultEditMode(editMode) {
state.resultEditMode = editMode;
$("#result-editor").style.display = editMode ? "block" : "none";
$("#result-md").style.display = editMode ? "none" : "block";
$("#btn-toggle-result-edit").textContent = editMode ? "保存结果" : "编辑结果";
$("#btn-toggle-result-edit").textContent = editMode ? "保存" : "编辑";
}
function showResult(markdown) {
@ -374,13 +385,13 @@ function updateGuideButton(resource) {
const button = $("#btn-reparse-guide");
if (!resource || (resource.type !== "template" && resource.type !== "template-guide")) {
button.disabled = true;
button.textContent = "生成解析说明";
button.textContent = "解析";
return;
}
const hasGuide = resource.type === "template-guide" || Boolean(resource.hasGuide);
button.disabled = false;
button.textContent = hasGuide ? "重新生成说明" : "生成解析说明";
button.textContent = hasGuide ? "重解析" : "解析";
}
function applyRightResource(resource) {
@ -388,7 +399,7 @@ function applyRightResource(resource) {
$("#editor-resource-label").textContent = `当前资源:${resource.label}`;
$("#side-editor").value = resource.content || "";
$("#btn-toggle-side-edit").disabled = !resource.editable;
$("#btn-toggle-side-edit").textContent = resource.editable ? "编辑资源" : "只读资源";
$("#btn-toggle-side-edit").textContent = resource.editable ? "编辑" : "只读";
updateGuideButton(resource);
state.rightEditMode = false;
renderSidePreview(resource);
@ -406,7 +417,7 @@ function setRightEditMode(editMode) {
return;
}
$("#btn-toggle-side-edit").textContent = editMode ? "保存资源" : "编辑资源";
$("#btn-toggle-side-edit").textContent = editMode ? "保存" : "编辑";
if (editMode) {
$("#side-preview").style.display = "none";
$("#side-plain-preview").style.display = "none";
@ -439,13 +450,12 @@ async function openTemplate(name, treeKey = `file:templates/${name}`) {
}
async function openTemplateGuide(name, treeKey = `file:template_guides/${name}`) {
const templateName = templateNameFromGuideName(name);
const data = await api(`/api/templates/${encodeURIComponent(templateName)}/guide`);
const data = await api(`/api/templates/${encodeURIComponent(name)}/guide`);
await openRightResource({
type: "template-guide",
name,
templateName,
label: `模板说明 / ${templateName}`,
templateName: name,
label: `模板说明 / ${name}`,
content: data.content,
hasGuide: true,
editable: true,
@ -717,10 +727,13 @@ $("#btn-process").addEventListener("click", () => {
state.processing = true;
$("#btn-process").disabled = true;
$("#btn-process").textContent = "处理中";
showProcessingView();
$("#stream-box").style.display = "block";
$("#stream-title").textContent = "第一阶段:结构化主题...";
$("#stream-content").textContent = "";
setRightStatus(true, "工作中");
resetRightStream();
const source = new EventSource(
`/api/meetings/${state.meetingId}/process?template_name=${encodeURIComponent(state.templateName)}`,
@ -739,13 +752,13 @@ $("#btn-process").addEventListener("click", () => {
if (payload.type === "status") {
if (payload.data === "preprocessing") {
$("#stream-title").textContent = "第一阶段:结构化主题...";
streamAcc = "";
} else if (payload.data === "preprocessing_done") {
$("#stream-title").textContent = "主题提取完成,开始生成会议总结...";
} else if (payload.data === "summarizing") {
$("#stream-title").textContent = "第二阶段:生成会议总结...";
streamAcc = "";
$("#stream-content").textContent = "";
resetRightStream();
}
return;
}
@ -754,6 +767,7 @@ $("#btn-process").addEventListener("click", () => {
const { data } = payload;
streamAcc += data.text || "";
$("#stream-content").textContent = streamAcc.replace(/\r\n/g, "\n").split("\n").slice(-4).join("\n");
updateRightStream(streamAcc);
if (data.stage === 2 && data.chunk_type === "content") {
resultAcc += data.text || "";
}
@ -764,6 +778,7 @@ $("#btn-process").addEventListener("click", () => {
source.close();
state.processing = false;
$("#btn-process").disabled = false;
$("#btn-process").textContent = "处理";
showResult(payload.data?.result || resultAcc || "");
await refresh();
toast("会议处理完成");
@ -774,6 +789,7 @@ $("#btn-process").addEventListener("click", () => {
source.close();
state.processing = false;
$("#btn-process").disabled = false;
$("#btn-process").textContent = "处理";
resetProcessingStream();
$("#processing-indicator").hidden = true;
toast(`处理失败:${payload.data}`, "err");
@ -784,6 +800,7 @@ $("#btn-process").addEventListener("click", () => {
source.close();
state.processing = false;
$("#btn-process").disabled = false;
$("#btn-process").textContent = "处理";
resetProcessingStream();
$("#processing-indicator").hidden = true;
toast("处理连接中断", "err");
@ -801,14 +818,15 @@ $("#btn-toggle-result-edit").addEventListener("click", async () => {
}
const content = $("#result-editor").value;
$("#btn-toggle-result-edit").disabled = true;
await api(`/api/meetings/${state.meetingId}/summary`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content }),
});
showResult(content);
await refresh();
toast("会议结果已保存");
$("#btn-toggle-result-edit").disabled = false;
toast("结果已保存");
});
$("#btn-toggle-side-edit").addEventListener("click", async () => {
@ -823,6 +841,8 @@ $("#btn-toggle-side-edit").addEventListener("click", async () => {
}
const content = $("#side-editor").value;
$("#btn-toggle-side-edit").disabled = true;
if (resource.type === "template") {
await api(`/api/templates/${encodeURIComponent(resource.name)}`, {
method: "PUT",
@ -845,6 +865,7 @@ $("#btn-toggle-side-edit").addEventListener("click", async () => {
resource.content = content;
setRightEditMode(false);
$("#btn-toggle-side-edit").disabled = false;
toast("资源已保存");
});
@ -853,13 +874,6 @@ $("#tpl-select").addEventListener("change", async (event) => {
await openTemplate(event.target.value);
});
$("#btn-open-template").addEventListener("click", async () => {
if (!state.templateName) {
return;
}
await openTemplate(state.templateName);
});
$("#btn-reparse-guide").addEventListener("click", async () => {
const resource = state.rightResource;
if (!resource || (resource.type !== "template" && resource.type !== "template-guide")) {
@ -867,10 +881,17 @@ $("#btn-reparse-guide").addEventListener("click", async () => {
}
const templateName = resource.templateName || resource.name;
$("#btn-reparse-guide").disabled = true;
setRightStatus(true, "工作中");
resetRightStream();
const result = await api(`/api/templates/${encodeURIComponent(templateName)}/guide/reparse`, {
method: "POST",
});
toast(`模板说明已重新解析:${templateName}`);
setRightStatus(false, "空闲");
$("#btn-reparse-guide").disabled = false;
toast(`说明已更新:${templateName}`);
await refresh();
await openTemplateGuide(result.name);
});
@ -957,6 +978,8 @@ document.addEventListener("DOMContentLoaded", async () => {
applySavedLayout();
initResize("gutter-1", "sidebar", "result-panel");
initResize("gutter-2", "result-panel", "template-panel");
setRightStatus(false, "空闲");
resetRightStream();
try {
await refresh();

View File

@ -56,6 +56,64 @@ textarea {
font-size: 13px;
}
.side-status {
margin-top: 6px;
display: grid;
gap: 4px;
}
.side-status-row {
display: flex;
align-items: center;
gap: 8px;
}
.side-status-label,
.side-status-meta {
color: var(--muted);
font-size: 13px;
}
.side-status-text {
font-size: 13px;
color: var(--text);
}
.status-light {
width: 10px;
height: 10px;
border-radius: 999px;
display: inline-block;
flex: 0 0 auto;
}
.status-light.idle {
background: #2db36c;
box-shadow: 0 0 0 4px rgba(45, 179, 108, 0.14);
}
.status-light.busy {
width: 52px;
height: 10px;
border-radius: 999px;
background:
linear-gradient(90deg, rgba(47, 128, 237, 0.18), rgba(47, 128, 237, 0.55), rgba(47, 128, 237, 0.18));
background-size: 200% 100%;
box-shadow: inset 0 0 0 1px rgba(47, 128, 237, 0.12);
animation: status-marquee 1.2s linear infinite;
}
.side-status-stream {
margin: 2px 0 0;
min-height: 2.9em;
max-width: 100%;
color: #557cad;
white-space: pre-wrap;
line-height: 1.45;
font-size: 12px;
font-family: var(--mono);
}
.app-shell {
height: var(--app-h);
padding: 24px;
@ -540,6 +598,15 @@ select.btn {
padding: 18px 20px;
}
@keyframes status-marquee {
from {
background-position: 200% 0;
}
to {
background-position: 0 0;
}
}
#result-md,
#side-preview,
#side-plain-preview,

View File

@ -47,8 +47,8 @@
<small id="selected-meeting-tip">未选择会议</small>
</div>
<div class="toolbar">
<button class="btn primary sm" id="btn-process" disabled>处理当前会议</button>
<button class="btn sm" id="btn-toggle-result-edit" disabled>编辑结果</button>
<button class="btn primary sm" id="btn-process" disabled>处理</button>
<button class="btn sm" id="btn-toggle-result-edit" disabled>编辑</button>
</div>
</div>
<div class="panel-body panel-scroll" id="result-body">
@ -73,14 +73,21 @@
<div class="panel-header">
<div class="panel-heading">
<span>右侧编辑区</span>
<small id="editor-resource-label">当前资源:模板</small>
<div class="side-status" id="side-status">
<div class="side-status-row">
<span class="side-status-label">当前状态</span>
<span class="status-light idle" id="side-status-light"></span>
<span class="side-status-text" id="side-status-text">空闲</span>
</div>
<div class="side-status-meta" id="editor-resource-label">当前资源:模板</div>
<pre class="side-status-stream" id="side-status-stream"></pre>
</div>
</div>
<div class="toolbar">
<label class="inline-label" for="tpl-select">模板选择</label>
<select class="btn sm" id="tpl-select"></select>
<button class="btn sm" id="btn-open-template">打开模板</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-reparse-guide" disabled>解析</button>
<button class="btn sm" id="btn-toggle-side-edit" disabled>编辑</button>
</div>
</div>
<div class="panel-body panel-scroll" id="template-body">

View File

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