@klarkxy/dsh-current-title 0.2.0 → 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 +3 -0
- package/docs/README.zh-CN.md +3 -0
- package/lib/client.inner.cjs +327 -36
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +327 -36
- 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 +1 -0
- package/lib/index.js +78 -11
- package/lib/index.js.map +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @klarkxy/dsh-current-title
|
|
2
2
|
|
|
3
|
+
<!-- release-state-note -->
|
|
4
|
+
The [repository registry](../../README.md#plugin-lifecycle) tracks Draft → Testing → Published, moving forward only. Fixes, testing and version updates keep published packages Published; each update still passes release checks. First publication explicitly selects Testing packages and records Published only after npm archive verification. See the [release guide](../../RELEASING.md).
|
|
5
|
+
|
|
3
6
|
Keeps a DSH session title on the task you are working on right now, and leaves a name you set by hand alone.
|
|
4
7
|
|
|
5
8
|
[简体中文](docs/README.zh-CN.md)
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @klarkxy/dsh-current-title
|
|
2
2
|
|
|
3
|
+
<!-- release-state-note -->
|
|
4
|
+
插件生命周期见[仓库状态清单](../../../README.zh-CN.md#插件生命周期):草稿 → 测试中 → 已发布,只向前推进。已发布插件的修复、测试和升版保持已发布;每次更新仍须通过发布检查。首次发布须明确选择测试中的包,并在 npm 归档验证成功后登记。详见[发布规则](../../../RELEASING.zh-CN.md)。
|
|
5
|
+
|
|
3
6
|
让会话标题跟着你手头的任务走;你手动改过的名字不会被覆盖。
|
|
4
7
|
|
|
5
8
|
[English](../README.md)
|
package/lib/client.inner.cjs
CHANGED
|
@@ -412,7 +412,7 @@ ${scope} {
|
|
|
412
412
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
413
413
|
border-radius: var(--dsw-radius-md);
|
|
414
414
|
background: var(--dsw-alias-bg-layer-1);
|
|
415
|
-
font-family: var(--ds-font-family-code
|
|
415
|
+
font-family: var(--ds-font-family-code);
|
|
416
416
|
font-size: 12px;
|
|
417
417
|
line-height: 20px;
|
|
418
418
|
color: var(--dsw-alias-label-secondary);
|
|
@@ -488,6 +488,162 @@ ${scope} {
|
|
|
488
488
|
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
|
|
489
489
|
}
|
|
490
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
|
|
491
647
|
//#region src/contracts.ts
|
|
492
648
|
const RPC_CHANNEL = "/dsh-current-title";
|
|
493
649
|
const TITLE_CLIENT_SERVICE = "dshCurrentTitleClient";
|
|
@@ -511,7 +667,11 @@ const titleCopy = {
|
|
|
511
667
|
cadenceFirst: "仅首条消息",
|
|
512
668
|
saved: "已保存。",
|
|
513
669
|
failed: "无法读取标题设置。",
|
|
514
|
-
inactive: "标题服务未接管,新消息暂时不会自动更新。"
|
|
670
|
+
inactive: "标题服务未接管,新消息暂时不会自动更新。",
|
|
671
|
+
retry: "重试",
|
|
672
|
+
externalChange: "设置已在别处修改。提示词草稿已保留,请重新加载后确认。",
|
|
673
|
+
syncUnavailable: "自动刷新不可用,请手动重新加载设置。",
|
|
674
|
+
reload: "重新加载(丢弃草稿)"
|
|
515
675
|
},
|
|
516
676
|
en: {
|
|
517
677
|
label: "Auto Title",
|
|
@@ -530,9 +690,33 @@ const titleCopy = {
|
|
|
530
690
|
cadenceFirst: "First message only",
|
|
531
691
|
saved: "Saved.",
|
|
532
692
|
failed: "Could not read title settings.",
|
|
533
|
-
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)"
|
|
534
698
|
}
|
|
535
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
|
+
}
|
|
536
720
|
/** `undefined` draft = not edited; an empty string is a real edit (use built-in instruction). */
|
|
537
721
|
function promptFieldValue(draft, saved) {
|
|
538
722
|
return draft ?? saved;
|
|
@@ -699,12 +883,21 @@ function TitleSettings(props) {
|
|
|
699
883
|
const seat = useNativeSeat(props.client);
|
|
700
884
|
const sessionId = seat.sessionId;
|
|
701
885
|
const text = titleCopy[seat.locale];
|
|
886
|
+
const messages = titleCopy.en;
|
|
702
887
|
const [status, setStatus] = (0, react.useState)();
|
|
703
888
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
704
889
|
const [note, setNote] = (0, react.useState)("");
|
|
705
890
|
const [error, setError] = (0, react.useState)("");
|
|
706
891
|
const [prompt, setPrompt] = (0, react.useState)(void 0);
|
|
707
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)(() => {});
|
|
708
901
|
const workRef = (0, react.useRef)(void 0);
|
|
709
902
|
if (!workRef.current) workRef.current = createTitleClientWork();
|
|
710
903
|
const work = workRef.current;
|
|
@@ -713,6 +906,11 @@ function TitleSettings(props) {
|
|
|
713
906
|
const readGate = readGateRef.current;
|
|
714
907
|
const busyRef = (0, react.useRef)(busy);
|
|
715
908
|
busyRef.current = busy;
|
|
909
|
+
const changeBusy = (value) => {
|
|
910
|
+
busyRef.current = value;
|
|
911
|
+
setBusy(value);
|
|
912
|
+
if (!value && pending.current) syncRef.current();
|
|
913
|
+
};
|
|
716
914
|
const call = (endpoint, payload = {}) => props.client.connection.rpc.call(RPC_CHANNEL, endpoint, payload);
|
|
717
915
|
const readCall = (endpoint, payload) => readGate.run(() => call(endpoint, payload));
|
|
718
916
|
function commitMarker(next) {
|
|
@@ -725,6 +923,8 @@ function TitleSettings(props) {
|
|
|
725
923
|
(0, react.useEffect)(() => props.client.connection.generation?.subscribe(() => {
|
|
726
924
|
readGate.reset();
|
|
727
925
|
work.beginLoad();
|
|
926
|
+
reading.current = false;
|
|
927
|
+
setLoadingSettings(false);
|
|
728
928
|
busyRef.current = false;
|
|
729
929
|
setBusy(false);
|
|
730
930
|
}), [
|
|
@@ -754,6 +954,9 @@ function TitleSettings(props) {
|
|
|
754
954
|
]);
|
|
755
955
|
(0, react.useEffect)(() => {
|
|
756
956
|
const generation = work.beginLoad();
|
|
957
|
+
reading.current = true;
|
|
958
|
+
pending.current = false;
|
|
959
|
+
setLoadingSettings(true);
|
|
757
960
|
readGate.reset();
|
|
758
961
|
const isCurrent = () => work.isLoad(generation);
|
|
759
962
|
setBusy(false);
|
|
@@ -763,10 +966,22 @@ function TitleSettings(props) {
|
|
|
763
966
|
call: readCall,
|
|
764
967
|
sessionId: sessionId || void 0,
|
|
765
968
|
isCurrent,
|
|
766
|
-
failedMessage:
|
|
767
|
-
onStatus:
|
|
768
|
-
|
|
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
|
+
},
|
|
769
979
|
onMarker: commitMarker
|
|
980
|
+
}).finally(() => {
|
|
981
|
+
if (!work.isLoad(generation)) return;
|
|
982
|
+
reading.current = false;
|
|
983
|
+
setLoadingSettings(false);
|
|
984
|
+
if (pending.current) syncRef.current();
|
|
770
985
|
});
|
|
771
986
|
return () => {
|
|
772
987
|
if (work.isLoad(generation)) work.beginLoad();
|
|
@@ -775,49 +990,95 @@ function TitleSettings(props) {
|
|
|
775
990
|
props.client,
|
|
776
991
|
props.marker,
|
|
777
992
|
sessionId,
|
|
778
|
-
text.failed,
|
|
779
993
|
work
|
|
780
994
|
]);
|
|
781
995
|
const refreshStatus = (0, react.useCallback)(() => {
|
|
782
996
|
if (!readGate.canRead() || shouldSkipTitleRefresh({ busy: busyRef.current }) || work.disposed) return;
|
|
997
|
+
if (reading.current) return;
|
|
998
|
+
reading.current = true;
|
|
999
|
+
setLoadingSettings(true);
|
|
783
1000
|
readGate.reset();
|
|
784
1001
|
const token = work.beginRequest();
|
|
785
1002
|
runTitleStatusLoad({
|
|
786
1003
|
call: readCall,
|
|
787
1004
|
sessionId: sessionId || void 0,
|
|
788
1005
|
isCurrent: () => work.isRequest(token) && !shouldSkipTitleRefresh({ busy: busyRef.current }),
|
|
789
|
-
failedMessage:
|
|
1006
|
+
failedMessage: messages.failed,
|
|
790
1007
|
onStatus: (next) => {
|
|
791
|
-
if (shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
792
|
-
setStatus(
|
|
1008
|
+
if (pending.current || shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
1009
|
+
setStatus((current) => current && external.current ? {
|
|
1010
|
+
...next,
|
|
1011
|
+
settings: current.settings
|
|
1012
|
+
} : next);
|
|
793
1013
|
setError("");
|
|
794
1014
|
},
|
|
795
|
-
onError:
|
|
1015
|
+
onError: (message) => {
|
|
1016
|
+
if (!pending.current) setError(message);
|
|
1017
|
+
},
|
|
796
1018
|
onMarker: (next) => {
|
|
797
1019
|
if (!next || shouldSkipTitleRefresh({ busy: busyRef.current })) return;
|
|
798
1020
|
commitMarker(next);
|
|
799
1021
|
}
|
|
1022
|
+
}).finally(() => {
|
|
1023
|
+
if (!work.isRequest(token)) return;
|
|
1024
|
+
reading.current = false;
|
|
1025
|
+
setLoadingSettings(false);
|
|
1026
|
+
if (pending.current) syncRef.current();
|
|
800
1027
|
});
|
|
801
1028
|
}, [
|
|
802
1029
|
props.client,
|
|
803
1030
|
props.marker,
|
|
804
1031
|
sessionId,
|
|
805
|
-
text.failed,
|
|
806
1032
|
work
|
|
807
1033
|
]);
|
|
808
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]);
|
|
809
1069
|
const savedPrompt = status?.settings.prompt ?? "";
|
|
810
1070
|
const savedModel = status?.settings.model ?? {
|
|
811
1071
|
provider: "",
|
|
812
1072
|
model: ""
|
|
813
1073
|
};
|
|
1074
|
+
const selectableChoices = choices.some((choice) => choice.provider === savedModel.provider && choice.model === savedModel.model) ? choices : [...choices, ...catalogChoices({}, savedModel)];
|
|
814
1075
|
const modelKey = savedModel.provider && savedModel.model ? `${savedModel.provider}\u001f${savedModel.model}` : "";
|
|
815
1076
|
const title = status?.session?.title;
|
|
816
1077
|
const pinned = status?.session?.pinned === true;
|
|
817
1078
|
const weOwn = status?.support.weOwn === true;
|
|
818
|
-
const canRegenerate = Boolean(sessionId) && weOwn && !busy;
|
|
1079
|
+
const canRegenerate = Boolean(sessionId) && weOwn && !busy && !loadingSettings;
|
|
819
1080
|
function saveSettings(patch) {
|
|
820
|
-
if (!status) return;
|
|
1081
|
+
if (!status || busyRef.current || reading.current || external.current) return;
|
|
821
1082
|
readGate.reset();
|
|
822
1083
|
const token = work.beginRequest();
|
|
823
1084
|
setError("");
|
|
@@ -827,9 +1088,9 @@ function TitleSettings(props) {
|
|
|
827
1088
|
...patch,
|
|
828
1089
|
expectedRevision: status.settings.revision,
|
|
829
1090
|
isCurrent: () => work.isRequest(token),
|
|
830
|
-
savedMessage:
|
|
831
|
-
failedMessage:
|
|
832
|
-
onBusy:
|
|
1091
|
+
savedMessage: messages.saved,
|
|
1092
|
+
failedMessage: messages.failed,
|
|
1093
|
+
onBusy: changeBusy,
|
|
833
1094
|
onSettings: (settings) => setStatus((current) => current ? {
|
|
834
1095
|
...current,
|
|
835
1096
|
settings
|
|
@@ -840,6 +1101,7 @@ function TitleSettings(props) {
|
|
|
840
1101
|
}
|
|
841
1102
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
842
1103
|
className: "current-title-settings ct-root dsh-ui-panel",
|
|
1104
|
+
lang: seat.locale,
|
|
843
1105
|
"data-testid": "current-title-settings",
|
|
844
1106
|
children: [
|
|
845
1107
|
!status && !error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -864,12 +1126,12 @@ function TitleSettings(props) {
|
|
|
864
1126
|
status && !weOwn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
865
1127
|
role: "status",
|
|
866
1128
|
className: "dsh-ui-hint",
|
|
867
|
-
children: [text.inactive, status.support.limitation ? ` ${status.support.limitation}` : ""]
|
|
1129
|
+
children: [text.inactive, status.support.limitation ? ` ${titleMessage(status.support.limitation, seat.locale)}` : ""]
|
|
868
1130
|
}) : null,
|
|
869
1131
|
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
870
1132
|
role: "alert",
|
|
871
1133
|
className: "dsh-ui-error dsh-ui-wrap",
|
|
872
|
-
children: error
|
|
1134
|
+
children: titleMessage(error, seat.locale)
|
|
873
1135
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
874
1136
|
variant: "outline",
|
|
875
1137
|
size: "sm",
|
|
@@ -878,16 +1140,39 @@ function TitleSettings(props) {
|
|
|
878
1140
|
readGate.reset();
|
|
879
1141
|
refreshStatus();
|
|
880
1142
|
},
|
|
881
|
-
children:
|
|
1143
|
+
children: text.retry
|
|
882
1144
|
})] }) : null,
|
|
883
1145
|
note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
884
1146
|
role: "status",
|
|
885
1147
|
className: "dsh-ui-notice",
|
|
886
|
-
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
|
|
887
1159
|
}) : null,
|
|
888
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
889
1161
|
className: "dsh-ui-actions",
|
|
890
|
-
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, {
|
|
891
1176
|
variant: "outline",
|
|
892
1177
|
size: "md",
|
|
893
1178
|
disabled: !canRegenerate,
|
|
@@ -901,15 +1186,15 @@ function TitleSettings(props) {
|
|
|
901
1186
|
call,
|
|
902
1187
|
sessionId,
|
|
903
1188
|
isCurrent: () => work.isRequest(token),
|
|
904
|
-
failedMessage:
|
|
905
|
-
onBusy:
|
|
1189
|
+
failedMessage: messages.failed,
|
|
1190
|
+
onBusy: changeBusy,
|
|
906
1191
|
onStatus: setStatus,
|
|
907
1192
|
onError: setError,
|
|
908
1193
|
onMarker: commitMarker
|
|
909
1194
|
});
|
|
910
1195
|
},
|
|
911
1196
|
children: text.regenerate
|
|
912
|
-
}) : null
|
|
1197
|
+
}) : null]
|
|
913
1198
|
}),
|
|
914
1199
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
915
1200
|
className: "dsh-ui-field",
|
|
@@ -923,15 +1208,17 @@ function TitleSettings(props) {
|
|
|
923
1208
|
value: status ? promptFieldValue(prompt, savedPrompt) : "",
|
|
924
1209
|
maxLength: 4e3,
|
|
925
1210
|
rows: 5,
|
|
926
|
-
disabled: busy || !status,
|
|
1211
|
+
disabled: busy || loadingSettings || !status,
|
|
927
1212
|
onChange: (event) => {
|
|
1213
|
+
dirty.current = true;
|
|
928
1214
|
setPrompt(event.target.value);
|
|
929
1215
|
setNote("");
|
|
930
1216
|
},
|
|
931
1217
|
onBlur: () => {
|
|
932
|
-
if (!status) return;
|
|
1218
|
+
if (!status || busyRef.current || reading.current || external.current) return;
|
|
933
1219
|
const next = promptToSave(prompt, savedPrompt);
|
|
934
1220
|
if (next === void 0) {
|
|
1221
|
+
dirty.current = false;
|
|
935
1222
|
setPrompt(void 0);
|
|
936
1223
|
return;
|
|
937
1224
|
}
|
|
@@ -943,14 +1230,15 @@ function TitleSettings(props) {
|
|
|
943
1230
|
prompt: next,
|
|
944
1231
|
expectedRevision: status.settings.revision,
|
|
945
1232
|
isCurrent: () => work.isRequest(token),
|
|
946
|
-
savedMessage:
|
|
947
|
-
failedMessage:
|
|
948
|
-
onBusy:
|
|
1233
|
+
savedMessage: messages.saved,
|
|
1234
|
+
failedMessage: messages.failed,
|
|
1235
|
+
onBusy: changeBusy,
|
|
949
1236
|
onSettings: (settings) => {
|
|
950
1237
|
setStatus((current) => current ? {
|
|
951
1238
|
...current,
|
|
952
1239
|
settings
|
|
953
1240
|
} : current);
|
|
1241
|
+
dirty.current = false;
|
|
954
1242
|
setPrompt(void 0);
|
|
955
1243
|
},
|
|
956
1244
|
onNote: setNote,
|
|
@@ -972,10 +1260,10 @@ function TitleSettings(props) {
|
|
|
972
1260
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
973
1261
|
className: "dsh-ui-select",
|
|
974
1262
|
value: modelKey,
|
|
975
|
-
disabled: busy || !status,
|
|
1263
|
+
disabled: busy || loadingSettings || !status,
|
|
976
1264
|
onChange: (event) => {
|
|
977
1265
|
const value = event.target.value;
|
|
978
|
-
const choice =
|
|
1266
|
+
const choice = selectableChoices.find((item) => `${item.provider}\u001f${item.model}` === value);
|
|
979
1267
|
saveSettings({ model: choice ? {
|
|
980
1268
|
provider: choice.provider,
|
|
981
1269
|
model: choice.model
|
|
@@ -987,7 +1275,7 @@ function TitleSettings(props) {
|
|
|
987
1275
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
988
1276
|
value: "",
|
|
989
1277
|
children: text.modelDefault
|
|
990
|
-
}),
|
|
1278
|
+
}), selectableChoices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
991
1279
|
value: `${choice.provider}\u001f${choice.model}`,
|
|
992
1280
|
children: choice.label
|
|
993
1281
|
}, `${choice.provider}\u001f${choice.model}`))]
|
|
@@ -1001,7 +1289,7 @@ function TitleSettings(props) {
|
|
|
1001
1289
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1002
1290
|
className: "dsh-ui-select",
|
|
1003
1291
|
value: status?.settings.cadence ?? "all-prompts",
|
|
1004
|
-
disabled: busy || !status,
|
|
1292
|
+
disabled: busy || loadingSettings || !status,
|
|
1005
1293
|
onChange: (event) => {
|
|
1006
1294
|
saveSettings({ cadence: event.target.value === "first-prompt" ? "first-prompt" : "all-prompts" });
|
|
1007
1295
|
},
|
|
@@ -1039,6 +1327,8 @@ const styles = `${officialUiCss("ct-root")}
|
|
|
1039
1327
|
`;
|
|
1040
1328
|
function apply(ctx) {
|
|
1041
1329
|
const client = ctx;
|
|
1330
|
+
const settingsSubscriber = createModelSettingsSubscriber(ctx, "@klarkxy/dsh-current-title");
|
|
1331
|
+
const subscribeSettings = (change, error) => settingsSubscriber.subscribe(change, error);
|
|
1042
1332
|
const marker = createTitleClientMarker();
|
|
1043
1333
|
ctx.provide(TITLE_CLIENT_SERVICE, marker);
|
|
1044
1334
|
ctx.effect(() => {
|
|
@@ -1054,7 +1344,8 @@ function apply(ctx) {
|
|
|
1054
1344
|
key: "@klarkxy/dsh-current-title"
|
|
1055
1345
|
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TitleSettings, {
|
|
1056
1346
|
client,
|
|
1057
|
-
marker
|
|
1347
|
+
marker,
|
|
1348
|
+
subscribeSettings
|
|
1058
1349
|
}))), "current-title.settings");
|
|
1059
1350
|
ctx.effect(() => {
|
|
1060
1351
|
let live = true;
|