lookatstudy-mobile 0.13.0 → 0.15.0

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.
Files changed (70) hide show
  1. package/install-termux.sh +6 -14
  2. package/package.json +1 -1
  3. package/server.cjs +194 -23
  4. package/web/assets/{abnfDiagram-N423BO3Z-wFTVk74C.js → abnfDiagram-N423BO3Z-BXpGdNuJ.js} +1 -1
  5. package/web/assets/api-web-CgR5Mh04.js +1 -0
  6. package/web/assets/{arc-DrtveQeQ.js → arc-D3nd0e6x.js} +1 -1
  7. package/web/assets/{architectureDiagram-T3A2C74G-iSGM_chc.js → architectureDiagram-T3A2C74G-_KSRK-eF.js} +1 -1
  8. package/web/assets/{blockDiagram-VBNYF7ZC-Cormy30B.js → blockDiagram-VBNYF7ZC-CTH5HFTT.js} +1 -1
  9. package/web/assets/{c4Diagram-5PPSVZJV-D1Y3B0gV.js → c4Diagram-5PPSVZJV-A0wHAp7g.js} +1 -1
  10. package/web/assets/channel-DUHQbrVw.js +1 -0
  11. package/web/assets/{chunk-2GRJ4B5K-J_o-lKOF.js → chunk-2GRJ4B5K-DqNyC6-z.js} +1 -1
  12. package/web/assets/{chunk-2Q5K7J3B-gPWW6y-A.js → chunk-2Q5K7J3B-CdnPfIqd.js} +1 -1
  13. package/web/assets/{chunk-5RXB4S5H-yigaUOuY.js → chunk-5RXB4S5H-BGhsRWsR.js} +1 -1
  14. package/web/assets/{chunk-5VM5RSS4-DEnbC3cS.js → chunk-5VM5RSS4-B52gSt0J.js} +1 -1
  15. package/web/assets/{chunk-6Q2QTUOP-ComVOTYh.js → chunk-6Q2QTUOP-sn207b1U.js} +1 -1
  16. package/web/assets/{chunk-GF5L2VYU-DIOiMyrX.js → chunk-GF5L2VYU-DQ35exrC.js} +1 -1
  17. package/web/assets/{chunk-JWPE2WC7-m78qF4H_.js → chunk-JWPE2WC7-V2anUJlE.js} +1 -1
  18. package/web/assets/{chunk-KBJHAD2P-Clh4Yr08.js → chunk-KBJHAD2P-f5DAZkkL.js} +1 -1
  19. package/web/assets/{chunk-RYQCIY6F-BMETLjTZ.js → chunk-RYQCIY6F-C4aIfT0J.js} +1 -1
  20. package/web/assets/{chunk-XXDRQBXY-DyGYyX9Z.js → chunk-XXDRQBXY-BnihJuYl.js} +1 -1
  21. package/web/assets/classDiagram-JCYQIIEL-CRVgeWuu.js +1 -0
  22. package/web/assets/classDiagram-v2-OCEON4UE-CRVgeWuu.js +1 -0
  23. package/web/assets/{cose-bilkent-JH36ORCC-BO1RXGb2.js → cose-bilkent-JH36ORCC-BnJjiie5.js} +1 -1
  24. package/web/assets/{cynefin-VYW2F7L2-DGYmCJ7v.js → cynefin-VYW2F7L2-CG6JQnIq.js} +1 -1
  25. package/web/assets/{cynefinDiagram-MW4NZA55-iSEBLol5.js → cynefinDiagram-MW4NZA55-Bpm2ec5V.js} +1 -1
  26. package/web/assets/{dagre-VZM6K2ZE-ByPA0378.js → dagre-VZM6K2ZE-Dp-oDNQg.js} +1 -1
  27. package/web/assets/{diagram-7IWD3JNH-CUCAodFK.js → diagram-7IWD3JNH-B7UEiNnb.js} +1 -1
  28. package/web/assets/{diagram-B4RE2ZJO-DIFgvaCt.js → diagram-B4RE2ZJO-DHSLHS46.js} +1 -1
  29. package/web/assets/{diagram-LBJQPF4R-XBahy00D.js → diagram-LBJQPF4R-V8wA5qd1.js} +1 -1
  30. package/web/assets/{diagram-Q27KOJAE-DwqZKJhm.js → diagram-Q27KOJAE-BExt6RO_.js} +1 -1
  31. package/web/assets/{diagram-UB23O5K3-BXMt7Op8.js → diagram-UB23O5K3-C5IsI8Wd.js} +1 -1
  32. package/web/assets/{ebnfDiagram-BXEA7PRR-Cpz6ljuD.js → ebnfDiagram-BXEA7PRR-worbNlxi.js} +1 -1
  33. package/web/assets/{erDiagram-JOGREHBK-BLsnp8Sa.js → erDiagram-JOGREHBK-SmQLtE55.js} +1 -1
  34. package/web/assets/{flowDiagram-UKHOOZJN-DdGtlHt6.js → flowDiagram-UKHOOZJN-Dlj-KsqU.js} +1 -1
  35. package/web/assets/{ganttDiagram-PKOTCBZU-BA83m21H.js → ganttDiagram-PKOTCBZU-wrhVphV1.js} +1 -1
  36. package/web/assets/{gitGraphDiagram-DS77QQ5N-PHvv3GCK.js → gitGraphDiagram-DS77QQ5N-x5RHkYuw.js} +1 -1
  37. package/web/assets/index-D0a3MQ1T.css +1 -0
  38. package/web/assets/index-DjwNXkwE.js +563 -0
  39. package/web/assets/{infoDiagram-6WML65LV-KHrgwc8I.js → infoDiagram-6WML65LV-QSObfGHr.js} +1 -1
  40. package/web/assets/{ishikawaDiagram-WSZJBQD7-DX5VKU6v.js → ishikawaDiagram-WSZJBQD7-DnFs9iM-.js} +1 -1
  41. package/web/assets/{journeyDiagram-NVQOT4AX-DYR1w7M7.js → journeyDiagram-NVQOT4AX-DRBcWglZ.js} +1 -1
  42. package/web/assets/{kanban-definition-27J2QSJJ-YdTG5_IH.js → kanban-definition-27J2QSJJ-DJ4YDBq2.js} +1 -1
  43. package/web/assets/{linear-BG8Ya1Ih.js → linear-CakPBKKz.js} +1 -1
  44. package/web/assets/{mermaid.core-DtRZ8QZA.js → mermaid.core-ChTdgwuo.js} +4 -4
  45. package/web/assets/{mindmap-definition-FAOFIHXS-fo77Y4vS.js → mindmap-definition-FAOFIHXS-CheoRsDG.js} +1 -1
  46. package/web/assets/{pegDiagram-VL7TDLO6-DNcbtY8y.js → pegDiagram-VL7TDLO6-CZaM-7KZ.js} +1 -1
  47. package/web/assets/{pieDiagram-7S7Q4E2Y-DwCsKwsz.js → pieDiagram-7S7Q4E2Y-C5M0AvL-.js} +1 -1
  48. package/web/assets/{quadrantDiagram-CIZ2JOQS-DBsVlNC3.js → quadrantDiagram-CIZ2JOQS-COeP6vBt.js} +1 -1
  49. package/web/assets/{railroadDiagram-AXF67PYL-DF2bC_5D.js → railroadDiagram-AXF67PYL-C4o9Lel-.js} +1 -1
  50. package/web/assets/{requirementDiagram-LRYGKXZP-BP7teWQ3.js → requirementDiagram-LRYGKXZP-GMDyeWVe.js} +1 -1
  51. package/web/assets/{sankeyDiagram-W5VNT64P-DNoGqQqk.js → sankeyDiagram-W5VNT64P-DSZwyoSj.js} +1 -1
  52. package/web/assets/{sequenceDiagram-SI44F4Z6-DJtGHgQX.js → sequenceDiagram-SI44F4Z6-DGBuqaHP.js} +1 -1
  53. package/web/assets/{sizeCapture-X5ZJPWSS-CR8vipze.js → sizeCapture-X5ZJPWSS-B-zxFv3_.js} +1 -1
  54. package/web/assets/{stateDiagram-OKZ733FA-Bk1VizvY.js → stateDiagram-OKZ733FA-D7G84Iof.js} +1 -1
  55. package/web/assets/stateDiagram-v2-UEYNNEHI-DFfmMQ2j.js +1 -0
  56. package/web/assets/{swimlanes-SLNWSIFB-6eb6Yq1y.js → swimlanes-SLNWSIFB-DzSBg8NJ.js} +2 -2
  57. package/web/assets/swimlanesDiagram-ULZ7WXOC-DvlFN_gb.js +8 -0
  58. package/web/assets/{timeline-definition-Z64GVDOM-CNhXrkJs.js → timeline-definition-Z64GVDOM-CsGorQl9.js} +1 -1
  59. package/web/assets/{vennDiagram-T6HMQDX7-NX3AZCBh.js → vennDiagram-T6HMQDX7-DDwrAPDB.js} +1 -1
  60. package/web/assets/{wardleyDiagram-T6FBY63Y-swaO6e6e.js → wardleyDiagram-T6FBY63Y-CcclmHqH.js} +1 -1
  61. package/web/assets/{xychartDiagram-ELKLHX3M-CL4seKwn.js → xychartDiagram-ELKLHX3M-CJORQave.js} +1 -1
  62. package/web/index.html +2 -2
  63. package/web/assets/api-web-CfrRrKkd.js +0 -1
  64. package/web/assets/channel-CO81t0iZ.js +0 -1
  65. package/web/assets/classDiagram-JCYQIIEL--cirGlAO.js +0 -1
  66. package/web/assets/classDiagram-v2-OCEON4UE--cirGlAO.js +0 -1
  67. package/web/assets/index-BvQFXLZ2.js +0 -553
  68. package/web/assets/index-C6xbHOXP.css +0 -1
  69. package/web/assets/stateDiagram-v2-UEYNNEHI-BuuaxVSs.js +0 -1
  70. package/web/assets/swimlanesDiagram-ULZ7WXOC-DEN6Tg7V.js +0 -8
package/install-termux.sh CHANGED
@@ -111,7 +111,7 @@ install_voice() {
111
111
  tar -xzf voice.tgz --strip-components=1 -C "$APP_DIR/node_modules"
112
112
  rm -f voice.tgz
113
113
  ok "语音引擎就位(npm 镜像): $APP_DIR/node_modules/sherpa-onnx-node"
114
- info "语音模型(朗读约 430MB + 听写约 200MB)在应用内下载: 设置 语音能力"
114
+ info "语音模型在应用内按需下载(设置 语音能力):朗读本地档约 430MB;听写建议云档(Groq/Azure),本地 Whisper 360MB~1GB"
115
115
  info "装完模型重启服务生效: bash ~/lookatstudy/start.sh"
116
116
  return 0
117
117
  fi
@@ -304,19 +304,11 @@ case "$(getprop ro.product.manufacturer 2>/dev/null | tr '[:upper:]' '[:lower:]'
304
304
  ;;
305
305
  esac
306
306
 
307
- # ── 6.5 语音引擎(Termux 专属交叉编译包,可选) ────────────────
308
- # 引擎包 ~12MB 一次性下载;语音模型( 630MB)由用户在应用内按需下载。
309
- if [ "$1" = "--voice" ] || [ -d "$APP_DIR/node_modules/sherpa-onnx-node" ]; then
310
- install_voice || true
311
- else
312
- echo ''
313
- info "可选:Termux 语音朗读/听写(引擎包约 12MB,语音模型在应用内另下)。"
314
- read -r -p "[*] 现在安装吗?[y/N] " ans </dev/tty 2>/dev/null || ans="n"
315
- case "$ans" in
316
- y|Y) install_voice || true ;;
317
- *) info "跳过(以后可: bash install-termux.sh --voice)" ;;
318
- esac
319
- fi
307
+ # ── 6.5 语音引擎(Termux 专属交叉编译包,默认安装) ────────────
308
+ # 引擎包 ~12MB 一次到位(相对 Node+系统升级的体量可忽略;语音朗读/听写默认能力)
309
+ # 全程零交互:失败不阻断安装(朗读在线档/听写云档不依赖本地引擎),重跑安装命令即补装。
310
+ # 兼容旧写法:--voice 参数仍被接受(现为默认,传不传一样)。
311
+ install_voice || true
320
312
 
321
313
  # ── 7. 启动 + 打印访问链接 ───────────────────────────────────
322
314
  info "启动 LookatStudy..."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lookatstudy-mobile",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "LookatStudy Termux portable bundle (server.cjs + web frontend)",
5
5
  "repository": {
6
6
  "type": "git",
package/server.cjs CHANGED
@@ -12440,6 +12440,10 @@ var init_schema = __esm({
12440
12440
  id: text("id").primaryKey(),
12441
12441
  /** 用户起的名字,如"智谱 CodingPlan CN" */
12442
12442
  label: text("label").notNull(),
12443
+ /** 用途分区:主模型 / 看图覆盖 / 朗读 / 听写(v0.15 前的行全部是 llm) */
12444
+ kind: text("kind", {
12445
+ enum: ["llm", "vision", "tts", "asr"]
12446
+ }).notNull().default("llm"),
12443
12447
  /** 协议:openai-compatible(默认,覆盖 90% 场景)/ anthropic / google */
12444
12448
  protocol: text("protocol", {
12445
12449
  enum: ["openai-compatible", "anthropic", "google"]
@@ -12758,6 +12762,8 @@ CREATE INDEX IF NOT EXISTS idx_memory_category ON memory(category);
12758
12762
  CREATE TABLE IF NOT EXISTS custom_providers (
12759
12763
  id TEXT PRIMARY KEY, -- 'custom-' + uuid\uFF0C\u4E5F\u4F5C\u4E3A active_provider \u7684\u503C
12760
12764
  label TEXT NOT NULL, -- \u7528\u6237\u8D77\u7684\u540D\u5B57\uFF0C\u5982 "\u667A\u8C31 CodingPlan CN"
12765
+ kind TEXT NOT NULL DEFAULT 'llm' -- \u7528\u9014\u5206\u533A\uFF1A\u4E3B\u6A21\u578B/\u770B\u56FE/\u6717\u8BFB/\u542C\u5199\uFF08v0.15 \u524D\u7684\u884C\u5168\u90E8\u662F llm\uFF09
12766
+ CHECK (kind IN ('llm', 'vision', 'tts', 'asr')),
12761
12767
  protocol TEXT NOT NULL DEFAULT 'openai-compatible'
12762
12768
  CHECK (protocol IN ('openai-compatible', 'anthropic', 'google')),
12763
12769
  base_url TEXT NOT NULL, -- \u5982 https://api.z.ai/api/coding/paas/v4
@@ -12961,6 +12967,7 @@ function runMigrations(db) {
12961
12967
  addColumnIfMissing("content_nodes", "knowledge_points", "TEXT");
12962
12968
  addColumnIfMissing("exercises", "kc_title", "TEXT");
12963
12969
  addColumnIfMissing("courses", "source_lang", "TEXT");
12970
+ addColumnIfMissing("custom_providers", "kind", "TEXT NOT NULL DEFAULT 'llm'");
12964
12971
  addColumnIfMissing("memory", "course_id", "TEXT");
12965
12972
  db.run("CREATE INDEX IF NOT EXISTS idx_memory_course ON memory(course_id);");
12966
12973
  addColumnIfMissing("canvas_items", "source_type", "TEXT");
@@ -59167,6 +59174,7 @@ function rowToCustomProvider(row) {
59167
59174
  return {
59168
59175
  id: row.id,
59169
59176
  label: row.label,
59177
+ kind: row.kind ?? "llm",
59170
59178
  protocol: row.protocol,
59171
59179
  baseUrl: row.baseUrl,
59172
59180
  defaultModel: row.defaultModel,
@@ -59189,6 +59197,7 @@ function createCustomProvider(db, input) {
59189
59197
  db.insert(customProviders).values({
59190
59198
  id,
59191
59199
  label: input.label,
59200
+ kind: input.kind ?? "llm",
59192
59201
  protocol: input.protocol,
59193
59202
  baseUrl: input.baseUrl,
59194
59203
  apiKey: input.apiKey ?? null,
@@ -59205,6 +59214,8 @@ function updateCustomProvider(db, id, input) {
59205
59214
  const patch = {};
59206
59215
  if (input.label !== void 0)
59207
59216
  patch.label = input.label;
59217
+ if (input.kind !== void 0)
59218
+ patch.kind = input.kind;
59208
59219
  if (input.protocol !== void 0)
59209
59220
  patch.protocol = input.protocol;
59210
59221
  if (input.baseUrl !== void 0)
@@ -59228,6 +59239,7 @@ function getCustomProviderRaw(db, id) {
59228
59239
  return null;
59229
59240
  return {
59230
59241
  id: row.id,
59242
+ kind: row.kind ?? "llm",
59231
59243
  protocol: row.protocol,
59232
59244
  baseUrl: row.baseUrl,
59233
59245
  apiKey: row.apiKey,
@@ -491056,17 +491068,27 @@ function speedToRatePercent(speed) {
491056
491068
  return `${pct >= 0 ? "+" : ""}${pct}%`;
491057
491069
  }
491058
491070
  function resolveTtsTier(settings2) {
491059
- const engineRaw = settings2.tts_engine;
491060
- const engine = engineRaw === "azure" || engineRaw === "local" || engineRaw === "edge" ? engineRaw : DEFAULT_TTS_ENGINE;
491071
+ const engineRaw = settings2.tts_engine?.trim() ?? "";
491072
+ const isCustom = engineRaw.startsWith("custom-");
491073
+ const engine = isCustom || engineRaw === "azure" || engineRaw === "local" || engineRaw === "edge" ? isCustom ? "custom" : engineRaw : DEFAULT_TTS_ENGINE;
491061
491074
  const voice = engine === "azure" ? settings2.azure_tts_voice?.trim() || DEFAULT_AZURE_TTS_VOICE : settings2.tts_voice_edge?.trim() || DEFAULT_EDGE_VOICE;
491062
491075
  return {
491063
491076
  engine,
491077
+ customProviderId: isCustom ? engineRaw : null,
491064
491078
  voice,
491079
+ customVoice: settings2.tts_custom_voice?.trim() || null,
491065
491080
  speed: clampSpeed(settings2.tts_speed),
491066
491081
  azureKey: settings2.azure_tts_api_key?.trim() || null,
491067
491082
  azureRegion: settings2.azure_tts_region?.trim() || null
491068
491083
  };
491069
491084
  }
491085
+ function speedToOpenaiSpeed(speed) {
491086
+ if (speed <= 0.85)
491087
+ return "slow";
491088
+ if (speed >= 1.15)
491089
+ return "fast";
491090
+ return "normal";
491091
+ }
491070
491092
  function azureTtsMissing(cfg) {
491071
491093
  if (cfg.engine !== "azure")
491072
491094
  return null;
@@ -491156,6 +491178,39 @@ async function synthesizeAzureWav(text3, opts) {
491156
491178
  return buf;
491157
491179
  }
491158
491180
 
491181
+ // src/main/services/speech/openai-tts-client.ts
491182
+ function openaiTtsUrl(baseUrl) {
491183
+ return `${baseUrl.replace(/\/+$/, "")}/audio/speech`;
491184
+ }
491185
+ async function synthesizeOpenaiTts(opts, timeoutMs = 3e4) {
491186
+ const headers = { "Content-Type": "application/json" };
491187
+ if (opts.apiKey)
491188
+ headers.Authorization = `Bearer ${opts.apiKey}`;
491189
+ const body = {
491190
+ model: opts.model,
491191
+ input: opts.text,
491192
+ response_format: "mp3"
491193
+ };
491194
+ if (opts.voice)
491195
+ body.voice = opts.voice;
491196
+ if (opts.speed && opts.speed !== "normal")
491197
+ body.speed = opts.speed;
491198
+ const res = await fetch(openaiTtsUrl(opts.baseUrl), {
491199
+ method: "POST",
491200
+ headers,
491201
+ body: JSON.stringify(body),
491202
+ signal: AbortSignal.timeout(timeoutMs)
491203
+ });
491204
+ if (!res.ok) {
491205
+ const detail = await res.text().catch(() => "");
491206
+ throw new Error(`tts HTTP ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`);
491207
+ }
491208
+ const buf = Buffer.from(await res.arrayBuffer());
491209
+ if (buf.length === 0)
491210
+ throw new Error("tts empty response");
491211
+ return buf;
491212
+ }
491213
+
491159
491214
  // src/main/services/speech/tts-service.ts
491160
491215
  var TTS_ENTRY = SPEECH_MODELS_MANIFEST.models.find((m) => m.id === "tts-kokoro");
491161
491216
  var active = null;
@@ -491163,13 +491218,15 @@ function localModelReady(dataDir) {
491163
491218
  return isSpeechEngineLoadable() && readSpeechModelStatus(dataDir, TTS_ENTRY).state === "ready";
491164
491219
  }
491165
491220
  async function synthSentence(dataDir, cfg, engine, sentence, opts) {
491221
+ const isMp3 = engine === "edge" || engine === "custom";
491166
491222
  const key = ttsCacheKey({
491167
- engine,
491168
- voice: engine === "local" ? `sid-${opts.sid}` : cfg.voice,
491223
+ engine: engine === "custom" ? cfg.customProviderId ?? "custom" : engine,
491224
+ // custom 缓存键把模型并进 voice 段(同 provider 换模型/音色不串味)
491225
+ voice: engine === "local" ? `sid-${opts.sid}` : engine === "custom" ? `${opts.custom?.model ?? "?"}|${opts.custom?.voice ?? ""}` : cfg.voice,
491169
491226
  speed: cfg.speed,
491170
491227
  sentence
491171
491228
  });
491172
- const mime = engine === "edge" ? "audio/mpeg" : "audio/wav";
491229
+ const mime = isMp3 ? "audio/mpeg" : "audio/wav";
491173
491230
  const cached2 = readCachedAudio(dataDir, key, mime);
491174
491231
  if (cached2)
491175
491232
  return { bytes: cached2, mime, sampleRate: 24e3 };
@@ -491182,6 +491239,21 @@ async function synthSentence(dataDir, cfg, engine, sentence, opts) {
491182
491239
  await writeCachedAudio(dataDir, key, mime, bytes2);
491183
491240
  return { bytes: bytes2, mime, sampleRate: 24e3 };
491184
491241
  }
491242
+ if (engine === "custom") {
491243
+ if (!opts.custom)
491244
+ throw new Error("custom tts provider not resolved");
491245
+ const mp3 = await synthesizeOpenaiTts({
491246
+ baseUrl: opts.custom.baseUrl,
491247
+ apiKey: opts.custom.apiKey,
491248
+ model: opts.custom.model,
491249
+ voice: opts.custom.voice ?? cfg.customVoice,
491250
+ text: sentence,
491251
+ speed: speedToOpenaiSpeed(cfg.speed)
491252
+ });
491253
+ const bytes2 = bufferToArrayBuffer(mp3);
491254
+ await writeCachedAudio(dataDir, key, mime, bytes2);
491255
+ return { bytes: bytes2, mime, sampleRate: 24e3 };
491256
+ }
491185
491257
  if (engine === "azure") {
491186
491258
  const wav = await synthesizeAzureWav(sentence, {
491187
491259
  key: cfg.azureKey ?? "",
@@ -491216,6 +491288,9 @@ async function speakMessage(emit2, dataDir, settings2, messageId, rawText, deps
491216
491288
  return { ok: false, reason: "azure-key-missing" };
491217
491289
  if (missing === "region")
491218
491290
  return { ok: false, reason: "azure-region-missing" };
491291
+ } else if (cfg.engine === "custom") {
491292
+ if (!deps.custom)
491293
+ return { ok: false, reason: "custom-provider-missing" };
491219
491294
  }
491220
491295
  const { sentences } = splitSentences(normalizeSpeechText(rawText), { flush: true });
491221
491296
  if (sentences.length === 0)
@@ -491234,7 +491309,8 @@ async function speakMessage(emit2, dataDir, settings2, messageId, rawText, deps
491234
491309
  const sentence = sentences[i];
491235
491310
  const p = synth(dataDir, cfg, engine, sentence, {
491236
491311
  sid,
491237
- onChunk: () => !mine.stopped && active === mine
491312
+ onChunk: () => !mine.stopped && active === mine,
491313
+ custom: deps.custom ?? void 0
491238
491314
  }).finally(() => inflight.delete(i));
491239
491315
  inflight.set(i, p);
491240
491316
  return p;
@@ -491322,10 +491398,12 @@ function speechModelsStatusSnapshot(dataDir) {
491322
491398
  // src/main/services/speech/asr-tiers.ts
491323
491399
  var DEFAULT_ASR_ENGINE = "local";
491324
491400
  function resolveAsrTier(settings2) {
491325
- const raw = settings2.asr_engine;
491326
- const engine = raw === "groq" || raw === "azure" || raw === "local" ? raw : DEFAULT_ASR_ENGINE;
491401
+ const raw = settings2.asr_engine?.trim() ?? "";
491402
+ const isCustom = raw.startsWith("custom-");
491403
+ const engine = isCustom || raw === "groq" || raw === "azure" || raw === "local" ? isCustom ? "custom" : raw : DEFAULT_ASR_ENGINE;
491327
491404
  return {
491328
491405
  engine,
491406
+ customProviderId: isCustom ? raw : null,
491329
491407
  groqKey: settings2.groq_api_key?.trim() || null,
491330
491408
  azureKey: settings2.azure_stt_api_key?.trim() || null,
491331
491409
  azureRegion: settings2.azure_stt_region?.trim() || null
@@ -491373,31 +491451,40 @@ function asrCloudMissing(cfg) {
491373
491451
 
491374
491452
  // src/main/services/speech/cloud-asr-client.ts
491375
491453
  var GROQ_TRANSCRIBE_MODEL = "whisper-large-v3-turbo";
491376
- function groqTranscribeUrl() {
491377
- return "https://api.groq.com/openai/v1/audio/transcriptions";
491454
+ function openaiTranscribeUrl(baseUrl) {
491455
+ return `${baseUrl.replace(/\/+$/, "")}/audio/transcriptions`;
491378
491456
  }
491379
491457
  function azureSttUrl(region, language) {
491380
491458
  return `https://${region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=${encodeURIComponent(language)}&format=simple`;
491381
491459
  }
491382
- async function groqTranscribe(key, wav, language, timeoutMs = 3e4) {
491460
+ async function openaiTranscribe(opts, timeoutMs = 3e4) {
491383
491461
  const form = new FormData();
491384
- form.append("file", new Blob([wav], { type: "audio/wav" }), "dictation.wav");
491385
- form.append("model", GROQ_TRANSCRIBE_MODEL);
491386
- form.append("language", language);
491462
+ form.append("file", new Blob([opts.wav], { type: "audio/wav" }), "dictation.wav");
491463
+ form.append("model", opts.model);
491464
+ form.append("language", opts.language);
491387
491465
  form.append("response_format", "json");
491388
- const res = await fetch(groqTranscribeUrl(), {
491466
+ const headers = {};
491467
+ if (opts.apiKey)
491468
+ headers.Authorization = `Bearer ${opts.apiKey}`;
491469
+ const res = await fetch(openaiTranscribeUrl(opts.baseUrl), {
491389
491470
  method: "POST",
491390
- headers: { Authorization: `Bearer ${key}` },
491471
+ headers,
491391
491472
  body: form,
491392
491473
  signal: AbortSignal.timeout(timeoutMs)
491393
491474
  });
491394
491475
  if (!res.ok) {
491395
491476
  const detail = await res.text().catch(() => "");
491396
- throw new Error(`groq asr HTTP ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`);
491477
+ throw new Error(`asr HTTP ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`);
491397
491478
  }
491398
491479
  const j = await res.json();
491399
491480
  return (j.text ?? "").trim();
491400
491481
  }
491482
+ async function groqTranscribe(key, wav, language, timeoutMs = 3e4) {
491483
+ return openaiTranscribe(
491484
+ { baseUrl: "https://api.groq.com/openai/v1", apiKey: key, model: GROQ_TRANSCRIBE_MODEL, wav, language },
491485
+ timeoutMs
491486
+ );
491487
+ }
491401
491488
  async function azureSttTranscribe(key, region, wav, language, timeoutMs = 3e4) {
491402
491489
  const res = await fetch(azureSttUrl(region, language), {
491403
491490
  method: "POST",
@@ -491421,8 +491508,13 @@ async function azureSttTranscribe(key, region, wav, language, timeoutMs = 3e4) {
491421
491508
  }
491422
491509
 
491423
491510
  // src/main/services/speech/asr-service.ts
491424
- function pickLocalWhisperEntry(dataDir) {
491425
- for (const id of ["asr-whisper-turbo", "asr-whisper-small"]) {
491511
+ function pickLocalWhisperEntry(dataDir, settings2) {
491512
+ let order = ["asr-whisper-turbo", "asr-whisper-small"];
491513
+ const preferred = settings2?.asr_local_model;
491514
+ if (preferred === "asr-whisper-turbo" || preferred === "asr-whisper-small") {
491515
+ order = [preferred, ...order.filter((id) => id !== preferred)];
491516
+ }
491517
+ for (const id of order) {
491426
491518
  const entry = SPEECH_MODELS_MANIFEST.models.find((m) => m.id === id);
491427
491519
  if (entry && readSpeechModelStatus(dataDir, entry).state === "ready")
491428
491520
  return entry;
@@ -491450,8 +491542,24 @@ function normalizeChineseScript(text3, locale) {
491450
491542
  return text3;
491451
491543
  }
491452
491544
  }
491453
- async function transcribeAudio(dataDir, settings2, wavBytes, locale) {
491545
+ async function transcribeAudio(dataDir, settings2, wavBytes, locale, custom3) {
491454
491546
  const cfg = resolveAsrTier(settings2);
491547
+ if (cfg.engine === "custom") {
491548
+ if (!custom3)
491549
+ return { ok: false, reason: "custom-provider-missing" };
491550
+ try {
491551
+ const text3 = await openaiTranscribe({
491552
+ baseUrl: custom3.baseUrl,
491553
+ apiKey: custom3.apiKey,
491554
+ model: custom3.model,
491555
+ wav: wavBytes,
491556
+ language: localeToBcp47(locale)
491557
+ });
491558
+ return { ok: true, text: normalizeChineseScript(text3, locale) };
491559
+ } catch (e) {
491560
+ return { ok: false, reason: "asr-failed", detail: e instanceof Error ? e.message : String(e) };
491561
+ }
491562
+ }
491455
491563
  if (cfg.engine === "groq" || cfg.engine === "azure") {
491456
491564
  const missing = asrCloudMissing(cfg);
491457
491565
  if (missing === "groq-key")
@@ -491470,7 +491578,7 @@ async function transcribeAudio(dataDir, settings2, wavBytes, locale) {
491470
491578
  }
491471
491579
  if (!isSpeechEngineLoadable())
491472
491580
  return { ok: false, reason: "engine-unavailable" };
491473
- const entry = pickLocalWhisperEntry(dataDir);
491581
+ const entry = pickLocalWhisperEntry(dataDir, settings2);
491474
491582
  if (!entry)
491475
491583
  return { ok: false, reason: "model-missing" };
491476
491584
  return runLocal(async () => {
@@ -491500,6 +491608,9 @@ async function transcribeAudio(dataDir, settings2, wavBytes, locale) {
491500
491608
  });
491501
491609
  }
491502
491610
 
491611
+ // src/main/ipc/index.ts
491612
+ init_custom_provider_service();
491613
+
491503
491614
  // src/main/services/progress-service.ts
491504
491615
  init_drizzle_orm();
491505
491616
  init_schema();
@@ -496071,7 +496182,12 @@ function registerSpeechHandlers(deps) {
496071
496182
  });
496072
496183
  handle("speech:ttsSpeak", async (_e, text3, messageId) => {
496073
496184
  const settings2 = readSettingsMap(getDb());
496074
- const result = await speakMessage(emit2, deps.dataDir, settings2, messageId, text3);
496185
+ let custom3 = null;
496186
+ if (settings2.tts_engine?.startsWith("custom-")) {
496187
+ const row = getCustomProviderRaw(getDb(), settings2.tts_engine);
496188
+ custom3 = row ? { baseUrl: row.baseUrl, apiKey: row.apiKey, model: row.defaultModel, voice: settings2.tts_custom_voice?.trim() || null } : null;
496189
+ }
496190
+ const result = await speakMessage(emit2, deps.dataDir, settings2, messageId, text3, { custom: custom3 });
496075
496191
  if (result.ok && result.engine === "edge" && settings2.tts_edge_disclosed !== "1") {
496076
496192
  const db = getDb();
496077
496193
  db.insert(settings).values({ key: "tts_edge_disclosed", value: "1", isSecret: false }).onConflictDoUpdate({ target: settings.key, set: { value: "1" } }).run();
@@ -496085,7 +496201,62 @@ function registerSpeechHandlers(deps) {
496085
496201
  });
496086
496202
  handle("speech:asrTranscribe", async (_e, wavBytes, locale) => {
496087
496203
  const settings2 = readSettingsMap(getDb());
496088
- return transcribeAudio(deps.dataDir, settings2, wavBytes, locale);
496204
+ let custom3 = null;
496205
+ if (settings2.asr_engine?.startsWith("custom-")) {
496206
+ const row = getCustomProviderRaw(getDb(), settings2.asr_engine);
496207
+ custom3 = row ? { baseUrl: row.baseUrl, apiKey: row.apiKey, model: row.defaultModel } : null;
496208
+ }
496209
+ return transcribeAudio(deps.dataDir, settings2, wavBytes, locale, custom3);
496210
+ });
496211
+ handle("speech:testCustomTts", async (_e, input) => {
496212
+ let baseUrl = input.baseUrl ?? "";
496213
+ let apiKey = input.apiKey ?? null;
496214
+ let model = input.model ?? "";
496215
+ let voice = input.voice ?? null;
496216
+ if (input.providerId) {
496217
+ const row = getCustomProviderRaw(getDb(), input.providerId);
496218
+ if (!row)
496219
+ return { ok: false, detail: "provider \u4E0D\u5B58\u5728" };
496220
+ baseUrl = row.baseUrl;
496221
+ apiKey = row.apiKey;
496222
+ model = row.defaultModel;
496223
+ voice = readSettingsMap(getDb()).tts_custom_voice?.trim() || null;
496224
+ }
496225
+ if (!baseUrl || !model)
496226
+ return { ok: false, detail: "\u7F3A baseUrl/\u6A21\u578B" };
496227
+ try {
496228
+ const buf = await synthesizeOpenaiTts({ baseUrl, apiKey, model, voice, text: "\u4F60\u597D" });
496229
+ return { ok: buf.length > 0, detail: buf.length > 0 ? `${buf.length}B` : "empty" };
496230
+ } catch (e) {
496231
+ return { ok: false, detail: e instanceof Error ? e.message : String(e) };
496232
+ }
496233
+ });
496234
+ handle("speech:testCustomAsr", async (_e, input) => {
496235
+ let baseUrl = input.baseUrl ?? "";
496236
+ let apiKey = input.apiKey ?? null;
496237
+ if (input.providerId) {
496238
+ const row = getCustomProviderRaw(getDb(), input.providerId);
496239
+ if (!row)
496240
+ return { ok: false, detail: "provider \u4E0D\u5B58\u5728" };
496241
+ baseUrl = row.baseUrl;
496242
+ apiKey = row.apiKey;
496243
+ }
496244
+ if (!baseUrl)
496245
+ return { ok: false, detail: "\u7F3A baseUrl" };
496246
+ const url2 = `${baseUrl.replace(/\/+$/, "")}/models`;
496247
+ const headers = {};
496248
+ if (apiKey)
496249
+ headers.Authorization = `Bearer ${apiKey}`;
496250
+ try {
496251
+ const res = await fetch(url2, { headers, signal: AbortSignal.timeout(15e3) });
496252
+ if (res.ok)
496253
+ return { ok: true, detail: String(res.status) };
496254
+ if (res.status === 401 || res.status === 403)
496255
+ return { ok: false, detail: `HTTP ${res.status}(\u5BC6\u94A5\u65E0\u6548)` };
496256
+ return { ok: false, detail: `HTTP ${res.status}(\u7AEF\u70B9\u53EF\u8FBE,\u672A\u63D0\u4F9B\u6A21\u578B\u5217\u8868;\u4FDD\u5B58\u540E\u5B9E\u9645\u8BF4\u4E00\u53E5\u9A8C\u8BC1)` };
496257
+ } catch (e) {
496258
+ return { ok: false, detail: e instanceof Error ? e.message : String(e) };
496259
+ }
496089
496260
  });
496090
496261
  }
496091
496262
  function registerAllHandlers(deps) {
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-ComVOTYh.js";import{p as f}from"./chunk-JWPE2WC7-m78qF4H_.js";import{_ as n,l as o}from"./mermaid.core-DtRZ8QZA.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-DGYmCJ7v.js";import"./index-BvQFXLZ2.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-6Q2QTUOP-sn207b1U.js";import{p as f}from"./chunk-JWPE2WC7-V2anUJlE.js";import{_ as n,l as o}from"./mermaid.core-ChTdgwuo.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-CG6JQnIq.js";import"./index-DjwNXkwE.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
@@ -0,0 +1 @@
1
+ var f=Object.defineProperty;var P=(s,e,t)=>e in s?f(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t;var g=(s,e,t)=>P(s,typeof e!="symbol"?e+"":e,t);const S={listCourses:"course:list",getCourseTree:"course:getTree",importCourseFromRepo:"course:importFromRepo",detectLanguages:"course:detectLanguages",getCourseLanguages:"course:getLanguages",setCourseLanguage:"course:setLanguage",getCourseLanguage:"course:getLanguage",analyzeRepo:"course:analyzeRepo",importAnalyzed:"course:importAnalyzed",importLocalFolder:"import:localFolder",importGithub:"import:github",importCancel:"import:cancel",importResume:"import:resume",importPack:"import:importPack",exportPack:"import:exportPack",generateCourseFromMarkdown:"course:generateFromMarkdown",deleteCourse:"course:delete",restructureCourse:"course:restructure",generateSummaries:"course:generateSummaries",getStarterPrompts:"course:getStarterPrompts",getNodeContent:"course:getNodeContent",getNodeSummary:"course:getNodeSummary",getPracticeForLesson:"course:getPracticeForLesson",getLessonForPractice:"course:getLessonForPractice",listAssetsByNode:"asset:listByNode",listAssetsByCourse:"asset:listByCourse",getAssetDataUrl:"asset:getDataUrl",getProgress:"progress:get",updateProgress:"progress:update",markNodeAttempted:"progress:markAttempted",generateExercise:"exercise:generate",listExercises:"exercise:list",submitExerciseAnswer:"exercise:submit",examPrepare:"exam:prepare",examRegenerate:"exam:regenerate",examGetStatus:"exam:getStatus",examStartAttempt:"exam:startAttempt",examRecordAnswer:"exam:recordAnswer",examSubmitAttempt:"exam:submitAttempt",getDueReviews:"srs:getDue",getAllSrsItems:"srs:getAll",recordReview:"srs:record",getStreak:"streak:get",touchStreakToday:"streak:touchToday",listSouls:"soul:list",getSoul:"soul:get",createSoul:"soul:create",setActiveSoul:"soul:setActive",getActiveSoul:"soul:getActive",agentChat:"agent:chat",abortAgentChat:"agent:abort",getChatHistory:"agent:getHistory",clearChatHistory:"agent:clearHistory",agentChatThread:"agent:chatThread",abortAgentChatThread:"agent:abortThread",getContextUsage:"agent:getContextUsage",getSpeechModelStatus:"speech:getModelStatus",ensureSpeechModel:"speech:ensureModel",deleteSpeechModel:"speech:deleteModel",ttsSpeak:"speech:ttsSpeak",ttsStop:"speech:ttsStop",asrTranscribe:"speech:asrTranscribe",testCustomTts:"speech:testCustomTts",testCustomAsr:"speech:testCustomAsr",getAttachmentDataUrl:"attachment:getDataUrl",isAgentReady:"agent:isReady",getProviderPresets:"agent:getProviderPresets",testLlmConnection:"agent:testConnection",testCustomProvider:"agent:testCustomProvider",discoverModels:"agent:discoverModels",discoverProviderModels:"agent:discoverProviderModels",listCustomProviders:"customProvider:list",createCustomProvider:"customProvider:create",updateCustomProvider:"customProvider:update",deleteCustomProvider:"customProvider:delete",listPendingProposals:"proposal:listPending",applyProposal:"proposal:apply",rejectProposal:"proposal:reject",recordQuizAnswer:"quiz:recordAnswer",logFriction:"friction:log",getDashboard:"dashboard:get",searchContent:"search:content",updateMemory:"memory:update",getMemory:"memory:get",consolidateMemory:"consolidate:run",getSetting:"settings:get",setSetting:"settings:set",getXpStatus:"xp:getStatus",exportCourse:"course:export",canvasList:"canvas:list",canvasSave:"canvas:save",canvasDelete:"canvas:delete",canvasTogglePin:"canvas:togglePin",canvasSaveUserNote:"canvas:saveUserNote",canvasRecordQuizResult:"canvas:recordQuizResult",canvasUpdateUserNoteComment:"canvas:updateUserNoteComment",threadList:"thread:list",threadCreate:"thread:create",threadUpdate:"thread:update",threadDelete:"thread:delete",threadGetMessages:"thread:getMessages",threadFindRecentByNode:"thread:findRecentByNode"},h=1,m="ls-serve-token";function y(){const s=new URLSearchParams(window.location.search).get("token");if(s){try{window.localStorage.setItem(m,s)}catch{}return s}try{return window.localStorage.getItem(m)??""}catch{return""}}class p{constructor(){g(this,"ws",null);g(this,"everOpened",!1);g(this,"pending",new Map);g(this,"listeners",new Map);g(this,"connectPromise",null)}connect(){return this.ws&&this.ws.readyState===WebSocket.OPEN?Promise.resolve(this.ws):this.connectPromise?this.connectPromise:(this.connectPromise=new Promise((e,t)=>{const c=window.location.protocol==="https:"?"wss:":"ws:",o=y(),a=`${c}//${window.location.host}/?token=${encodeURIComponent(o)}`;let i=!1;const d=new WebSocket(a);this.ws=d;const l=window.setTimeout(()=>{i||(i=!0,this.connectPromise=null,t(new Error("连接学习服务超时(serve 未启动?)")))},5e3);d.addEventListener("open",()=>{this.everOpened=!0,window.clearTimeout(l),i||(i=!0,this.connectPromise=null,e(d))}),d.addEventListener("message",u=>{let r=null;try{r=JSON.parse(String(u.data))}catch{return}if(!(!r||r.v!==h)){if(r.type==="res"){const n=this.pending.get(r.id);if(!n)return;this.pending.delete(r.id),window.clearTimeout(n.timer),r.ok?n.resolve(r.result):n.reject(new Error(r.error))}else if(r.type==="event"){const n=this.listeners.get(r.channel);if(n)for(const v of n)v(...r.args)}}}),d.addEventListener("close",u=>{if(this.connectPromise=null,this.ws===d&&(this.ws=null),!i){i=!0,window.clearTimeout(l),t(new Error(A(u)));return}const r=u.code===4001?"学习服务拒绝了连接(token 无效)——请用启动时打印的带 token 链接打开":"与服务端的连接已断开";for(const[,n]of this.pending)window.clearTimeout(n.timer),n.reject(new Error(r));if(u.code===4001&&(this.everOpened=!1),this.pending.clear(),u.code===4001){window.dispatchEvent(new CustomEvent(C));return}this.everOpened&&window.setTimeout(()=>void this.connect().catch(()=>{}),2e3)}),d.addEventListener("error",()=>{})}),this.connectPromise)}static bytesToB64(e){const t=new Uint8Array(e);let c="";const o=32768;for(let a=0;a<t.length;a+=o)c+=String.fromCharCode(...t.subarray(a,a+o));return btoa(c)}async invoke(e,...t){var a,i;const c=await this.connect(),o=((i=(a=globalThis.crypto)==null?void 0:a.randomUUID)==null?void 0:i.call(a))??String(Math.random()).slice(2);return new Promise((d,l)=>{const u=window.setTimeout(()=>{this.pending.delete(o),l(new Error(`调用 ${e} 超时`))},3e5);this.pending.set(o,{resolve:d,reject:l,timer:u});const r=e==="speech:asrTranscribe"&&t[0]instanceof ArrayBuffer?[p.bytesToB64(t[0]),...t.slice(1)]:t;c.send(JSON.stringify({v:h,type:"req",id:o,channel:e,args:r}))})}on(e,t){const c=e==="speech:ttsAudio"?(...a)=>{const i=a[0];if(i&&typeof i.wavBase64=="string"){const d=atob(i.wavBase64),l=new Uint8Array(d.length);for(let n=0;n<d.length;n++)l[n]=d.charCodeAt(n);const{wavBase64:u,...r}=i;t({...r,wavBytes:l.buffer})}else t(...a)}:t;let o=this.listeners.get(e);return o||(o=new Set,this.listeners.set(e,o)),o.add(c),this.connect().catch(()=>{}),()=>{o.delete(c)}}}function b(){if(typeof window>"u")return!1;if(new URLSearchParams(window.location.search).get("token"))return!0;try{return!!window.localStorage.getItem(m)}catch{return!1}}function k(s){try{window.localStorage.setItem(m,s)}catch{}const e=new URL(window.location.href);e.searchParams.set("token",s),window.location.replace(e.toString())}const C="ls-web-token-rejected";function A(s){return s.code===4001?"学习服务拒绝了连接(token 无效)——请用启动时打印的带 token 链接打开":"无法连接学习服务(serve 未启动?)"}let w=!1;async function x(){if(w||window.api)return;w=!0;const s=new p,e={};for(const[t,c]of Object.entries(S))e[t]=(...o)=>s.invoke(c,...o);e.on=(t,c)=>s.on(t,c),window.api=e}function E(){return new p}export{C as TOKEN_REJECTED_EVENT,E as createWebTransportForTest,b as hasWebToken,x as installWebApi,k as setWebToken};
@@ -1 +1 @@
1
- import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-DtRZ8QZA.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-ChTdgwuo.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};