@yoooclaw/phone-notifications 1.10.0-beta.21 → 1.10.0-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1133,6 +1133,7 @@ async function runTranscriptionWorkflow(params) {
1133
1133
  durationSec,
1134
1134
  createdAt,
1135
1135
  transcriptsDir,
1136
+ summariesDir,
1136
1137
  recordingId,
1137
1138
  logger
1138
1139
  } = params;
@@ -1157,9 +1158,14 @@ async function runTranscriptionWorkflow(params) {
1157
1158
  const filePath = join15(transcriptsDir, filename);
1158
1159
  writeFileSync12(filePath, markdown, "utf-8");
1159
1160
  logger.info(`[asr] \u8F6C\u5199\u6587\u672C\u5DF2\u5199\u5165: ${filePath}`);
1161
+ const summaryFilename = `${recordingId}.md`;
1162
+ const summaryFilePath = join15(summariesDir, summaryFilename);
1163
+ writeFileSync12(summaryFilePath, summary, "utf-8");
1164
+ logger.info(`[asr] \u6458\u8981\u6587\u672C\u5DF2\u5199\u5165: ${summaryFilePath}`);
1160
1165
  return {
1161
1166
  ok: true,
1162
1167
  transcriptFilename: filename,
1168
+ summaryFilename,
1163
1169
  transcript: result.text ?? "",
1164
1170
  summary,
1165
1171
  title
@@ -7762,6 +7768,8 @@ function registerRecStatus(rec, ctx) {
7762
7768
  audioFile: entry.audioFile ?? null,
7763
7769
  srtFile: entry.srtFile ?? null,
7764
7770
  transcriptFile: entry.transcriptFile ?? null,
7771
+ summaryFile: entry.summaryFile ?? null,
7772
+ title: entry.title ?? null,
7765
7773
  ingestedAt: entry.ingestedAt,
7766
7774
  updatedAt: entry.updatedAt
7767
7775
  }
@@ -8616,7 +8624,7 @@ import {
8616
8624
  writeFileSync as writeFileSync11,
8617
8625
  rmSync as rmSync5
8618
8626
  } from "fs";
8619
- import { join as join13 } from "path";
8627
+ import { join as join13, basename as basename3 } from "path";
8620
8628
 
8621
8629
  // src/recording/state-machine.ts
8622
8630
  var VALID_TRANSITIONS = /* @__PURE__ */ new Map([
@@ -8668,7 +8676,40 @@ function extractAudioExt(ossUrl) {
8668
8676
  var RECORDINGS_DIR = "recordings";
8669
8677
  var AUDIO_DIR = "audio";
8670
8678
  var TRANSCRIPTS_DIR = "transcripts";
8679
+ var SUMMARIES_DIR = "summaries";
8671
8680
  var INDEX_FILE = "index.json";
8681
+ function stripMarkdownFence(markdown) {
8682
+ return markdown.replace(/\r\n/g, "\n");
8683
+ }
8684
+ function deriveTitleFromTranscriptPath(transcriptFile, recordingId) {
8685
+ if (!transcriptFile) return void 0;
8686
+ const name = basename3(transcriptFile, ".md");
8687
+ const prefix = `${recordingId}_`;
8688
+ if (name.startsWith(prefix)) {
8689
+ const derived = name.slice(prefix.length).trim();
8690
+ return derived || void 0;
8691
+ }
8692
+ return void 0;
8693
+ }
8694
+ function extractTranscriptContent(markdown) {
8695
+ const normalized = stripMarkdownFence(markdown);
8696
+ const firstDivider = normalized.indexOf("\n---\n");
8697
+ let body = firstDivider >= 0 ? normalized.slice(firstDivider + "\n---\n".length) : normalized;
8698
+ if (body.startsWith("\n")) {
8699
+ body = body.slice(1);
8700
+ }
8701
+ if (body.startsWith("### \u5173\u952E\u70B9")) {
8702
+ const secondDivider = body.indexOf("\n---\n");
8703
+ if (secondDivider >= 0) {
8704
+ body = body.slice(secondDivider + "\n---\n".length);
8705
+ if (body.startsWith("\n")) {
8706
+ body = body.slice(1);
8707
+ }
8708
+ }
8709
+ }
8710
+ const lines = body.split("\n").filter((line) => !/^\*\*\[关键点 .+\]\*\*$/.test(line.trim())).filter((line) => !/^- \*\*\[关键点 .+\]\*\*$/.test(line.trim()));
8711
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
8712
+ }
8672
8713
  function resolveRecordingStorageDir(ctx, logger) {
8673
8714
  const stateRecDir = join13(
8674
8715
  ctx.stateDir,
@@ -8699,17 +8740,20 @@ var RecordingStorage = class {
8699
8740
  this.dir = dir;
8700
8741
  this.audioDir = join13(dir, AUDIO_DIR);
8701
8742
  this.transcriptsDir = join13(dir, TRANSCRIPTS_DIR);
8743
+ this.summariesDir = join13(dir, SUMMARIES_DIR);
8702
8744
  this.indexPath = join13(dir, INDEX_FILE);
8703
8745
  }
8704
8746
  dir;
8705
8747
  audioDir;
8706
8748
  transcriptsDir;
8749
+ summariesDir;
8707
8750
  indexPath;
8708
8751
  index = { recordings: [] };
8709
8752
  /** 初始化目录结构并加载索引 */
8710
8753
  async init() {
8711
8754
  mkdirSync10(this.audioDir, { recursive: true });
8712
8755
  mkdirSync10(this.transcriptsDir, { recursive: true });
8756
+ mkdirSync10(this.summariesDir, { recursive: true });
8713
8757
  this.loadIndex();
8714
8758
  this.logger.info(
8715
8759
  `\u5F55\u97F3\u5B58\u50A8\u5DF2\u521D\u59CB\u5316: ${this.dir}\uFF08\u5171 ${this.index.recordings.length} \u6761\u8BB0\u5F55\uFF09`
@@ -8723,6 +8767,10 @@ var RecordingStorage = class {
8723
8767
  getTranscriptsDir() {
8724
8768
  return this.transcriptsDir;
8725
8769
  }
8770
+ /** 获取摘要目录路径 */
8771
+ getSummariesDir() {
8772
+ return this.summariesDir;
8773
+ }
8726
8774
  // ─── 录音入库 ───
8727
8775
  /**
8728
8776
  * 收到 recordings.sync 后,将元数据写入索引。
@@ -8733,9 +8781,17 @@ var RecordingStorage = class {
8733
8781
  const id = recordingId;
8734
8782
  const existing = this.findById(id);
8735
8783
  if (existing) {
8784
+ if (existing.transcriptFile) {
8785
+ rmSync5(join13(this.dir, existing.transcriptFile), { force: true });
8786
+ }
8787
+ if (existing.summaryFile) {
8788
+ rmSync5(join13(this.dir, existing.summaryFile), { force: true });
8789
+ }
8736
8790
  existing.metadata = metadata;
8737
8791
  existing.status = "syncing_openclaw";
8738
8792
  existing.transcriptFile = void 0;
8793
+ existing.summaryFile = void 0;
8794
+ existing.title = void 0;
8739
8795
  existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
8740
8796
  this.logger.info(`\u5F55\u97F3\u5143\u6570\u636E\u5DF2\u66F4\u65B0: ${id}`);
8741
8797
  } else {
@@ -8802,6 +8858,50 @@ var RecordingStorage = class {
8802
8858
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
8803
8859
  this.saveIndex();
8804
8860
  }
8861
+ /**
8862
+ * 记录摘要文件路径
8863
+ */
8864
+ setSummaryFile(recordingId, filename) {
8865
+ const entry = this.findById(recordingId);
8866
+ if (!entry) return;
8867
+ const nextSummaryFile = `${SUMMARIES_DIR}/${filename}`;
8868
+ if (entry.summaryFile && entry.summaryFile !== nextSummaryFile) {
8869
+ rmSync5(join13(this.dir, entry.summaryFile), { force: true });
8870
+ }
8871
+ entry.summaryFile = nextSummaryFile;
8872
+ entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
8873
+ this.saveIndex();
8874
+ }
8875
+ /**
8876
+ * 记录转写标题
8877
+ */
8878
+ setTitle(recordingId, title) {
8879
+ const entry = this.findById(recordingId);
8880
+ if (!entry) return;
8881
+ entry.title = title?.trim() || void 0;
8882
+ entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
8883
+ this.saveIndex();
8884
+ }
8885
+ /**
8886
+ * 读取摘要文本
8887
+ */
8888
+ readSummary(recordingId) {
8889
+ const entry = this.findById(recordingId);
8890
+ if (!entry?.summaryFile) return void 0;
8891
+ const summary = this.readRelativeTextFile(entry.summaryFile);
8892
+ return summary?.trim() || void 0;
8893
+ }
8894
+ /**
8895
+ * 从 transcript Markdown 中提取正文。
8896
+ */
8897
+ readTranscript(recordingId) {
8898
+ const entry = this.findById(recordingId);
8899
+ if (!entry?.transcriptFile) return void 0;
8900
+ const markdown = this.readRelativeTextFile(entry.transcriptFile);
8901
+ if (!markdown) return void 0;
8902
+ const transcript = extractTranscriptContent(markdown);
8903
+ return transcript || void 0;
8904
+ }
8805
8905
  // ─── 查询 ───
8806
8906
  findById(id) {
8807
8907
  return this.index.recordings.find((r) => r.id === id);
@@ -8851,10 +8951,15 @@ var RecordingStorage = class {
8851
8951
  const transcriptPath = join13(this.dir, entry.transcriptFile);
8852
8952
  rmSync5(transcriptPath, { force: true });
8853
8953
  }
8954
+ if (entry.summaryFile) {
8955
+ const summaryPath = join13(this.dir, entry.summaryFile);
8956
+ rmSync5(summaryPath, { force: true });
8957
+ }
8854
8958
  if (opts?.localOnly) {
8855
8959
  entry.audioFile = void 0;
8856
8960
  entry.srtFile = void 0;
8857
8961
  entry.transcriptFile = void 0;
8962
+ entry.summaryFile = void 0;
8858
8963
  entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
8859
8964
  this.saveIndex();
8860
8965
  this.logger.info(`\u5F55\u97F3\u672C\u5730\u6587\u4EF6\u5DF2\u5220\u9664\uFF08\u4FDD\u7559\u7D22\u5F15\uFF09: ${recordingId}`);
@@ -8889,6 +8994,12 @@ var RecordingStorage = class {
8889
8994
  const safeSummary = summary.replace(/[/\\:*?"<>|]/g, "").trim().slice(0, 20);
8890
8995
  return safeSummary ? `${recordingId}_${safeSummary}.md` : `${recordingId}.md`;
8891
8996
  }
8997
+ /**
8998
+ * 生成摘要文件名
8999
+ */
9000
+ buildSummaryFilename(recordingId) {
9001
+ return `${recordingId}.md`;
9002
+ }
8892
9003
  /**
8893
9004
  * 获取音频文件的绝对路径。ossUrl 用于推断文件扩展名
8894
9005
  */
@@ -8901,6 +9012,12 @@ var RecordingStorage = class {
8901
9012
  getSrtFilePath(recordingId) {
8902
9013
  return join13(this.audioDir, this.buildSrtFilename(recordingId));
8903
9014
  }
9015
+ /**
9016
+ * 获取摘要文件的绝对路径
9017
+ */
9018
+ getSummaryFilePath(recordingId) {
9019
+ return join13(this.summariesDir, this.buildSummaryFilename(recordingId));
9020
+ }
8904
9021
  // ─── Persistence ───
8905
9022
  loadIndex() {
8906
9023
  if (!existsSync17(this.indexPath)) {
@@ -8910,6 +9027,7 @@ var RecordingStorage = class {
8910
9027
  try {
8911
9028
  const raw = JSON.parse(readFileSync18(this.indexPath, "utf-8"));
8912
9029
  if (raw && Array.isArray(raw.recordings)) {
9030
+ let needsRewrite = false;
8913
9031
  const normalized = raw.recordings.filter((entry) => entry && typeof entry === "object").map((entry) => {
8914
9032
  const compacted = {
8915
9033
  id: entry.id,
@@ -8923,13 +9041,37 @@ var RecordingStorage = class {
8923
9041
  if (typeof entry.transcriptFile === "string") {
8924
9042
  compacted.transcriptFile = entry.transcriptFile;
8925
9043
  }
9044
+ if (typeof entry.summaryFile === "string") {
9045
+ compacted.summaryFile = entry.summaryFile;
9046
+ } else if (typeof entry.summary === "string" && entry.summary.trim()) {
9047
+ const summaryFilename = this.buildSummaryFilename(entry.id);
9048
+ writeFileSync11(
9049
+ join13(this.summariesDir, summaryFilename),
9050
+ entry.summary.trim(),
9051
+ "utf-8"
9052
+ );
9053
+ compacted.summaryFile = `${SUMMARIES_DIR}/${summaryFilename}`;
9054
+ needsRewrite = true;
9055
+ }
9056
+ if (typeof entry.title === "string" && entry.title.trim()) {
9057
+ compacted.title = entry.title.trim();
9058
+ } else {
9059
+ const derivedTitle = deriveTitleFromTranscriptPath(
9060
+ compacted.transcriptFile,
9061
+ compacted.id
9062
+ );
9063
+ if (derivedTitle) {
9064
+ compacted.title = derivedTitle;
9065
+ needsRewrite = true;
9066
+ }
9067
+ }
8926
9068
  return compacted;
8927
9069
  });
8928
9070
  const hadLargeFields = raw.recordings.some(
8929
9071
  (entry) => entry && typeof entry === "object" && ("transcript" in entry || "summary" in entry)
8930
9072
  );
8931
9073
  this.index = { recordings: normalized };
8932
- if (hadLargeFields) {
9074
+ if (hadLargeFields || needsRewrite) {
8933
9075
  this.saveIndex();
8934
9076
  }
8935
9077
  } else {
@@ -8940,6 +9082,13 @@ var RecordingStorage = class {
8940
9082
  this.index = { recordings: [] };
8941
9083
  }
8942
9084
  }
9085
+ readRelativeTextFile(relativePath) {
9086
+ try {
9087
+ return readFileSync18(join13(this.dir, relativePath), "utf-8");
9088
+ } catch {
9089
+ return void 0;
9090
+ }
9091
+ }
8943
9092
  saveIndex() {
8944
9093
  writeFileSync11(
8945
9094
  this.indexPath,
@@ -9151,11 +9300,16 @@ async function triggerTranscription(recordingId, storage, asrConfig, logger, opt
9151
9300
  durationSec: entry.metadata.duration_sec,
9152
9301
  createdAt: entry.metadata.created_at,
9153
9302
  transcriptsDir: storage.getTranscriptsDir(),
9303
+ summariesDir: storage.getSummariesDir(),
9154
9304
  recordingId,
9155
9305
  logger
9156
9306
  });
9157
9307
  if (result.ok && result.transcriptFilename) {
9158
9308
  storage.setTranscriptFile(recordingId, result.transcriptFilename);
9309
+ if (result.summaryFilename) {
9310
+ storage.setSummaryFile(recordingId, result.summaryFilename);
9311
+ }
9312
+ storage.setTitle(recordingId, result.title);
9159
9313
  storage.updateStatus(recordingId, "transcribed");
9160
9314
  emitRecordingStatus(
9161
9315
  recordingId,
@@ -10959,7 +11113,7 @@ function buildRecordingListItem(entry) {
10959
11113
  updatedAt: entry.updatedAt
10960
11114
  };
10961
11115
  }
10962
- function buildRecordingDetail(entry) {
11116
+ function buildRecordingDetail(entry, extras) {
10963
11117
  return {
10964
11118
  recordingId: entry.id,
10965
11119
  name: entry.metadata.name,
@@ -10974,6 +11128,10 @@ function buildRecordingDetail(entry) {
10974
11128
  audioFile: entry.audioFile,
10975
11129
  srtFile: entry.srtFile,
10976
11130
  transcriptFile: entry.transcriptFile,
11131
+ summaryFile: entry.summaryFile,
11132
+ title: entry.title,
11133
+ summary: extras?.summary,
11134
+ transcript: extras?.transcript,
10977
11135
  ingestedAt: entry.ingestedAt,
10978
11136
  updatedAt: entry.updatedAt
10979
11137
  };
@@ -11081,7 +11239,10 @@ function registerRecordingInterfaces(deps) {
11081
11239
  });
11082
11240
  return;
11083
11241
  }
11084
- const { recordingId } = params;
11242
+ const {
11243
+ recordingId,
11244
+ includeTranscriptContent
11245
+ } = params;
11085
11246
  if (!recordingId) {
11086
11247
  respond(false, null, {
11087
11248
  code: "INVALID_PARAMS",
@@ -11097,7 +11258,13 @@ function registerRecordingInterfaces(deps) {
11097
11258
  });
11098
11259
  return;
11099
11260
  }
11100
- respond(true, buildRecordingDetail(entry));
11261
+ respond(
11262
+ true,
11263
+ buildRecordingDetail(entry, {
11264
+ summary: recordingStorage.readSummary(recordingId),
11265
+ transcript: includeTranscriptContent ? recordingStorage.readTranscript(recordingId) : void 0
11266
+ })
11267
+ );
11101
11268
  }
11102
11269
  );
11103
11270
  registerGatewayMethod(