@klarkxy/dsh-current-title 0.1.7 → 0.2.1
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/README.md +7 -2
- package/docs/README.zh-CN.md +7 -2
- package/lib/client.inner.cjs +391 -96
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +392 -258
- package/lib/contracts.d.ts +7 -8
- package/lib/contracts.js +1 -22
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +82 -12
- package/lib/index.js.map +1 -1
- package/package.json +18 -11
package/lib/client.inner.cjs
CHANGED
|
@@ -3,6 +3,30 @@ let react = require("react");
|
|
|
3
3
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
4
4
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
5
|
//#region ../dsh-plugin-kit/src/client-utils.ts
|
|
6
|
+
/** Fixed pause for failing background reads; manual work is never replayed. */
|
|
7
|
+
function createPluginReadGate(now = Date.now) {
|
|
8
|
+
let pausedUntil = 0;
|
|
9
|
+
let epoch = 0;
|
|
10
|
+
return {
|
|
11
|
+
canRead: () => now() >= pausedUntil,
|
|
12
|
+
reset: () => {
|
|
13
|
+
epoch++;
|
|
14
|
+
pausedUntil = 0;
|
|
15
|
+
},
|
|
16
|
+
async run(read) {
|
|
17
|
+
const current = epoch;
|
|
18
|
+
try {
|
|
19
|
+
const value = await read();
|
|
20
|
+
if (current === epoch) pausedUntil = 0;
|
|
21
|
+
return value;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : "";
|
|
24
|
+
if (current === epoch && (/(?:^|\s)transport failure for .+: HTTP (?:404|405|503)\b/.test(message) || error instanceof TypeError && /^(?:Failed to fetch|fetch failed|NetworkError when attempting to fetch resource\.?)$/i.test(message))) pausedUntil = now() + 3e4;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
6
30
|
/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
|
|
7
31
|
function selectedSessionId(client) {
|
|
8
32
|
const workspace = client.uiWorkspace?.current;
|
|
@@ -388,7 +412,7 @@ ${scope} {
|
|
|
388
412
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
389
413
|
border-radius: var(--dsw-radius-md);
|
|
390
414
|
background: var(--dsw-alias-bg-layer-1);
|
|
391
|
-
font-family: var(--ds-font-family-code
|
|
415
|
+
font-family: var(--ds-font-family-code);
|
|
392
416
|
font-size: 12px;
|
|
393
417
|
line-height: 20px;
|
|
394
418
|
color: var(--dsw-alias-label-secondary);
|
|
@@ -464,6 +488,162 @@ ${scope} {
|
|
|
464
488
|
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
|
|
465
489
|
}
|
|
466
490
|
//#endregion
|
|
491
|
+
//#region ../dsh-plugin-kit/src/contracts.ts
|
|
492
|
+
/**
|
|
493
|
+
* Remote contribution for model-settings change notifications.
|
|
494
|
+
* Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
|
|
495
|
+
* subscribers degrade silently when that package is not installed.
|
|
496
|
+
* The stream carries invalidations only — `ready` is never a settings snapshot.
|
|
497
|
+
*/
|
|
498
|
+
const MODEL_SETTINGS_CHANGES_REMOTE = {
|
|
499
|
+
package: "@klarkxy/dsh-model-hub",
|
|
500
|
+
descriptors: [{
|
|
501
|
+
id: "modelSettingsChanges/watch",
|
|
502
|
+
service: "modelSettingsChangesController",
|
|
503
|
+
namespace: "modelSettingsChanges",
|
|
504
|
+
method: "watch",
|
|
505
|
+
mode: "stream",
|
|
506
|
+
invocation: { kind: "direct" },
|
|
507
|
+
parameters: [],
|
|
508
|
+
cancellation: { parameter: "signal" }
|
|
509
|
+
}]
|
|
510
|
+
};
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region ../dsh-plugin-kit/src/model-settings-client.ts
|
|
513
|
+
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
514
|
+
function parseModelSettingsChangeFrame(value) {
|
|
515
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid model settings change frame");
|
|
516
|
+
const frame = value;
|
|
517
|
+
if (frame.kind === "ready" && Object.keys(frame).length === 1) return { kind: "ready" };
|
|
518
|
+
if (frame.kind === "changed" && Object.keys(frame).length === 3 && typeof frame.ns === "string" && frame.ns.length > 0 && typeof frame.revision === "number" && Number.isSafeInteger(frame.revision) && frame.revision >= 0) return {
|
|
519
|
+
kind: "changed",
|
|
520
|
+
ns: frame.ns,
|
|
521
|
+
revision: frame.revision
|
|
522
|
+
};
|
|
523
|
+
throw new Error("Invalid model settings change frame");
|
|
524
|
+
}
|
|
525
|
+
MODEL_SETTINGS_CHANGES_REMOTE.package, MODEL_SETTINGS_CHANGES_REMOTE.descriptors.map((descriptor) => ({
|
|
526
|
+
...descriptor,
|
|
527
|
+
result: {
|
|
528
|
+
mode: "strict",
|
|
529
|
+
typeSymbol: "ModelSettings.ChangeFrame",
|
|
530
|
+
create: () => ({ parse: parseModelSettingsChangeFrame })
|
|
531
|
+
}
|
|
532
|
+
}));
|
|
533
|
+
/** Native RemoteStream owns carrier reconnects; no timers or browser transport.
|
|
534
|
+
* A factory shares one logical stream while at least one subscriber remains. */
|
|
535
|
+
function createModelSettingsSubscriber(ctx, namespace) {
|
|
536
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
537
|
+
let injection;
|
|
538
|
+
let current;
|
|
539
|
+
let lifecycle = 0;
|
|
540
|
+
let failureMessage;
|
|
541
|
+
const report = (message) => {
|
|
542
|
+
failureMessage = message;
|
|
543
|
+
for (const listener of [...listeners]) try {
|
|
544
|
+
listener.error?.(message);
|
|
545
|
+
} catch {}
|
|
546
|
+
};
|
|
547
|
+
const invalidate = () => {
|
|
548
|
+
for (const listener of [...listeners]) {
|
|
549
|
+
if (!listeners.has(listener)) continue;
|
|
550
|
+
try {
|
|
551
|
+
listener.change();
|
|
552
|
+
} catch (error) {
|
|
553
|
+
try {
|
|
554
|
+
listener.error?.(error instanceof Error ? error.message : String(error));
|
|
555
|
+
} catch {}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
const start = () => {
|
|
560
|
+
const life = ++lifecycle;
|
|
561
|
+
injection = ctx.inject(["remote", "remote.modelSettingsChanges"], (injected) => {
|
|
562
|
+
if (life !== lifecycle || !listeners.size) return;
|
|
563
|
+
const scope = injected;
|
|
564
|
+
const stream = scope.remote.$stream({
|
|
565
|
+
name: "Model settings sync",
|
|
566
|
+
open: (signal) => scope.remote.modelSettingsChanges.watch(signal),
|
|
567
|
+
ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
|
|
568
|
+
});
|
|
569
|
+
const session = {
|
|
570
|
+
stream,
|
|
571
|
+
active: true
|
|
572
|
+
};
|
|
573
|
+
current = session;
|
|
574
|
+
scope.effect(() => () => {
|
|
575
|
+
session.active = false;
|
|
576
|
+
if (current === session) {
|
|
577
|
+
current = void 0;
|
|
578
|
+
if (life === lifecycle && listeners.size) report("Model settings sync service unavailable");
|
|
579
|
+
}
|
|
580
|
+
return stream.dispose();
|
|
581
|
+
});
|
|
582
|
+
(async () => {
|
|
583
|
+
let generation;
|
|
584
|
+
let ready = false;
|
|
585
|
+
try {
|
|
586
|
+
for await (const item of stream) {
|
|
587
|
+
if (!session.active || life !== lifecycle || !listeners.size) break;
|
|
588
|
+
if (item.signal.aborted) continue;
|
|
589
|
+
if (generation !== item.generation) {
|
|
590
|
+
generation = item.generation;
|
|
591
|
+
ready = false;
|
|
592
|
+
}
|
|
593
|
+
const frame = parseModelSettingsChangeFrame(item.value);
|
|
594
|
+
if (frame.kind === "ready") {
|
|
595
|
+
ready = true;
|
|
596
|
+
failureMessage = void 0;
|
|
597
|
+
item.accept();
|
|
598
|
+
invalidate();
|
|
599
|
+
} else {
|
|
600
|
+
if (!ready) throw new Error("Model settings sync change arrived before ready");
|
|
601
|
+
if (namespace === "*" || frame.ns === namespace) invalidate();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
} catch (error) {
|
|
605
|
+
if (session.active && life === lifecycle) report(error instanceof Error ? error.message : String(error));
|
|
606
|
+
} finally {
|
|
607
|
+
if (current === session) current = void 0;
|
|
608
|
+
session.active = false;
|
|
609
|
+
await stream.dispose().catch(() => {});
|
|
610
|
+
}
|
|
611
|
+
})();
|
|
612
|
+
});
|
|
613
|
+
queueMicrotask(() => {
|
|
614
|
+
if (life === lifecycle && listeners.size && !current) report("Model settings sync service unavailable");
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
return Object.freeze({ subscribe(onChange, onError) {
|
|
618
|
+
const listener = {
|
|
619
|
+
change: onChange,
|
|
620
|
+
error: onError
|
|
621
|
+
};
|
|
622
|
+
listeners.add(listener);
|
|
623
|
+
if (!injection) start();
|
|
624
|
+
else if (failureMessage) queueMicrotask(() => {
|
|
625
|
+
if (listeners.has(listener) && failureMessage) try {
|
|
626
|
+
listener.error?.(failureMessage);
|
|
627
|
+
} catch {}
|
|
628
|
+
});
|
|
629
|
+
return () => {
|
|
630
|
+
if (!listeners.delete(listener) || listeners.size) return;
|
|
631
|
+
lifecycle++;
|
|
632
|
+
if (current) {
|
|
633
|
+
current.active = false;
|
|
634
|
+
current.stream.dispose().catch(() => {});
|
|
635
|
+
}
|
|
636
|
+
current = void 0;
|
|
637
|
+
failureMessage = void 0;
|
|
638
|
+
const previous = injection;
|
|
639
|
+
injection = void 0;
|
|
640
|
+
try {
|
|
641
|
+
Promise.resolve(previous?.dispose()).catch(() => {});
|
|
642
|
+
} catch {}
|
|
643
|
+
};
|
|
644
|
+
} });
|
|
645
|
+
}
|
|
646
|
+
//#endregion
|
|
467
647
|
//#region src/contracts.ts
|
|
468
648
|
const RPC_CHANNEL = "/dsh-current-title";
|
|
469
649
|
const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
@@ -482,14 +662,16 @@ const titleCopy = {
|
|
|
482
662
|
loading: "正在读取标题设置…",
|
|
483
663
|
model: "标题模型",
|
|
484
664
|
modelDefault: "跟随会话模型",
|
|
485
|
-
effort: "思考强度",
|
|
486
|
-
effortDefault: "默认",
|
|
487
665
|
cadence: "更新节奏",
|
|
488
666
|
cadenceAll: "跟随当前任务(每条消息)",
|
|
489
667
|
cadenceFirst: "仅首条消息",
|
|
490
668
|
saved: "已保存。",
|
|
491
669
|
failed: "无法读取标题设置。",
|
|
492
|
-
inactive: "标题服务未接管,新消息暂时不会自动更新。"
|
|
670
|
+
inactive: "标题服务未接管,新消息暂时不会自动更新。",
|
|
671
|
+
retry: "重试",
|
|
672
|
+
externalChange: "设置已在别处修改。提示词草稿已保留,请重新加载后确认。",
|
|
673
|
+
syncUnavailable: "自动刷新不可用,请手动重新加载设置。",
|
|
674
|
+
reload: "重新加载(丢弃草稿)"
|
|
493
675
|
},
|
|
494
676
|
en: {
|
|
495
677
|
label: "Auto Title",
|
|
@@ -503,16 +685,38 @@ const titleCopy = {
|
|
|
503
685
|
loading: "Loading title settings…",
|
|
504
686
|
model: "Title model",
|
|
505
687
|
modelDefault: "Use the session model",
|
|
506
|
-
effort: "Reasoning effort",
|
|
507
|
-
effortDefault: "Default",
|
|
508
688
|
cadence: "Update cadence",
|
|
509
689
|
cadenceAll: "Track the current task (every message)",
|
|
510
690
|
cadenceFirst: "First message only",
|
|
511
691
|
saved: "Saved.",
|
|
512
692
|
failed: "Could not read title settings.",
|
|
513
|
-
inactive: "Title generation is not active, so new messages will not rename this session."
|
|
693
|
+
inactive: "Title generation is not active, so new messages will not rename this session.",
|
|
694
|
+
retry: "Retry",
|
|
695
|
+
externalChange: "Settings changed elsewhere. Prompt draft preserved; reload and review.",
|
|
696
|
+
syncUnavailable: "Automatic refresh unavailable; reload settings manually.",
|
|
697
|
+
reload: "Reload (discard draft)"
|
|
514
698
|
}
|
|
515
699
|
};
|
|
700
|
+
/** Only plugin-owned defaults are translated; model output and remote diagnostics stay intact. */
|
|
701
|
+
const titleMessages = [
|
|
702
|
+
["标题设置无效。", "Invalid title settings."],
|
|
703
|
+
["设置已更新,请刷新后重试。", "Settings changed. Refresh and try again."],
|
|
704
|
+
["无法保存标题设置。", "Could not save title settings."],
|
|
705
|
+
["缺少会话。", "A session is required."],
|
|
706
|
+
["找不到该会话。", "Session not found."],
|
|
707
|
+
["标题服务未就绪。", "Title service is not ready."],
|
|
708
|
+
["已取消。", "Cancelled."],
|
|
709
|
+
["标题生成失败。", "Title generation failed."],
|
|
710
|
+
["未知操作。", "Unknown operation."],
|
|
711
|
+
["缺少所需的宿主服务。", "required host services are missing"],
|
|
712
|
+
["原生标题服务位置已被占用。", "native title provider slot is occupied"],
|
|
713
|
+
["其他标题服务已接管;先前的服务未恢复。", "another title provider owns the slot; previous owner was not restored"],
|
|
714
|
+
[titleCopy.zh.saved, titleCopy.en.saved],
|
|
715
|
+
[titleCopy.zh.failed, titleCopy.en.failed]
|
|
716
|
+
];
|
|
717
|
+
function titleMessage(message, locale) {
|
|
718
|
+
return titleMessages.find((pair) => pair.includes(message))?.[locale === "zh" ? 0 : 1] ?? message;
|
|
719
|
+
}
|
|
516
720
|
/** `undefined` draft = not edited; an empty string is a real edit (use built-in instruction). */
|
|
517
721
|
function promptFieldValue(draft, saved) {
|
|
518
722
|
return draft ?? saved;
|
|
@@ -661,28 +865,17 @@ function catalogChoices(value, selected) {
|
|
|
661
865
|
const id = typeof item.id === "string" ? item.id : "";
|
|
662
866
|
if (!id) continue;
|
|
663
867
|
const name = typeof item.name === "string" && item.name ? item.name : id;
|
|
664
|
-
const reasoning = item.reasoning && typeof item.reasoning === "object" ? item.reasoning : void 0;
|
|
665
|
-
const efforts = Array.isArray(reasoning?.efforts) ? reasoning.efforts.flatMap((effort) => {
|
|
666
|
-
if (!effort || typeof effort !== "object") return [];
|
|
667
|
-
const entry = effort;
|
|
668
|
-
return typeof entry.id === "string" && entry.id ? [{
|
|
669
|
-
id: entry.id,
|
|
670
|
-
name: typeof entry.name === "string" && entry.name ? entry.name : entry.id
|
|
671
|
-
}] : [];
|
|
672
|
-
}) : [];
|
|
673
868
|
choices.push({
|
|
674
869
|
provider,
|
|
675
870
|
model: id,
|
|
676
|
-
label: `${providerName} / ${name}
|
|
677
|
-
efforts
|
|
871
|
+
label: `${providerName} / ${name}`
|
|
678
872
|
});
|
|
679
873
|
}
|
|
680
874
|
}
|
|
681
875
|
if (selected.provider && selected.model && !choices.some((item) => item.provider === selected.provider && item.model === selected.model)) choices.push({
|
|
682
876
|
provider: selected.provider,
|
|
683
877
|
model: selected.model,
|
|
684
|
-
label: `${selected.provider} / ${selected.model}
|
|
685
|
-
efforts: []
|
|
878
|
+
label: `${selected.provider} / ${selected.model}`
|
|
686
879
|
});
|
|
687
880
|
return choices;
|
|
688
881
|
}
|
|
@@ -690,24 +883,55 @@ function TitleSettings(props) {
|
|
|
690
883
|
const seat = useNativeSeat(props.client);
|
|
691
884
|
const sessionId = seat.sessionId;
|
|
692
885
|
const text = titleCopy[seat.locale];
|
|
886
|
+
const messages = titleCopy.en;
|
|
693
887
|
const [status, setStatus] = (0, react.useState)();
|
|
694
888
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
695
889
|
const [note, setNote] = (0, react.useState)("");
|
|
696
890
|
const [error, setError] = (0, react.useState)("");
|
|
697
891
|
const [prompt, setPrompt] = (0, react.useState)(void 0);
|
|
698
892
|
const [choices, setChoices] = (0, react.useState)([]);
|
|
893
|
+
const [externalChange, setExternalChange] = (0, react.useState)(false);
|
|
894
|
+
const [syncError, setSyncError] = (0, react.useState)("");
|
|
895
|
+
const [loadingSettings, setLoadingSettings] = (0, react.useState)(true);
|
|
896
|
+
const dirty = (0, react.useRef)(false);
|
|
897
|
+
const external = (0, react.useRef)(false);
|
|
898
|
+
const reading = (0, react.useRef)(false);
|
|
899
|
+
const pending = (0, react.useRef)(false);
|
|
900
|
+
const syncRef = (0, react.useRef)(() => {});
|
|
699
901
|
const workRef = (0, react.useRef)(void 0);
|
|
700
902
|
if (!workRef.current) workRef.current = createTitleClientWork();
|
|
701
903
|
const work = workRef.current;
|
|
904
|
+
const readGateRef = (0, react.useRef)(void 0);
|
|
905
|
+
if (!readGateRef.current) readGateRef.current = createPluginReadGate();
|
|
906
|
+
const readGate = readGateRef.current;
|
|
702
907
|
const busyRef = (0, react.useRef)(busy);
|
|
703
908
|
busyRef.current = busy;
|
|
909
|
+
const changeBusy = (value) => {
|
|
910
|
+
busyRef.current = value;
|
|
911
|
+
setBusy(value);
|
|
912
|
+
if (!value && pending.current) syncRef.current();
|
|
913
|
+
};
|
|
704
914
|
const call = (endpoint, payload = {}) => props.client.connection.rpc.call(RPC_CHANNEL, endpoint, payload);
|
|
915
|
+
const readCall = (endpoint, payload) => readGate.run(() => call(endpoint, payload));
|
|
705
916
|
function commitMarker(next) {
|
|
706
917
|
applyMarkerFromStatus(props.marker, next, true);
|
|
707
918
|
}
|
|
708
919
|
(0, react.useEffect)(() => () => {
|
|
920
|
+
readGate.reset();
|
|
709
921
|
work.dispose();
|
|
710
|
-
}, [work]);
|
|
922
|
+
}, [work, readGate]);
|
|
923
|
+
(0, react.useEffect)(() => props.client.connection.generation?.subscribe(() => {
|
|
924
|
+
readGate.reset();
|
|
925
|
+
work.beginLoad();
|
|
926
|
+
reading.current = false;
|
|
927
|
+
setLoadingSettings(false);
|
|
928
|
+
busyRef.current = false;
|
|
929
|
+
setBusy(false);
|
|
930
|
+
}), [
|
|
931
|
+
props.client,
|
|
932
|
+
work,
|
|
933
|
+
readGate
|
|
934
|
+
]);
|
|
711
935
|
(0, react.useEffect)(() => {
|
|
712
936
|
const catalog = props.client.remote?.session?.modelCatalog;
|
|
713
937
|
if (!catalog) return void 0;
|
|
@@ -730,18 +954,34 @@ function TitleSettings(props) {
|
|
|
730
954
|
]);
|
|
731
955
|
(0, react.useEffect)(() => {
|
|
732
956
|
const generation = work.beginLoad();
|
|
957
|
+
reading.current = true;
|
|
958
|
+
pending.current = false;
|
|
959
|
+
setLoadingSettings(true);
|
|
960
|
+
readGate.reset();
|
|
733
961
|
const isCurrent = () => work.isLoad(generation);
|
|
734
962
|
setBusy(false);
|
|
735
963
|
setError("");
|
|
736
964
|
setNote("");
|
|
737
965
|
runTitleStatusLoad({
|
|
738
|
-
call,
|
|
966
|
+
call: readCall,
|
|
739
967
|
sessionId: sessionId || void 0,
|
|
740
968
|
isCurrent,
|
|
741
|
-
failedMessage:
|
|
742
|
-
onStatus:
|
|
743
|
-
|
|
969
|
+
failedMessage: messages.failed,
|
|
970
|
+
onStatus: (next) => {
|
|
971
|
+
if (!pending.current) setStatus((current) => current && external.current ? {
|
|
972
|
+
...next,
|
|
973
|
+
settings: current.settings
|
|
974
|
+
} : next);
|
|
975
|
+
},
|
|
976
|
+
onError: (message) => {
|
|
977
|
+
if (!pending.current) setError(message);
|
|
978
|
+
},
|
|
744
979
|
onMarker: commitMarker
|
|
980
|
+
}).finally(() => {
|
|
981
|
+
if (!work.isLoad(generation)) return;
|
|
982
|
+
reading.current = false;
|
|
983
|
+
setLoadingSettings(false);
|
|
984
|
+
if (pending.current) syncRef.current();
|
|
745
985
|
});
|
|
746
986
|
return () => {
|
|
747
987
|
if (work.isLoad(generation)) work.beginLoad();
|
|
@@ -750,53 +990,96 @@ function TitleSettings(props) {
|
|
|
750
990
|
props.client,
|
|
751
991
|
props.marker,
|
|
752
992
|
sessionId,
|
|
753
|
-
text.failed,
|
|
754
993
|
work
|
|
755
994
|
]);
|
|
756
995
|
const refreshStatus = (0, react.useCallback)(() => {
|
|
757
|
-
if (shouldSkipTitleRefresh({ busy: busyRef.current }) || work.disposed) return;
|
|
758
|
-
|
|
996
|
+
if (!readGate.canRead() || shouldSkipTitleRefresh({ busy: busyRef.current }) || work.disposed) return;
|
|
997
|
+
if (reading.current) return;
|
|
998
|
+
reading.current = true;
|
|
999
|
+
setLoadingSettings(true);
|
|
1000
|
+
readGate.reset();
|
|
1001
|
+
const token = work.beginRequest();
|
|
759
1002
|
runTitleStatusLoad({
|
|
760
|
-
call,
|
|
1003
|
+
call: readCall,
|
|
761
1004
|
sessionId: sessionId || void 0,
|
|
762
|
-
isCurrent: () => work.
|
|
763
|
-
failedMessage:
|
|
1005
|
+
isCurrent: () => work.isRequest(token) && !shouldSkipTitleRefresh({ busy: busyRef.current }),
|
|
1006
|
+
failedMessage: messages.failed,
|
|
764
1007
|
onStatus: (next) => {
|
|
765
|
-
if (shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
766
|
-
setStatus(
|
|
1008
|
+
if (pending.current || shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
1009
|
+
setStatus((current) => current && external.current ? {
|
|
1010
|
+
...next,
|
|
1011
|
+
settings: current.settings
|
|
1012
|
+
} : next);
|
|
1013
|
+
setError("");
|
|
1014
|
+
},
|
|
1015
|
+
onError: (message) => {
|
|
1016
|
+
if (!pending.current) setError(message);
|
|
767
1017
|
},
|
|
768
|
-
onError: () => {},
|
|
769
1018
|
onMarker: (next) => {
|
|
770
|
-
if (shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
1019
|
+
if (!next || shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
771
1020
|
commitMarker(next);
|
|
772
1021
|
}
|
|
1022
|
+
}).finally(() => {
|
|
1023
|
+
if (!work.isRequest(token)) return;
|
|
1024
|
+
reading.current = false;
|
|
1025
|
+
setLoadingSettings(false);
|
|
1026
|
+
if (pending.current) syncRef.current();
|
|
773
1027
|
});
|
|
774
1028
|
}, [
|
|
775
1029
|
props.client,
|
|
776
1030
|
props.marker,
|
|
777
1031
|
sessionId,
|
|
778
|
-
text.failed,
|
|
779
1032
|
work
|
|
780
1033
|
]);
|
|
781
1034
|
useFeatureRefresh(props.client, sessionId, refreshStatus, status?.session?.generating === true && !busy, Boolean(sessionId));
|
|
1035
|
+
syncRef.current = () => {
|
|
1036
|
+
if (work.disposed) return;
|
|
1037
|
+
if (reading.current || busyRef.current) {
|
|
1038
|
+
pending.current = true;
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
pending.current = false;
|
|
1042
|
+
if (dirty.current) {
|
|
1043
|
+
external.current = true;
|
|
1044
|
+
setExternalChange(true);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
readGate.reset();
|
|
1048
|
+
refreshStatus();
|
|
1049
|
+
};
|
|
1050
|
+
(0, react.useEffect)(() => {
|
|
1051
|
+
setSyncError("");
|
|
1052
|
+
let live = true;
|
|
1053
|
+
const stop = props.subscribeSettings?.(() => {
|
|
1054
|
+
if (!live) return;
|
|
1055
|
+
setSyncError("");
|
|
1056
|
+
if (dirty.current && !busyRef.current) {
|
|
1057
|
+
external.current = true;
|
|
1058
|
+
setExternalChange(true);
|
|
1059
|
+
}
|
|
1060
|
+
syncRef.current();
|
|
1061
|
+
}, (message) => {
|
|
1062
|
+
if (live) setSyncError(message);
|
|
1063
|
+
});
|
|
1064
|
+
return () => {
|
|
1065
|
+
live = false;
|
|
1066
|
+
stop?.();
|
|
1067
|
+
};
|
|
1068
|
+
}, [props.subscribeSettings, props.client]);
|
|
782
1069
|
const savedPrompt = status?.settings.prompt ?? "";
|
|
783
1070
|
const savedModel = status?.settings.model ?? {
|
|
784
1071
|
provider: "",
|
|
785
1072
|
model: ""
|
|
786
1073
|
};
|
|
1074
|
+
const selectableChoices = choices.some((choice) => choice.provider === savedModel.provider && choice.model === savedModel.model) ? choices : [...choices, ...catalogChoices({}, savedModel)];
|
|
787
1075
|
const modelKey = savedModel.provider && savedModel.model ? `${savedModel.provider}\u001f${savedModel.model}` : "";
|
|
788
1076
|
const title = status?.session?.title;
|
|
789
1077
|
const pinned = status?.session?.pinned === true;
|
|
790
1078
|
const weOwn = status?.support.weOwn === true;
|
|
791
|
-
const canRegenerate = Boolean(sessionId) && weOwn && !busy;
|
|
792
|
-
const selectedChoice = choices.find((item) => item.provider === savedModel.provider && item.model === savedModel.model);
|
|
793
|
-
const efforts = selectedChoice ? [...selectedChoice.efforts] : [];
|
|
794
|
-
if (selectedChoice && savedModel.reasoningEffort && !efforts.some((item) => item.id === savedModel.reasoningEffort)) efforts.push({
|
|
795
|
-
id: savedModel.reasoningEffort,
|
|
796
|
-
name: savedModel.reasoningEffort
|
|
797
|
-
});
|
|
1079
|
+
const canRegenerate = Boolean(sessionId) && weOwn && !busy && !loadingSettings;
|
|
798
1080
|
function saveSettings(patch) {
|
|
799
|
-
if (!status) return;
|
|
1081
|
+
if (!status || busyRef.current || reading.current || external.current) return;
|
|
1082
|
+
readGate.reset();
|
|
800
1083
|
const token = work.beginRequest();
|
|
801
1084
|
setError("");
|
|
802
1085
|
setNote("");
|
|
@@ -805,9 +1088,9 @@ function TitleSettings(props) {
|
|
|
805
1088
|
...patch,
|
|
806
1089
|
expectedRevision: status.settings.revision,
|
|
807
1090
|
isCurrent: () => work.isRequest(token),
|
|
808
|
-
savedMessage:
|
|
809
|
-
failedMessage:
|
|
810
|
-
onBusy:
|
|
1091
|
+
savedMessage: messages.saved,
|
|
1092
|
+
failedMessage: messages.failed,
|
|
1093
|
+
onBusy: changeBusy,
|
|
811
1094
|
onSettings: (settings) => setStatus((current) => current ? {
|
|
812
1095
|
...current,
|
|
813
1096
|
settings
|
|
@@ -818,6 +1101,7 @@ function TitleSettings(props) {
|
|
|
818
1101
|
}
|
|
819
1102
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
820
1103
|
className: "current-title-settings ct-root dsh-ui-panel",
|
|
1104
|
+
lang: seat.locale,
|
|
821
1105
|
"data-testid": "current-title-settings",
|
|
822
1106
|
children: [
|
|
823
1107
|
!status && !error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -842,26 +1126,59 @@ function TitleSettings(props) {
|
|
|
842
1126
|
status && !weOwn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
843
1127
|
role: "status",
|
|
844
1128
|
className: "dsh-ui-hint",
|
|
845
|
-
children: [text.inactive, status.support.limitation ? ` ${status.support.limitation}` : ""]
|
|
1129
|
+
children: [text.inactive, status.support.limitation ? ` ${titleMessage(status.support.limitation, seat.locale)}` : ""]
|
|
846
1130
|
}) : null,
|
|
847
|
-
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1131
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
848
1132
|
role: "alert",
|
|
849
1133
|
className: "dsh-ui-error dsh-ui-wrap",
|
|
850
|
-
children: error
|
|
851
|
-
})
|
|
1134
|
+
children: titleMessage(error, seat.locale)
|
|
1135
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1136
|
+
variant: "outline",
|
|
1137
|
+
size: "sm",
|
|
1138
|
+
disabled: busy,
|
|
1139
|
+
onClick: () => {
|
|
1140
|
+
readGate.reset();
|
|
1141
|
+
refreshStatus();
|
|
1142
|
+
},
|
|
1143
|
+
children: text.retry
|
|
1144
|
+
})] }) : null,
|
|
852
1145
|
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
853
1146
|
role: "status",
|
|
854
1147
|
className: "dsh-ui-notice",
|
|
855
|
-
children: note
|
|
1148
|
+
children: titleMessage(note, seat.locale)
|
|
1149
|
+
}) : null,
|
|
1150
|
+
externalChange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1151
|
+
role: "alert",
|
|
1152
|
+
className: "dsh-ui-error",
|
|
1153
|
+
children: text.externalChange
|
|
1154
|
+
}) : null,
|
|
1155
|
+
syncError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1156
|
+
role: "status",
|
|
1157
|
+
className: "dsh-ui-hint",
|
|
1158
|
+
children: text.syncUnavailable
|
|
856
1159
|
}) : null,
|
|
857
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
858
1161
|
className: "dsh-ui-actions",
|
|
859
|
-
children:
|
|
1162
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1163
|
+
variant: "ghost",
|
|
1164
|
+
size: "sm",
|
|
1165
|
+
disabled: busy || loadingSettings,
|
|
1166
|
+
onClick: () => {
|
|
1167
|
+
setPrompt(void 0);
|
|
1168
|
+
dirty.current = false;
|
|
1169
|
+
external.current = false;
|
|
1170
|
+
setExternalChange(false);
|
|
1171
|
+
readGate.reset();
|
|
1172
|
+
syncRef.current();
|
|
1173
|
+
},
|
|
1174
|
+
children: text.reload
|
|
1175
|
+
}), sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
860
1176
|
variant: "outline",
|
|
861
1177
|
size: "md",
|
|
862
1178
|
disabled: !canRegenerate,
|
|
863
1179
|
onClick: () => {
|
|
864
1180
|
if (!sessionId) return;
|
|
1181
|
+
readGate.reset();
|
|
865
1182
|
const token = work.beginRequest();
|
|
866
1183
|
setError("");
|
|
867
1184
|
setNote("");
|
|
@@ -869,15 +1186,15 @@ function TitleSettings(props) {
|
|
|
869
1186
|
call,
|
|
870
1187
|
sessionId,
|
|
871
1188
|
isCurrent: () => work.isRequest(token),
|
|
872
|
-
failedMessage:
|
|
873
|
-
onBusy:
|
|
1189
|
+
failedMessage: messages.failed,
|
|
1190
|
+
onBusy: changeBusy,
|
|
874
1191
|
onStatus: setStatus,
|
|
875
1192
|
onError: setError,
|
|
876
1193
|
onMarker: commitMarker
|
|
877
1194
|
});
|
|
878
1195
|
},
|
|
879
1196
|
children: text.regenerate
|
|
880
|
-
}) : null
|
|
1197
|
+
}) : null]
|
|
881
1198
|
}),
|
|
882
1199
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
883
1200
|
className: "dsh-ui-field",
|
|
@@ -891,18 +1208,21 @@ function TitleSettings(props) {
|
|
|
891
1208
|
value: status ? promptFieldValue(prompt, savedPrompt) : "",
|
|
892
1209
|
maxLength: 4e3,
|
|
893
1210
|
rows: 5,
|
|
894
|
-
disabled: busy || !status,
|
|
1211
|
+
disabled: busy || loadingSettings || !status,
|
|
895
1212
|
onChange: (event) => {
|
|
1213
|
+
dirty.current = true;
|
|
896
1214
|
setPrompt(event.target.value);
|
|
897
1215
|
setNote("");
|
|
898
1216
|
},
|
|
899
1217
|
onBlur: () => {
|
|
900
|
-
if (!status) return;
|
|
1218
|
+
if (!status || busyRef.current || reading.current || external.current) return;
|
|
901
1219
|
const next = promptToSave(prompt, savedPrompt);
|
|
902
1220
|
if (next === void 0) {
|
|
1221
|
+
dirty.current = false;
|
|
903
1222
|
setPrompt(void 0);
|
|
904
1223
|
return;
|
|
905
1224
|
}
|
|
1225
|
+
readGate.reset();
|
|
906
1226
|
const token = work.beginRequest();
|
|
907
1227
|
setError("");
|
|
908
1228
|
runTitleSettingsSave({
|
|
@@ -910,14 +1230,15 @@ function TitleSettings(props) {
|
|
|
910
1230
|
prompt: next,
|
|
911
1231
|
expectedRevision: status.settings.revision,
|
|
912
1232
|
isCurrent: () => work.isRequest(token),
|
|
913
|
-
savedMessage:
|
|
914
|
-
failedMessage:
|
|
915
|
-
onBusy:
|
|
1233
|
+
savedMessage: messages.saved,
|
|
1234
|
+
failedMessage: messages.failed,
|
|
1235
|
+
onBusy: changeBusy,
|
|
916
1236
|
onSettings: (settings) => {
|
|
917
1237
|
setStatus((current) => current ? {
|
|
918
1238
|
...current,
|
|
919
1239
|
settings
|
|
920
1240
|
} : current);
|
|
1241
|
+
dirty.current = false;
|
|
921
1242
|
setPrompt(void 0);
|
|
922
1243
|
},
|
|
923
1244
|
onNote: setNote,
|
|
@@ -939,10 +1260,10 @@ function TitleSettings(props) {
|
|
|
939
1260
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
940
1261
|
className: "dsh-ui-select",
|
|
941
1262
|
value: modelKey,
|
|
942
|
-
disabled: busy || !status,
|
|
1263
|
+
disabled: busy || loadingSettings || !status,
|
|
943
1264
|
onChange: (event) => {
|
|
944
1265
|
const value = event.target.value;
|
|
945
|
-
const choice =
|
|
1266
|
+
const choice = selectableChoices.find((item) => `${item.provider}\u001f${item.model}` === value);
|
|
946
1267
|
saveSettings({ model: choice ? {
|
|
947
1268
|
provider: choice.provider,
|
|
948
1269
|
model: choice.model
|
|
@@ -954,41 +1275,12 @@ function TitleSettings(props) {
|
|
|
954
1275
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
955
1276
|
value: "",
|
|
956
1277
|
children: text.modelDefault
|
|
957
|
-
}),
|
|
1278
|
+
}), selectableChoices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
958
1279
|
value: `${choice.provider}\u001f${choice.model}`,
|
|
959
1280
|
children: choice.label
|
|
960
1281
|
}, `${choice.provider}\u001f${choice.model}`))]
|
|
961
1282
|
})]
|
|
962
1283
|
}),
|
|
963
|
-
selectedChoice && efforts.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
964
|
-
className: "dsh-ui-field",
|
|
965
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
966
|
-
className: "dsh-ui-label",
|
|
967
|
-
children: text.effort
|
|
968
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
969
|
-
className: "dsh-ui-select",
|
|
970
|
-
value: savedModel.reasoningEffort ?? "",
|
|
971
|
-
disabled: busy || !status,
|
|
972
|
-
onChange: (event) => {
|
|
973
|
-
const effort = event.target.value;
|
|
974
|
-
const model = {
|
|
975
|
-
provider: savedModel.provider,
|
|
976
|
-
model: savedModel.model
|
|
977
|
-
};
|
|
978
|
-
saveSettings({ model: effort ? {
|
|
979
|
-
...model,
|
|
980
|
-
reasoningEffort: effort
|
|
981
|
-
} : model });
|
|
982
|
-
},
|
|
983
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
984
|
-
value: "",
|
|
985
|
-
children: text.effortDefault
|
|
986
|
-
}), efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
987
|
-
value: effort.id,
|
|
988
|
-
children: effort.name
|
|
989
|
-
}, effort.id))]
|
|
990
|
-
})]
|
|
991
|
-
}) : null,
|
|
992
1284
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
993
1285
|
className: "dsh-ui-field",
|
|
994
1286
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -997,7 +1289,7 @@ function TitleSettings(props) {
|
|
|
997
1289
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
998
1290
|
className: "dsh-ui-select",
|
|
999
1291
|
value: status?.settings.cadence ?? "all-prompts",
|
|
1000
|
-
disabled: busy || !status,
|
|
1292
|
+
disabled: busy || loadingSettings || !status,
|
|
1001
1293
|
onChange: (event) => {
|
|
1002
1294
|
saveSettings({ cadence: event.target.value === "first-prompt" ? "first-prompt" : "all-prompts" });
|
|
1003
1295
|
},
|
|
@@ -1035,6 +1327,8 @@ const styles = `${officialUiCss("ct-root")}
|
|
|
1035
1327
|
`;
|
|
1036
1328
|
function apply(ctx) {
|
|
1037
1329
|
const client = ctx;
|
|
1330
|
+
const settingsSubscriber = createModelSettingsSubscriber(ctx, "@klarkxy/dsh-current-title");
|
|
1331
|
+
const subscribeSettings = (change, error) => settingsSubscriber.subscribe(change, error);
|
|
1038
1332
|
const marker = createTitleClientMarker();
|
|
1039
1333
|
ctx.provide(TITLE_CLIENT_SERVICE, marker);
|
|
1040
1334
|
ctx.effect(() => {
|
|
@@ -1050,7 +1344,8 @@ function apply(ctx) {
|
|
|
1050
1344
|
key: "@klarkxy/dsh-current-title"
|
|
1051
1345
|
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettings, {
|
|
1052
1346
|
client,
|
|
1053
|
-
marker
|
|
1347
|
+
marker,
|
|
1348
|
+
subscribeSettings
|
|
1054
1349
|
}))), "current-title.settings");
|
|
1055
1350
|
ctx.effect(() => {
|
|
1056
1351
|
let live = true;
|