@klarkxy/dsh-git-commit 0.1.6 → 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 +34 -4
- package/docs/README.zh-CN.md +28 -2
- package/lib/client.inner.cjs +395 -79
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +396 -241
- package/lib/contracts.d.ts +45 -12
- package/lib/contracts.js +1 -27
- package/lib/contracts.js.map +1 -1
- package/lib/index.d.ts +14 -5
- package/lib/index.js +294 -82
- package/lib/index.js.map +1 -1
- package/package.json +16 -9
package/lib/client.inner.cjs
CHANGED
|
@@ -3,21 +3,14 @@ let react = require("react");
|
|
|
3
3
|
let react_dom = require("react-dom");
|
|
4
4
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
-
//#region ../dsh-
|
|
7
|
-
/**
|
|
8
|
-
* Shared plugin-page model menu.
|
|
9
|
-
*
|
|
10
|
-
* A feature plugin's own settings row exposes a model select. An empty
|
|
11
|
-
* selection means the host default chat model for that feature's own call.
|
|
12
|
-
*
|
|
13
|
-
* Browser-safe and React-optional: pure helpers live here so hosts and clients
|
|
14
|
-
* share one catalog contract without bundling React into the host build.
|
|
15
|
-
*/
|
|
6
|
+
//#region ../dsh-model-route/src/route.ts
|
|
16
7
|
const KEY_SEPARATOR = "";
|
|
17
|
-
|
|
8
|
+
/** Stable key for one route, safe to use as a select-option value. */
|
|
9
|
+
function modelRouteKey(provider, model) {
|
|
18
10
|
return provider && model ? `${provider}${KEY_SEPARATOR}${model}` : "";
|
|
19
11
|
}
|
|
20
|
-
|
|
12
|
+
/** Inverse of modelRouteKey; undefined for malformed keys. */
|
|
13
|
+
function parseModelRouteKey(value) {
|
|
21
14
|
const index = value.indexOf(KEY_SEPARATOR);
|
|
22
15
|
if (index <= 0) return void 0;
|
|
23
16
|
const provider = value.slice(0, index);
|
|
@@ -28,6 +21,8 @@ function parseModelMenuChoiceKey(value) {
|
|
|
28
21
|
model
|
|
29
22
|
};
|
|
30
23
|
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region ../dsh-model-route/src/catalog.ts
|
|
31
26
|
function asRecord(value) {
|
|
32
27
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
33
28
|
}
|
|
@@ -386,7 +381,7 @@ ${scope} {
|
|
|
386
381
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
387
382
|
border-radius: var(--dsw-radius-md);
|
|
388
383
|
background: var(--dsw-alias-bg-layer-1);
|
|
389
|
-
font-family: var(--ds-font-family-code
|
|
384
|
+
font-family: var(--ds-font-family-code);
|
|
390
385
|
font-size: 12px;
|
|
391
386
|
line-height: 20px;
|
|
392
387
|
color: var(--dsw-alias-label-secondary);
|
|
@@ -462,6 +457,162 @@ ${scope} {
|
|
|
462
457
|
`.replace(/^& (\.dsh-ui-[a-z0-9-]+)(?![+>~])/gm, (_match, className) => `&${className}, & ${className}`);
|
|
463
458
|
}
|
|
464
459
|
//#endregion
|
|
460
|
+
//#region ../dsh-plugin-kit/src/contracts.ts
|
|
461
|
+
/**
|
|
462
|
+
* Remote contribution for model-settings change notifications.
|
|
463
|
+
* Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
|
|
464
|
+
* subscribers degrade silently when that package is not installed.
|
|
465
|
+
* The stream carries invalidations only — `ready` is never a settings snapshot.
|
|
466
|
+
*/
|
|
467
|
+
const MODEL_SETTINGS_CHANGES_REMOTE = {
|
|
468
|
+
package: "@klarkxy/dsh-model-hub",
|
|
469
|
+
descriptors: [{
|
|
470
|
+
id: "modelSettingsChanges/watch",
|
|
471
|
+
service: "modelSettingsChangesController",
|
|
472
|
+
namespace: "modelSettingsChanges",
|
|
473
|
+
method: "watch",
|
|
474
|
+
mode: "stream",
|
|
475
|
+
invocation: { kind: "direct" },
|
|
476
|
+
parameters: [],
|
|
477
|
+
cancellation: { parameter: "signal" }
|
|
478
|
+
}]
|
|
479
|
+
};
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region ../dsh-plugin-kit/src/model-settings-client.ts
|
|
482
|
+
/** This stream carries invalidations only; ready is never a settings snapshot. */
|
|
483
|
+
function parseModelSettingsChangeFrame(value) {
|
|
484
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid model settings change frame");
|
|
485
|
+
const frame = value;
|
|
486
|
+
if (frame.kind === "ready" && Object.keys(frame).length === 1) return { kind: "ready" };
|
|
487
|
+
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 {
|
|
488
|
+
kind: "changed",
|
|
489
|
+
ns: frame.ns,
|
|
490
|
+
revision: frame.revision
|
|
491
|
+
};
|
|
492
|
+
throw new Error("Invalid model settings change frame");
|
|
493
|
+
}
|
|
494
|
+
MODEL_SETTINGS_CHANGES_REMOTE.package, MODEL_SETTINGS_CHANGES_REMOTE.descriptors.map((descriptor) => ({
|
|
495
|
+
...descriptor,
|
|
496
|
+
result: {
|
|
497
|
+
mode: "strict",
|
|
498
|
+
typeSymbol: "ModelSettings.ChangeFrame",
|
|
499
|
+
create: () => ({ parse: parseModelSettingsChangeFrame })
|
|
500
|
+
}
|
|
501
|
+
}));
|
|
502
|
+
/** Native RemoteStream owns carrier reconnects; no timers or browser transport.
|
|
503
|
+
* A factory shares one logical stream while at least one subscriber remains. */
|
|
504
|
+
function createModelSettingsSubscriber(ctx, namespace) {
|
|
505
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
506
|
+
let injection;
|
|
507
|
+
let current;
|
|
508
|
+
let lifecycle = 0;
|
|
509
|
+
let failureMessage;
|
|
510
|
+
const report = (message) => {
|
|
511
|
+
failureMessage = message;
|
|
512
|
+
for (const listener of [...listeners]) try {
|
|
513
|
+
listener.error?.(message);
|
|
514
|
+
} catch {}
|
|
515
|
+
};
|
|
516
|
+
const invalidate = () => {
|
|
517
|
+
for (const listener of [...listeners]) {
|
|
518
|
+
if (!listeners.has(listener)) continue;
|
|
519
|
+
try {
|
|
520
|
+
listener.change();
|
|
521
|
+
} catch (error) {
|
|
522
|
+
try {
|
|
523
|
+
listener.error?.(error instanceof Error ? error.message : String(error));
|
|
524
|
+
} catch {}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
const start = () => {
|
|
529
|
+
const life = ++lifecycle;
|
|
530
|
+
injection = ctx.inject(["remote", "remote.modelSettingsChanges"], (injected) => {
|
|
531
|
+
if (life !== lifecycle || !listeners.size) return;
|
|
532
|
+
const scope = injected;
|
|
533
|
+
const stream = scope.remote.$stream({
|
|
534
|
+
name: "Model settings sync",
|
|
535
|
+
open: (signal) => scope.remote.modelSettingsChanges.watch(signal),
|
|
536
|
+
ended: (accepted) => /* @__PURE__ */ new Error(`Model settings sync stream ended ${accepted ? "after" : "before"} ready; refresh or reopen the page`)
|
|
537
|
+
});
|
|
538
|
+
const session = {
|
|
539
|
+
stream,
|
|
540
|
+
active: true
|
|
541
|
+
};
|
|
542
|
+
current = session;
|
|
543
|
+
scope.effect(() => () => {
|
|
544
|
+
session.active = false;
|
|
545
|
+
if (current === session) {
|
|
546
|
+
current = void 0;
|
|
547
|
+
if (life === lifecycle && listeners.size) report("Model settings sync service unavailable");
|
|
548
|
+
}
|
|
549
|
+
return stream.dispose();
|
|
550
|
+
});
|
|
551
|
+
(async () => {
|
|
552
|
+
let generation;
|
|
553
|
+
let ready = false;
|
|
554
|
+
try {
|
|
555
|
+
for await (const item of stream) {
|
|
556
|
+
if (!session.active || life !== lifecycle || !listeners.size) break;
|
|
557
|
+
if (item.signal.aborted) continue;
|
|
558
|
+
if (generation !== item.generation) {
|
|
559
|
+
generation = item.generation;
|
|
560
|
+
ready = false;
|
|
561
|
+
}
|
|
562
|
+
const frame = parseModelSettingsChangeFrame(item.value);
|
|
563
|
+
if (frame.kind === "ready") {
|
|
564
|
+
ready = true;
|
|
565
|
+
failureMessage = void 0;
|
|
566
|
+
item.accept();
|
|
567
|
+
invalidate();
|
|
568
|
+
} else {
|
|
569
|
+
if (!ready) throw new Error("Model settings sync change arrived before ready");
|
|
570
|
+
if (namespace === "*" || frame.ns === namespace) invalidate();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (session.active && life === lifecycle) report(error instanceof Error ? error.message : String(error));
|
|
575
|
+
} finally {
|
|
576
|
+
if (current === session) current = void 0;
|
|
577
|
+
session.active = false;
|
|
578
|
+
await stream.dispose().catch(() => {});
|
|
579
|
+
}
|
|
580
|
+
})();
|
|
581
|
+
});
|
|
582
|
+
queueMicrotask(() => {
|
|
583
|
+
if (life === lifecycle && listeners.size && !current) report("Model settings sync service unavailable");
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
return Object.freeze({ subscribe(onChange, onError) {
|
|
587
|
+
const listener = {
|
|
588
|
+
change: onChange,
|
|
589
|
+
error: onError
|
|
590
|
+
};
|
|
591
|
+
listeners.add(listener);
|
|
592
|
+
if (!injection) start();
|
|
593
|
+
else if (failureMessage) queueMicrotask(() => {
|
|
594
|
+
if (listeners.has(listener) && failureMessage) try {
|
|
595
|
+
listener.error?.(failureMessage);
|
|
596
|
+
} catch {}
|
|
597
|
+
});
|
|
598
|
+
return () => {
|
|
599
|
+
if (!listeners.delete(listener) || listeners.size) return;
|
|
600
|
+
lifecycle++;
|
|
601
|
+
if (current) {
|
|
602
|
+
current.active = false;
|
|
603
|
+
current.stream.dispose().catch(() => {});
|
|
604
|
+
}
|
|
605
|
+
current = void 0;
|
|
606
|
+
failureMessage = void 0;
|
|
607
|
+
const previous = injection;
|
|
608
|
+
injection = void 0;
|
|
609
|
+
try {
|
|
610
|
+
Promise.resolve(previous?.dispose()).catch(() => {});
|
|
611
|
+
} catch {}
|
|
612
|
+
};
|
|
613
|
+
} });
|
|
614
|
+
}
|
|
615
|
+
//#endregion
|
|
465
616
|
//#region src/contracts.ts
|
|
466
617
|
const PLUGIN_NAME = "@klarkxy/dsh-git-commit";
|
|
467
618
|
const RPC_CHANNEL = "/dsh-git-commit";
|
|
@@ -492,6 +643,12 @@ const zh = {
|
|
|
492
643
|
loading: "正在读取仓库状态…",
|
|
493
644
|
running: "正在整理修改并分次提交…",
|
|
494
645
|
done: "已创建提交",
|
|
646
|
+
partial: "本次提交未全部完成;以下提交已保留",
|
|
647
|
+
stopped: "本次操作未确认完成",
|
|
648
|
+
uncertain: "以下提交组的结果不确定。请检查 Git 日志和状态,确认后再操作。",
|
|
649
|
+
hashUnavailable: "提交已创建,哈希未读取",
|
|
650
|
+
remaining: "尚未提交:{n} 组",
|
|
651
|
+
cancelled: "提交操作已取消",
|
|
495
652
|
fallback: "模型不可用,已合并为单个提交",
|
|
496
653
|
noWorkspace: "工作区没有修改",
|
|
497
654
|
unavailable: "不可用",
|
|
@@ -502,6 +659,12 @@ const zh = {
|
|
|
502
659
|
sessionRunning: "会话正在运行,提交已暂停",
|
|
503
660
|
refresh: "刷新",
|
|
504
661
|
failed: "操作失败",
|
|
662
|
+
disabled: "插件已停用",
|
|
663
|
+
busy: "已有提交操作正在进行",
|
|
664
|
+
stale: "设置已更新,请刷新后重试",
|
|
665
|
+
workspaceChanged: "工作区或分支已变更,请刷新后重试提交",
|
|
666
|
+
settingsUnavailable: "无法读取提交设置,请重新加载插件后重试",
|
|
667
|
+
invalidRequest: "提交请求无效,请刷新后重试",
|
|
505
668
|
planUnavailable: "模型没有给出可用的提交方案,已取消本次提交,工作区没有被改动。换一个模型重试,或在插件设置里开启兜底提交。",
|
|
506
669
|
close: "关闭"
|
|
507
670
|
};
|
|
@@ -520,6 +683,12 @@ const en = {
|
|
|
520
683
|
loading: "Reading repository status…",
|
|
521
684
|
running: "Organizing changes into commits…",
|
|
522
685
|
done: "Commits created",
|
|
686
|
+
partial: "This run stopped before completion; these commits were kept",
|
|
687
|
+
stopped: "This run did not finish with a confirmed result",
|
|
688
|
+
uncertain: "The outcome of these groups is uncertain. Check Git log and status before taking further action.",
|
|
689
|
+
hashUnavailable: "Commit created; hash unavailable",
|
|
690
|
+
remaining: "Groups not committed: {n}",
|
|
691
|
+
cancelled: "Commit run cancelled",
|
|
523
692
|
fallback: "Model unavailable; used a single commit",
|
|
524
693
|
noWorkspace: "No workspace changes",
|
|
525
694
|
unavailable: "Unavailable",
|
|
@@ -530,23 +699,31 @@ const en = {
|
|
|
530
699
|
sessionRunning: "Session is running; commits paused",
|
|
531
700
|
refresh: "Refresh",
|
|
532
701
|
failed: "Operation failed",
|
|
702
|
+
disabled: "Plugin disabled",
|
|
703
|
+
busy: "A commit run is already in progress",
|
|
704
|
+
stale: "Settings changed; refresh and retry",
|
|
705
|
+
workspaceChanged: "The workspace or branch changed; refresh and retry the commit",
|
|
706
|
+
settingsUnavailable: "Could not read commit settings; reload the plugin and retry",
|
|
707
|
+
invalidRequest: "Invalid commit request; refresh and retry",
|
|
533
708
|
planUnavailable: "The model returned no usable commit plan, so the run was cancelled and your workspace is untouched. Try another model, or turn on the fallback in plugin settings.",
|
|
534
709
|
close: "Close"
|
|
535
710
|
};
|
|
536
711
|
/** A failed RPC keeps its code so the panel can explain a rejection in its own words. */
|
|
537
712
|
var RpcError = class extends Error {
|
|
538
713
|
code;
|
|
539
|
-
|
|
714
|
+
partial;
|
|
715
|
+
constructor(code, message, partial) {
|
|
540
716
|
super(message);
|
|
541
717
|
this.name = "RpcError";
|
|
542
718
|
this.code = code;
|
|
719
|
+
this.partial = partial;
|
|
543
720
|
}
|
|
544
721
|
};
|
|
545
|
-
async function rpc(call, endpoint, payload = {}, fallback = "
|
|
722
|
+
async function rpc(call, endpoint, payload = {}, fallback = "") {
|
|
546
723
|
const result = await call(endpoint, payload);
|
|
547
724
|
if (!result || result.ok !== true) {
|
|
548
725
|
const failed = result && "error" in result ? result.error : void 0;
|
|
549
|
-
throw new RpcError(failed?.code ?? "unknown", failed?.message ? failed.message : fallback);
|
|
726
|
+
throw new RpcError(failed?.code ?? "unknown", failed?.message ? failed.message : fallback, failed && "partial" in failed ? failed.partial : void 0);
|
|
550
727
|
}
|
|
551
728
|
return result.value;
|
|
552
729
|
}
|
|
@@ -561,7 +738,25 @@ function reasonText(t, status) {
|
|
|
561
738
|
}
|
|
562
739
|
/** A refusal the plugin can name gets its own sentence; the rest stay verbatim. */
|
|
563
740
|
function errorText(t, error) {
|
|
564
|
-
|
|
741
|
+
const key = {
|
|
742
|
+
"plan-unavailable": "planUnavailable",
|
|
743
|
+
"no-session": "reasonNoSession",
|
|
744
|
+
"no-cwd": "reasonNoCwd",
|
|
745
|
+
"no-git": "reasonNoGit",
|
|
746
|
+
"not-a-repo": "reasonNoRepo",
|
|
747
|
+
"no-changes": "noWorkspace",
|
|
748
|
+
"session-running": "sessionRunning",
|
|
749
|
+
"session-changed": "workspaceChanged",
|
|
750
|
+
"workspace-changed": "workspaceChanged",
|
|
751
|
+
busy: "busy",
|
|
752
|
+
stale: "stale",
|
|
753
|
+
disposed: "disabled",
|
|
754
|
+
cancelled: "cancelled",
|
|
755
|
+
"settings-unavailable": "settingsUnavailable",
|
|
756
|
+
"bad-request": "invalidRequest",
|
|
757
|
+
"not-found": "invalidRequest"
|
|
758
|
+
}[error.code];
|
|
759
|
+
return key ? t(key) : error.message || t("failed");
|
|
565
760
|
}
|
|
566
761
|
function panelError(reason) {
|
|
567
762
|
return reason instanceof RpcError ? {
|
|
@@ -660,7 +855,7 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
660
855
|
setLoading(true);
|
|
661
856
|
setError(null);
|
|
662
857
|
try {
|
|
663
|
-
const next = await rpc(call, "status", { sessionId }
|
|
858
|
+
const next = await rpc(call, "status", { sessionId });
|
|
664
859
|
if (generationRef.current !== generation) return;
|
|
665
860
|
setStatus(next);
|
|
666
861
|
} catch (reason) {
|
|
@@ -670,11 +865,7 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
670
865
|
} finally {
|
|
671
866
|
if (generationRef.current === generation) setLoading(false);
|
|
672
867
|
}
|
|
673
|
-
}, [
|
|
674
|
-
call,
|
|
675
|
-
sessionId,
|
|
676
|
-
t
|
|
677
|
-
]);
|
|
868
|
+
}, [call, sessionId]);
|
|
678
869
|
(0, react.useEffect)(() => {
|
|
679
870
|
if (!open) return;
|
|
680
871
|
loadStatus();
|
|
@@ -697,23 +888,23 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
697
888
|
setResult(null);
|
|
698
889
|
setError(null);
|
|
699
890
|
try {
|
|
700
|
-
const value = await rpc(call, "commit", { sessionId }
|
|
891
|
+
const value = await rpc(call, "commit", { sessionId });
|
|
701
892
|
if (generationRef.current !== generation) return;
|
|
702
893
|
setResult(value);
|
|
703
|
-
const next = await rpc(call, "status", { sessionId }).catch(() => void 0);
|
|
704
|
-
if (generationRef.current !== generation) return;
|
|
705
|
-
if (next) setStatus(next);
|
|
706
894
|
} catch (reason) {
|
|
707
895
|
if (generationRef.current !== generation) return;
|
|
896
|
+
if (reason instanceof RpcError && reason.partial) setResult(reason.partial);
|
|
708
897
|
setError(panelError(reason));
|
|
709
898
|
} finally {
|
|
710
|
-
if (generationRef.current === generation)
|
|
899
|
+
if (generationRef.current === generation) {
|
|
900
|
+
const next = await rpc(call, "status", { sessionId }).catch(() => void 0);
|
|
901
|
+
if (generationRef.current === generation) {
|
|
902
|
+
setStatus(next);
|
|
903
|
+
setRunning(false);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
711
906
|
}
|
|
712
|
-
}, [
|
|
713
|
-
call,
|
|
714
|
-
sessionId,
|
|
715
|
-
t
|
|
716
|
-
]);
|
|
907
|
+
}, [call, sessionId]);
|
|
717
908
|
const available = status?.available === true;
|
|
718
909
|
const workspaceCount = status?.workspace.files ?? 0;
|
|
719
910
|
const canCommit = available && !running && !sessionRunning && workspaceCount > 0;
|
|
@@ -861,23 +1052,48 @@ function GitCommitAction({ sessionId, useSession, call, t }) {
|
|
|
861
1052
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
862
1053
|
className: "dsh-ui-hint dsh-ui-wrap",
|
|
863
1054
|
children: [
|
|
864
|
-
t("done"),
|
|
1055
|
+
t("failure" in result ? result.commits.length ? "partial" : "stopped" : "done"),
|
|
865
1056
|
" · ",
|
|
866
1057
|
result.branch
|
|
867
1058
|
]
|
|
868
1059
|
}),
|
|
869
1060
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
870
1061
|
className: "gcm-commits",
|
|
871
|
-
children: result.commits.map((commit) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
872
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: commit.hash }),
|
|
1062
|
+
children: result.commits.map((commit, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
1063
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: commit.hash || t("hashUnavailable") }),
|
|
873
1064
|
" ",
|
|
874
|
-
commit.message
|
|
875
|
-
|
|
1065
|
+
commit.message,
|
|
1066
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1067
|
+
className: "dsh-ui-hint",
|
|
1068
|
+
children: commit.files.join(", ")
|
|
1069
|
+
})
|
|
1070
|
+
] }, `${index}:${commit.hash}`))
|
|
876
1071
|
}),
|
|
877
1072
|
result.fallback && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
878
1073
|
className: "dsh-ui-hint",
|
|
879
1074
|
children: t("fallback")
|
|
880
|
-
})
|
|
1075
|
+
}),
|
|
1076
|
+
"uncertain" in result && result.uncertain?.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1077
|
+
className: "dsh-ui-hint",
|
|
1078
|
+
role: "status",
|
|
1079
|
+
children: t("uncertain")
|
|
1080
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1081
|
+
className: "gcm-commits",
|
|
1082
|
+
children: result.uncertain.map((group, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [group.message, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1083
|
+
className: "dsh-ui-hint",
|
|
1084
|
+
children: group.files.join(", ")
|
|
1085
|
+
})] }, index))
|
|
1086
|
+
})] }) : null,
|
|
1087
|
+
"remaining" in result && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1088
|
+
className: "dsh-ui-hint",
|
|
1089
|
+
children: t("remaining").replace("{n}", String(result.remaining.length))
|
|
1090
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1091
|
+
className: "gcm-commits",
|
|
1092
|
+
children: result.remaining.map((group, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [group.message, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1093
|
+
className: "dsh-ui-hint",
|
|
1094
|
+
children: group.files.join(", ")
|
|
1095
|
+
})] }, index))
|
|
1096
|
+
})] })
|
|
881
1097
|
]
|
|
882
1098
|
})
|
|
883
1099
|
]
|
|
@@ -904,10 +1120,10 @@ const clientCss = `${officialUiCss("gcm-root")}
|
|
|
904
1120
|
.gcm-settings { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
|
|
905
1121
|
.gcm-settings-status { display: flex; flex-direction: column; gap: 4px; }
|
|
906
1122
|
`;
|
|
907
|
-
/** Host UI language, following live switches
|
|
1123
|
+
/** Host UI language, following live switches with the host's English fallback. */
|
|
908
1124
|
function useHostLocale(client) {
|
|
909
1125
|
const subscribe = (0, react.useCallback)((listener) => client.locale.subscribe?.(listener) ?? (() => {}), [client]);
|
|
910
|
-
const snapshot = (0, react.useCallback)(() => client.locale.getSnapshot?.().active ?? "
|
|
1126
|
+
const snapshot = (0, react.useCallback)(() => client.locale.getSnapshot?.().active ?? "en", [client]);
|
|
911
1127
|
return (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot);
|
|
912
1128
|
}
|
|
913
1129
|
const settingsZh = {
|
|
@@ -922,7 +1138,9 @@ const settingsZh = {
|
|
|
922
1138
|
saved: "已保存。",
|
|
923
1139
|
stale: "设置已更新,请刷新后重试。",
|
|
924
1140
|
failed: "无法读取提交设置。",
|
|
925
|
-
retry: "重试"
|
|
1141
|
+
retry: "重试",
|
|
1142
|
+
refresh: "刷新设置",
|
|
1143
|
+
syncUnavailable: "自动刷新不可用,请手动刷新设置。"
|
|
926
1144
|
};
|
|
927
1145
|
const settingsEn = {
|
|
928
1146
|
pageTitle: "Git Commit",
|
|
@@ -936,7 +1154,9 @@ const settingsEn = {
|
|
|
936
1154
|
saved: "Saved.",
|
|
937
1155
|
stale: "Settings changed; refresh and retry.",
|
|
938
1156
|
failed: "Could not load commit settings.",
|
|
939
|
-
retry: "Retry"
|
|
1157
|
+
retry: "Retry",
|
|
1158
|
+
refresh: "Refresh settings",
|
|
1159
|
+
syncUnavailable: "Automatic refresh unavailable; refresh settings manually."
|
|
940
1160
|
};
|
|
941
1161
|
function settingsCopy(locale) {
|
|
942
1162
|
return String(locale).startsWith("zh") ? settingsZh : settingsEn;
|
|
@@ -948,23 +1168,77 @@ function GitCommitSettingsPanel(props) {
|
|
|
948
1168
|
const [settings, setSettings] = (0, react.useState)(void 0);
|
|
949
1169
|
const [choices, setChoices] = (0, react.useState)([]);
|
|
950
1170
|
const [error, setError] = (0, react.useState)("");
|
|
951
|
-
const [note, setNote] = (0, react.useState)(
|
|
1171
|
+
const [note, setNote] = (0, react.useState)(false);
|
|
952
1172
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1173
|
+
const [syncError, setSyncError] = (0, react.useState)("");
|
|
1174
|
+
const operation = (0, react.useRef)({
|
|
1175
|
+
epoch: 0,
|
|
1176
|
+
active: false,
|
|
1177
|
+
reading: false,
|
|
1178
|
+
writing: false,
|
|
1179
|
+
pending: false
|
|
1180
|
+
});
|
|
1181
|
+
const refreshRef = (0, react.useRef)(() => {});
|
|
953
1182
|
const call = (0, react.useCallback)((endpoint, payload = {}) => props.client.connection.rpc.call(RPC_CHANNEL, endpoint, payload), [props.client]);
|
|
954
|
-
|
|
955
|
-
|
|
1183
|
+
const refresh = () => {
|
|
1184
|
+
const state = operation.current;
|
|
1185
|
+
if (!state.active) return;
|
|
1186
|
+
if (state.reading || state.writing) {
|
|
1187
|
+
state.pending = true;
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
state.pending = false;
|
|
1191
|
+
state.reading = true;
|
|
1192
|
+
setBusy(true);
|
|
1193
|
+
const epoch = state.epoch;
|
|
956
1194
|
rpc(call, "settings", {}, text.failed).then((next) => {
|
|
957
|
-
if (
|
|
1195
|
+
if (state.active && state.epoch === epoch && !state.pending) {
|
|
958
1196
|
setSettings(next);
|
|
959
1197
|
setError("");
|
|
960
1198
|
}
|
|
961
1199
|
}).catch(() => {
|
|
962
|
-
if (
|
|
1200
|
+
if (state.active && state.epoch === epoch && !state.pending) setError("failed");
|
|
1201
|
+
}).finally(() => {
|
|
1202
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
1203
|
+
state.reading = false;
|
|
1204
|
+
setBusy(false);
|
|
1205
|
+
if (state.pending) refreshRef.current();
|
|
1206
|
+
});
|
|
1207
|
+
};
|
|
1208
|
+
refreshRef.current = refresh;
|
|
1209
|
+
(0, react.useEffect)(() => {
|
|
1210
|
+
const state = operation.current;
|
|
1211
|
+
state.epoch++;
|
|
1212
|
+
state.active = true;
|
|
1213
|
+
state.reading = false;
|
|
1214
|
+
state.writing = false;
|
|
1215
|
+
state.pending = false;
|
|
1216
|
+
setBusy(false);
|
|
1217
|
+
setSettings(void 0);
|
|
1218
|
+
setError("");
|
|
1219
|
+
setNote(false);
|
|
1220
|
+
refreshRef.current();
|
|
1221
|
+
return () => {
|
|
1222
|
+
state.active = false;
|
|
1223
|
+
state.epoch++;
|
|
1224
|
+
};
|
|
1225
|
+
}, [call]);
|
|
1226
|
+
(0, react.useEffect)(() => {
|
|
1227
|
+
setSyncError("");
|
|
1228
|
+
let live = true;
|
|
1229
|
+
const stop = props.subscribeSettings?.(() => {
|
|
1230
|
+
if (live) {
|
|
1231
|
+
setSyncError("");
|
|
1232
|
+
refreshRef.current();
|
|
1233
|
+
}
|
|
1234
|
+
}, (message) => {
|
|
1235
|
+
if (live) setSyncError(message);
|
|
963
1236
|
});
|
|
964
1237
|
return () => {
|
|
965
1238
|
live = false;
|
|
1239
|
+
stop?.();
|
|
966
1240
|
};
|
|
967
|
-
}, [
|
|
1241
|
+
}, [props.subscribeSettings, props.client]);
|
|
968
1242
|
const boundProvider = settings?.model.provider ?? "";
|
|
969
1243
|
const boundModel = settings?.model.model ?? "";
|
|
970
1244
|
(0, react.useEffect)(() => {
|
|
@@ -987,9 +1261,12 @@ function GitCommitSettingsPanel(props) {
|
|
|
987
1261
|
boundModel
|
|
988
1262
|
]);
|
|
989
1263
|
const save = (0, react.useCallback)((patch) => {
|
|
990
|
-
|
|
1264
|
+
const state = operation.current;
|
|
1265
|
+
if (!settings || !state.active || state.writing || state.reading) return;
|
|
1266
|
+
state.writing = true;
|
|
1267
|
+
const epoch = state.epoch;
|
|
991
1268
|
setBusy(true);
|
|
992
|
-
setNote(
|
|
1269
|
+
setNote(false);
|
|
993
1270
|
setError("");
|
|
994
1271
|
rpc(call, "settings.update", {
|
|
995
1272
|
settings: {
|
|
@@ -998,12 +1275,17 @@ function GitCommitSettingsPanel(props) {
|
|
|
998
1275
|
},
|
|
999
1276
|
expectedRevision: settings.revision
|
|
1000
1277
|
}, text.stale).then((next) => {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1278
|
+
if (state.active && state.epoch === epoch) {
|
|
1279
|
+
setSettings(next);
|
|
1280
|
+
setNote(true);
|
|
1281
|
+
}
|
|
1004
1282
|
}).catch(() => {
|
|
1005
|
-
setError(
|
|
1283
|
+
if (state.active && state.epoch === epoch) setError("stale");
|
|
1284
|
+
}).finally(() => {
|
|
1285
|
+
if (!state.active || state.epoch !== epoch) return;
|
|
1286
|
+
state.writing = false;
|
|
1006
1287
|
setBusy(false);
|
|
1288
|
+
if (state.pending) refreshRef.current();
|
|
1007
1289
|
});
|
|
1008
1290
|
}, [
|
|
1009
1291
|
call,
|
|
@@ -1011,17 +1293,31 @@ function GitCommitSettingsPanel(props) {
|
|
|
1011
1293
|
text.saved,
|
|
1012
1294
|
text.stale
|
|
1013
1295
|
]);
|
|
1014
|
-
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1296
|
+
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1015
1297
|
className: "gcm-root gcm-settings",
|
|
1016
|
-
children:
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1298
|
+
children: [
|
|
1299
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1300
|
+
role: "alert",
|
|
1301
|
+
className: "dsh-ui-error",
|
|
1302
|
+
children: text[error]
|
|
1303
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1304
|
+
role: "status",
|
|
1305
|
+
className: "dsh-ui-hint",
|
|
1306
|
+
children: text.loading
|
|
1307
|
+
}),
|
|
1308
|
+
syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1309
|
+
role: "status",
|
|
1310
|
+
className: "dsh-ui-hint",
|
|
1311
|
+
children: text.syncUnavailable
|
|
1312
|
+
}),
|
|
1313
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1314
|
+
variant: "ghost",
|
|
1315
|
+
size: "sm",
|
|
1316
|
+
disabled: busy,
|
|
1317
|
+
onClick: () => refreshRef.current(),
|
|
1318
|
+
children: text.retry
|
|
1319
|
+
})
|
|
1320
|
+
]
|
|
1025
1321
|
});
|
|
1026
1322
|
const current = settings.model;
|
|
1027
1323
|
const selected = choices.find((item) => item.provider === current.provider && item.model === current.model);
|
|
@@ -1032,15 +1328,30 @@ function GitCommitSettingsPanel(props) {
|
|
|
1032
1328
|
children: [
|
|
1033
1329
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1034
1330
|
className: "gcm-settings-status",
|
|
1035
|
-
children: [
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1331
|
+
children: [
|
|
1332
|
+
syncError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1333
|
+
role: "status",
|
|
1334
|
+
className: "dsh-ui-hint",
|
|
1335
|
+
children: text.syncUnavailable
|
|
1336
|
+
}),
|
|
1337
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1338
|
+
variant: "ghost",
|
|
1339
|
+
size: "sm",
|
|
1340
|
+
disabled: busy,
|
|
1341
|
+
onClick: () => refreshRef.current(),
|
|
1342
|
+
children: text.refresh
|
|
1343
|
+
}),
|
|
1344
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1345
|
+
role: "alert",
|
|
1346
|
+
className: "dsh-ui-error",
|
|
1347
|
+
children: text[error]
|
|
1348
|
+
}),
|
|
1349
|
+
note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1350
|
+
role: "status",
|
|
1351
|
+
className: "dsh-ui-notice",
|
|
1352
|
+
children: text.saved
|
|
1353
|
+
})
|
|
1354
|
+
]
|
|
1044
1355
|
}),
|
|
1045
1356
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1046
1357
|
className: "dsh-ui-field",
|
|
@@ -1049,7 +1360,7 @@ function GitCommitSettingsPanel(props) {
|
|
|
1049
1360
|
children: text.model
|
|
1050
1361
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1051
1362
|
className: "dsh-ui-select",
|
|
1052
|
-
value:
|
|
1363
|
+
value: modelRouteKey(current.provider, current.model),
|
|
1053
1364
|
disabled: busy,
|
|
1054
1365
|
onChange: (event) => {
|
|
1055
1366
|
const key = event.target.value;
|
|
@@ -1060,16 +1371,16 @@ function GitCommitSettingsPanel(props) {
|
|
|
1060
1371
|
} });
|
|
1061
1372
|
return;
|
|
1062
1373
|
}
|
|
1063
|
-
const parsed =
|
|
1374
|
+
const parsed = parseModelRouteKey(key);
|
|
1064
1375
|
if (parsed) save({ model: parsed });
|
|
1065
1376
|
},
|
|
1066
1377
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1067
1378
|
value: "",
|
|
1068
1379
|
children: text.modelDefault
|
|
1069
1380
|
}), choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1070
|
-
value:
|
|
1381
|
+
value: modelRouteKey(choice.provider, choice.model),
|
|
1071
1382
|
children: choice.label
|
|
1072
|
-
},
|
|
1383
|
+
}, modelRouteKey(choice.provider, choice.model)))]
|
|
1073
1384
|
})]
|
|
1074
1385
|
}),
|
|
1075
1386
|
selected && efforts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -1124,6 +1435,8 @@ function GitCommitSettingsPanel(props) {
|
|
|
1124
1435
|
}
|
|
1125
1436
|
function apply(ctx) {
|
|
1126
1437
|
const client = ctx;
|
|
1438
|
+
const settingsSubscriber = createModelSettingsSubscriber(ctx, PLUGIN_NAME);
|
|
1439
|
+
const subscribeSettings = (change, error) => settingsSubscriber.subscribe(change, error);
|
|
1127
1440
|
ctx.provide(GIT_COMMIT_CLIENT_SERVICE, { active: true });
|
|
1128
1441
|
ctx.effect(() => client.locale.register(NS, {
|
|
1129
1442
|
zh: { ...zh },
|
|
@@ -1140,7 +1453,10 @@ function apply(ctx) {
|
|
|
1140
1453
|
ctx.effect(() => client.slots.inject("plugins.bundle.config", () => client.slots.register({
|
|
1141
1454
|
name: "plugins.bundle.config",
|
|
1142
1455
|
key: PLUGIN_NAME
|
|
1143
|
-
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GitCommitSettingsPanel, {
|
|
1456
|
+
}, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GitCommitSettingsPanel, {
|
|
1457
|
+
client,
|
|
1458
|
+
subscribeSettings
|
|
1459
|
+
}))), "git-commit.settings");
|
|
1144
1460
|
ctx.effect(() => client.slots.inject("conversation.session.header.actions", () => client.slots.register({
|
|
1145
1461
|
name: "conversation.session.header.actions",
|
|
1146
1462
|
id: "git-commit",
|