dsh-plugin-tool-management 0.1.1 → 0.1.3
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 +43 -13
- package/README_EN.md +43 -13
- package/cordis.patch.yml +18 -0
- package/lib/agents-md/service.js +240 -0
- package/lib/client.js +637 -30
- package/lib/history/projcache.js +335 -0
- package/lib/history/tombstone.js +32 -0
- package/lib/history/workspace.js +892 -0
- package/lib/imports/parsers.js +181 -0
- package/lib/index.js +651 -1
- package/package.json +18 -3
package/lib/client.js
CHANGED
|
@@ -32,16 +32,17 @@ window.__ModuleLoader__.load({
|
|
|
32
32
|
'.dsm-detail-title-row{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
|
|
33
33
|
'.dsm-note-user{color:var(--dsw-alias-label-tertiary)}' +
|
|
34
34
|
`
|
|
35
|
-
.dsm-section{box-sizing:border-box;display:flex;width:100%;max-width:820px;min-width:0;margin:0 auto;padding:2px 0 36px;container-type:inline-size;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary);font-family:inherit}.dsm-head{display:flex;flex-direction:column;align-items:stretch;gap:16px}.dsm-title-block{min-width:0}.dsm-title-row{display:flex;align-items:center;gap:8px 12px;min-width:0;flex-wrap:wrap}.dsm-feedback-links{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsm-title{margin:0;font-size:24px;line-height:32px;font-weight:600;letter-spacing:-.4px;white-space:nowrap}.dsm-feedback-link{display:inline-flex;min-height:28px;align-items:center;gap:5px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dsm-feedback-link:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsm-feedback-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsm-feedback-link svg{flex:none}.dsm-desc{margin:12px 0 0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}.dsm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;flex:none}
|
|
35
|
+
.dsm-tabs{display:flex;gap:4px;border-bottom:1px solid var(--dsw-alias-border-l1);margin-bottom:14px}.dsm-tab{padding:8px 14px;border:0;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.dsm-tab-active{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-success-primary)}.dsm-section{box-sizing:border-box;display:flex;width:100%;max-width:820px;min-width:0;margin:0 auto;padding:2px 0 36px;container-type:inline-size;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary);font-family:inherit}.dsm-head{display:flex;flex-direction:column;align-items:stretch;gap:16px}.dsm-title-block{min-width:0}.dsm-title-row{display:flex;align-items:center;gap:8px 12px;min-width:0;flex-wrap:wrap}.dsm-feedback-links{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.dsm-title{margin:0;font-size:24px;line-height:32px;font-weight:600;letter-spacing:-.4px;white-space:nowrap}.dsm-feedback-link{display:inline-flex;min-height:28px;align-items:center;gap:5px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:500;line-height:18px;text-decoration:none;white-space:nowrap}.dsm-feedback-link:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dsm-feedback-link:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dsm-feedback-link svg{flex:none}.dsm-desc{margin:12px 0 0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}.dsm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-left:0;flex:none}
|
|
36
36
|
.dsm-btn{box-sizing:border-box;display:inline-flex;min-height:34px;align-items:center;justify-content:center;padding:0 13px;border:1px solid transparent;border-radius:8px;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);font:inherit;font-size:13px;font-weight:580;white-space:nowrap;cursor:pointer}.dsm-btn:hover:not(:disabled){filter:brightness(1.08)}.dsm-btn:disabled{opacity:.48;cursor:default}.dsm-btn-secondary,.dsm-btn-quiet{border-color:var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsm-btn-quiet{min-height:28px;padding:0 9px;color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-btn-danger{border-color:var(--dsw-alias-state-error-primary);background:transparent;color:var(--dsw-alias-state-error-primary)}.dsm-btn:focus-visible,.dsm-control:focus-visible,.dsm-select-trigger:focus-visible,.dsm-source-head:focus-visible,.dsm-switch:focus-visible,.dsm-upload-link:focus-visible,.dsm-file-remove:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}
|
|
37
37
|
.dsm-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-stat{padding:12px 14px;border-right:1px solid var(--dsw-alias-border-l1);font-size:13px;color:var(--dsw-alias-label-secondary)}.dsm-stat:last-child{border-right:0}.dsm-stat strong{margin-right:5px;color:var(--dsw-alias-label-primary);font-size:17px;font-weight:680}.dsm-filters{display:flex;gap:9px}.dsm-search{flex:1}.dsm-source-filter{width:210px;flex:none}
|
|
38
38
|
.dsm-control,.dsm-select-trigger{box-sizing:border-box;width:100%;min-height:34px;padding:0 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}.dsm-control::placeholder{color:var(--dsw-alias-label-tertiary)}textarea.dsm-control{min-height:160px;padding-top:9px;resize:vertical;line-height:20px}.dsm-select{position:relative}.dsm-select-trigger{display:flex;align-items:center;justify-content:space-between;text-align:left;cursor:pointer}.dsm-select-menu{position:absolute;z-index:40;top:calc(100% + 5px);right:0;left:0;display:flex;max-height:260px;padding:5px;overflow:auto;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-3);box-shadow:var(--dsw-shadow-lv2)}.dsm-option{padding:8px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;text-align:left;cursor:pointer}.dsm-option:hover,.dsm-option[aria-selected=true]{background:var(--dsw-alias-interactive-bg-hover)}
|
|
39
|
-
.dsm-sources{display:flex;flex-direction:column;gap:9px}.dsm-source{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-source-head{box-sizing:border-box;display:flex;width:100%;min-height:48px;align-items:center;padding:0 13px}.dsm-source-head-main{display:flex;min-width:0;min-height:48px;flex:1;align-items:center;gap:10px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.dsm-source-head:hover,.dsm-row:hover,.dsm-trash-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-source-title{font-size:14px;font-weight:650}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px}.dsm-path{min-width:0;margin-left:auto;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.dsm-source-actions{display:flex;align-items:center;gap:9px;margin-left:8px}.dsm-source-body{border-top:1px solid var(--dsw-alias-border-l1)}.dsm-source-note{padding:9px 13px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
39
|
+
.dsm-sources{display:flex;flex-direction:column;gap:9px}.dsm-source{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.dsm-source-head{box-sizing:border-box;display:flex;width:100%;min-height:48px;align-items:center;padding:0 13px}.dsm-source-head-main{display:flex;min-width:0;min-height:48px;flex:1;align-items:center;gap:10px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.dsm-source-head:hover,.dsm-row:hover,.dsm-trash-row:hover,.dsm-hist-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-source-title{font-size:14px;font-weight:650}.dsm-count{color:var(--dsw-alias-label-tertiary);font-size:12px}.dsm-path{min-width:0;margin-left:auto;overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:11px;text-overflow:ellipsis;white-space:nowrap}.dsm-source-actions{display:flex;align-items:center;gap:9px;margin-left:8px}.dsm-source-body{border-top:1px solid var(--dsw-alias-border-l1)}.dsm-source-note{padding:9px 13px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
40
40
|
.dsm-table-head,.dsm-row{display:grid;grid-template-columns:minmax(180px,1fr) 120px 90px max-content;align-items:center;column-gap:12px;padding:0 13px}.dsm-table-head{min-height:32px;border-bottom:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-row{min-height:58px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-row:last-child{border-bottom:0}.dsm-main{min-width:0}.dsm-name{overflow:hidden;font-size:13px;font-weight:570;text-overflow:ellipsis;white-space:nowrap}.dsm-note{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-tags{display:flex;align-items:center;gap:5px;flex-wrap:wrap}.dsm-tag{display:inline-flex;min-height:19px;align-items:center;padding:0 6px;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;color:var(--dsw-alias-label-secondary);font-size:10px;white-space:nowrap}.dsm-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.dsm-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.dsm-enabled{color:var(--dsw-alias-state-success-primary);font-size:12px;white-space:nowrap}.dsm-disabled{color:#d49245;font-size:12px;white-space:nowrap}.dsm-shadowed{color:var(--dsw-alias-label-tertiary);font-size:12px;white-space:nowrap}.dsm-row-actions{display:flex;align-items:center;justify-content:flex-end;gap:7px}
|
|
41
41
|
.dsm-switch{position:relative;width:34px;height:20px;flex:none;padding:0;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}.dsm-switch:after{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:50%;background:#fff;content:"";transition:transform 160ms ease}.dsm-switch-on{background:var(--dsw-alias-state-success-primary)}.dsm-switch-on:after{transform:translateX(14px)}.dsm-switch:disabled{opacity:.45;cursor:default}.dsm-trash-row{display:flex;min-height:48px;align-items:center;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);color:inherit;font:inherit;font-size:13px;cursor:pointer}.dsm-trash-count{margin-left:auto;padding:2px 7px;border-radius:99px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:11px}.dsm-empty{padding:25px 14px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-align:center}.dsm-feedback{padding:9px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-warning{border-color:#d49245;color:#d49245}.dsm-error{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
42
|
-
.dsm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.dsm-modal{box-sizing:border-box;display:flex;width:min(560px,100%)!important;max-height:min(760px,calc(100vh - 48px));min-width:0;flex-direction:column;gap:16px;padding:22px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-wide{width:min(720px,100%)!important}.dsm-modal-import{width:min(480px,100%)!important;padding:24px}.dsm-modal-head{display:flex;align-items:flex-start;gap:12px}.dsm-modal-title{margin:0;flex:1;font-size:17px;line-height:24px;font-weight:670}.dsm-form,.dsm-field,.dsm-detail-section{display:flex;flex-direction:column}.dsm-form{gap:12px}.dsm-field{gap:6px}.dsm-label,.dsm-detail-title{font-size:12px}.dsm-label{color:var(--dsw-alias-label-secondary)}.dsm-detail-title{font-weight:650}.dsm-help{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.dsm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.dsm-hidden-input{display:none}.dsm-dropzone{box-sizing:border-box;display:flex;width:100%;min-height:170px;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:22px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);transition:border-color 180ms ease,background 180ms ease}.dsm-dropzone:hover,.dsm-dropzone-active{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsm-dropzone-title{color:var(--dsw-alias-label-primary);font-size:
|
|
42
|
+
.dsm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.dsm-modal{box-sizing:border-box;display:flex;width:min(560px,100%)!important;max-height:min(760px,calc(100vh - 48px));min-width:0;flex-direction:column;gap:16px;padding:22px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dsm-modal-wide{width:min(720px,100%)!important}.dsm-modal-import{width:min(480px,100%)!important;padding:24px}.dsm-modal-head{display:flex;align-items:flex-start;gap:12px}.dsm-modal-title{margin:0;flex:1;font-size:17px;line-height:24px;font-weight:670}.dsm-form,.dsm-field,.dsm-detail-section{display:flex;flex-direction:column}.dsm-form{gap:12px}.dsm-field{gap:6px}.dsm-label,.dsm-detail-title{font-size:12px}.dsm-label{color:var(--dsw-alias-label-secondary)}.dsm-detail-title{font-weight:650}.dsm-help{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.dsm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.dsm-hidden-input{display:none}.dsm-dropzone{box-sizing:border-box;display:flex;width:100%;min-height:170px;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:22px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);transition:border-color 180ms ease,background 180ms ease;cursor:pointer}.dsm-dropzone:hover,.dsm-dropzone-active{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsm-dropzone-title{color:var(--dsw-alias-label-primary);font-size:18px;font-weight:700}.dsm-dropzone-copy{font-size:12px;line-height:18px;text-align:center}.dsm-upload-choices{display:flex;align-items:center;gap:7px}.dsm-upload-link,.dsm-file-remove{padding:0;border:0;background:transparent;font:inherit;font-size:12px;cursor:pointer}.dsm-upload-link{color:var(--dsw-alias-label-secondary)}.dsm-upload-link:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.dsm-upload-link:disabled{opacity:.45;cursor:default}.dsm-upload-divider{color:var(--dsw-alias-label-tertiary);font-size:11px}.dsm-file{display:flex;align-items:center;gap:9px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px}.dsm-file-kind{display:inline-flex;min-width:30px;height:24px;align-items:center;justify-content:center;border-radius:5px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:9px;font-weight:700}.dsm-file-name{min-width:0;overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.dsm-file-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;white-space:nowrap}.dsm-file-remove{width:24px;height:24px;color:var(--dsw-alias-label-secondary);font-size:18px}.dsm-upload-requirements{padding:1px 1px 0}.dsm-upload-requirements ul{display:flex;margin:7px 0 0;padding-left:18px;flex-direction:column;gap:5px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.dsm-detail-section{gap:7px}.dsm-detail-path,.dsm-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}.dsm-detail-path{padding:8px 10px;border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);word-break:break-all}.dsm-code{max-height:280px;margin:0;padding:12px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);line-height:18px;white-space:pre-wrap}.dsm-diag{padding:8px 10px;border-left:2px solid #d49245;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:12px}.dsm-trash-item{display:flex;align-items:center;gap:10px;padding:11px 0;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-trash-item:last-child{border-bottom:0}.dsm-trash-main{min-width:0;flex:1}
|
|
43
43
|
.dsm-fm{display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-fm-row{display:grid;grid-template-columns:minmax(96px,150px) minmax(0,1fr);gap:10px;padding:8px 12px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:18px}.dsm-fm-row:last-child{border-bottom:0}.dsm-fm-key{color:var(--dsw-alias-label-secondary);word-break:break-word}.dsm-fm-raw{margin-left:5px;color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:10px}.dsm-fm-val{min-width:0;color:var(--dsw-alias-label-primary);word-break:break-word;white-space:pre-wrap}
|
|
44
44
|
@container(max-width:780px){.dsm-table-head{display:none}.dsm-row{grid-template-columns:minmax(0,1fr) max-content;gap:8px;padding:11px 13px}.dsm-row>.dsm-tags,.dsm-row>.dsm-status{grid-column:1}.dsm-row-actions{grid-column:2;grid-row:1 / span 3}.dsm-path{display:none}}@media(max-width:760px){.dsm-summary{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:720px){.dsm-title-row{flex-wrap:wrap}}@container(max-width:520px){.dsm-head{flex-direction:column}.dsm-actions{width:100%;margin-left:0}.dsm-actions .dsm-btn{flex:1}.dsm-filters{flex-direction:column}.dsm-source-filter{width:100%}.dsm-summary{grid-template-columns:1fr}}
|
|
45
|
+
.dsm-hist-row{display:flex;align-items:center;gap:11px;padding:10px 13px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dsm-hist-row:last-child{border-bottom:0}.dsm-hist-main{min-width:0;flex:1}.dsm-hist-title{overflow:hidden;font-size:13px;font-weight:580;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:17px;text-overflow:ellipsis;white-space:nowrap}.dsm-hist-cwd-missing{color:var(--dsw-alias-state-error-primary)}.dsm-hist-actions{display:flex;gap:6px;flex:none}.dsm-hist-check,.dsm-hist-group-check{flex:none;width:15px;height:15px;margin:0 4px 0 0;cursor:pointer;accent-color:var(--dsw-alias-state-success-primary)}.dsm-hist-group-head{gap:10px}.dsm-hist-batch{display:flex;align-items:center;gap:8px;margin-left:2px;padding-left:10px;border-left:1px solid var(--dsw-alias-border-l2)}.dsm-hist-batch-count{color:var(--dsw-alias-label-secondary);font-size:12px;white-space:nowrap}.dsm-hist-batch-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px;flex:none}.dsm-dir-row{display:flex;gap:8px}.dsm-dir-row .dsm-control{flex:1}.dsm-dir-list{display:flex;max-height:220px;flex-direction:column;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}.dsm-dir-item{display:flex;width:100%;align-items:center;padding:8px 12px;border:0;border-bottom:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-align:left;cursor:pointer}.dsm-dir-item:last-child{border-bottom:0}.dsm-dir-item:hover{background:var(--dsw-alias-interactive-bg-hover)}.dsm-dir-item::before{content:"📁";margin-right:8px;font-size:12px}
|
|
45
46
|
`
|
|
46
47
|
|
|
47
48
|
function ensureCss() {
|
|
@@ -121,14 +122,11 @@ window.__ModuleLoader__.load({
|
|
|
121
122
|
return s.length <= 4 ? '****' : s.slice(0, 4) + '****'
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
//
|
|
125
|
-
// 注意:引用了本作用域的 GithubMark16 / primitives,必须定义在 apply 里。
|
|
125
|
+
// 顶部栏反馈入口:合并成一个「GitHub 和反馈」链接,指向仓库(issues 在那)。
|
|
126
126
|
function FeedbackLinks() {
|
|
127
127
|
return React.createElement('div', { className: 'dsm-feedback-links' },
|
|
128
128
|
React.createElement('a', { className: 'dsm-feedback-link', href: 'https://github.com/ouli-1242/dsh-plugin-tool-management', target: '_blank', rel: 'noreferrer', 'aria-label': 'GitHub' },
|
|
129
|
-
React.createElement(GithubMark16, null), 'GitHub')
|
|
130
|
-
React.createElement('a', { className: 'dsm-feedback-link', href: 'https://github.com/ouli-1242/dsh-plugin-tool-management/issues', target: '_blank', rel: 'noreferrer', 'aria-label': '问题反馈' },
|
|
131
|
-
React.createElement(primitives.IconListPenOutline16, null), '问题反馈'))
|
|
129
|
+
React.createElement(GithubMark16, null), 'GitHub'))
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
// MCP 服务页:与 Skills 页共用 dsm-* 设计语言(统计卡 / 筛选 / 分组卡片 / 模态框)。
|
|
@@ -517,8 +515,7 @@ window.__ModuleLoader__.load({
|
|
|
517
515
|
React.createElement('div', { className: 'dsm-title-block' },
|
|
518
516
|
React.createElement('div', { className: 'dsm-title-row' },
|
|
519
517
|
React.createElement('h2', { className: 'dsm-title' }, 'MCP'),
|
|
520
|
-
React.createElement(VersionBadge, null),
|
|
521
|
-
React.createElement(FeedbackLinks, null)),
|
|
518
|
+
React.createElement(VersionBadge, null)),
|
|
522
519
|
React.createElement('p', { className: 'dsm-desc' }, '统一管理本机的 MCP 服务与运行状态。')),
|
|
523
520
|
React.createElement('div', { className: 'dsm-actions' },
|
|
524
521
|
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null || state.loading, onClick: () => refresh() }, '刷新'),
|
|
@@ -544,22 +541,151 @@ window.__ModuleLoader__.load({
|
|
|
544
541
|
confirmNode)
|
|
545
542
|
}
|
|
546
543
|
|
|
547
|
-
//
|
|
548
|
-
|
|
549
|
-
{
|
|
550
|
-
|
|
551
|
-
|
|
544
|
+
// ---------- AGENTS.md 预设页:多套全局指令基线,应用=写入 ~/.dsh/AGENTS.md ----------
|
|
545
|
+
function AgentsMdPage() {
|
|
546
|
+
var st = React.useState({ loading: true, error: null, presets: [], current: null })
|
|
547
|
+
var state = st[0], setState = st[1]
|
|
548
|
+
var busyState = React.useState(null), busy = busyState[0], setBusy = busyState[1]
|
|
549
|
+
var em = React.useState(null), editModal = em[0], setEditModal = em[1]
|
|
550
|
+
var cm = React.useState(null), createModal = cm[0], setCreateModal = cm[1]
|
|
551
|
+
var ac = React.useState(null), applyConfirm = ac[0], setApplyConfirm = ac[1]
|
|
552
|
+
var importInput = React.useRef(null)
|
|
553
|
+
function doImport(file) {
|
|
554
|
+
if (!file) return
|
|
555
|
+
var reader = new FileReader()
|
|
556
|
+
reader.onload = function () {
|
|
557
|
+
var id = String(file.name).replace(/\.(md|markdown|txt)$/i, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'imported'
|
|
558
|
+
setBusy('import')
|
|
559
|
+
apiCall('agentsmd-import', { id: id, content: String(reader.result) }).then(function (res) {
|
|
560
|
+
setBusy(null)
|
|
561
|
+
if (res && res.ok) refresh()
|
|
562
|
+
else if (res) setState(function (s) { return Object.assign({}, s, { error: res.error }) })
|
|
563
|
+
}).catch(function () { setBusy(null) })
|
|
564
|
+
}
|
|
565
|
+
reader.readAsText(file)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function refresh() {
|
|
569
|
+
apiCall('agentsmd-list', {}).then(function (res) {
|
|
570
|
+
setState(function (s) { return { loading: false, error: res && res.ok ? null : ((res && res.error) || '加载失败'), presets: (res && res.presets) || [], current: s.current } })
|
|
571
|
+
}).catch(function (e) { setState({ loading: false, error: String((e && e.message) || e), presets: [], current: null }) })
|
|
572
|
+
apiCall('agentsmd-get-current', {}).then(function (res) {
|
|
573
|
+
if (res && res.ok) setState(function (s) { return Object.assign({}, s, { current: res }) })
|
|
574
|
+
}).catch(function () {})
|
|
575
|
+
}
|
|
576
|
+
React.useEffect(function () { refresh() }, [])
|
|
577
|
+
|
|
578
|
+
var cur = state.current
|
|
579
|
+
var curLabel = cur && cur.exists ? (cur.presetId || '(已外部修改)') : '(未设置)'
|
|
580
|
+
|
|
581
|
+
function openEdit(p) {
|
|
582
|
+
apiCall('agentsmd-read', { id: p.id }).then(function (res) {
|
|
583
|
+
if (res && res.ok) setEditModal({ id: p.id, content: res.content })
|
|
584
|
+
}).catch(function () {})
|
|
585
|
+
}
|
|
586
|
+
function doApply(id) {
|
|
587
|
+
setBusy('apply-' + id)
|
|
588
|
+
apiCall('agentsmd-apply', { id: id }).then(function () { setBusy(null); setApplyConfirm(null); refresh() }).catch(function () { setBusy(null) })
|
|
589
|
+
}
|
|
590
|
+
function doRemove(p) {
|
|
591
|
+
setBusy('rm-' + p.id)
|
|
592
|
+
apiCall('agentsmd-remove', { id: p.id }).then(function () { setBusy(null); setApplyConfirm(null); refresh() }).catch(function () { setBusy(null) })
|
|
593
|
+
}
|
|
594
|
+
function doCreate(id, from) {
|
|
595
|
+
setBusy('create')
|
|
596
|
+
apiCall('agentsmd-create', { id: id, from: from || undefined }).then(function (res) {
|
|
597
|
+
setBusy(null)
|
|
598
|
+
if (res && res.ok) { setCreateModal(null); refresh() }
|
|
599
|
+
else if (res) setCreateModal(Object.assign({}, createModal, { error: res.error }))
|
|
600
|
+
}).catch(function () { setBusy(null) })
|
|
601
|
+
}
|
|
602
|
+
function doUpdate() {
|
|
603
|
+
setBusy('update')
|
|
604
|
+
apiCall('agentsmd-update', { id: editModal.id, content: editModal.content }).then(function (res) {
|
|
605
|
+
setBusy(null)
|
|
606
|
+
if (res && res.ok) { setEditModal(null); refresh() }
|
|
607
|
+
}).catch(function () { setBusy(null) })
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
var body = state.loading
|
|
611
|
+
? React.createElement('div', { className: 'dsm-empty' }, '加载中…')
|
|
612
|
+
: state.error
|
|
613
|
+
? React.createElement('div', { className: 'dsm-feedback dsm-error' }, state.error)
|
|
614
|
+
: !state.presets.length
|
|
615
|
+
? React.createElement('div', { className: 'dsm-empty' }, '暂无预设,点「新建预设」创建。')
|
|
616
|
+
: React.createElement('div', { className: 'dsm-sources' }, state.presets.map(function (p) {
|
|
617
|
+
return React.createElement('div', { key: p.id, className: 'dsm-source' },
|
|
618
|
+
React.createElement('div', { className: 'dsm-source-head' },
|
|
619
|
+
React.createElement('div', { className: 'dsm-source-head-main' },
|
|
620
|
+
React.createElement('span', { className: 'dsm-source-title' }, p.id),
|
|
621
|
+
p.active ? React.createElement('span', { className: 'dsm-tag dsm-tag-on' }, '生效中') : null),
|
|
622
|
+
React.createElement('div', { className: 'dsm-source-actions' },
|
|
623
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', onClick: function () { openEdit(p) } }, '编辑'),
|
|
624
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy !== null, onClick: function () { setApplyConfirm({ id: p.id }) }, title: '应用后新会话生效,当前会话不变' }, p.active ? '重新应用' : '应用'),
|
|
625
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet dsm-btn-danger', disabled: busy !== null, onClick: function () { setApplyConfirm({ id: p.id, remove: true }) } }, '删除'))))
|
|
626
|
+
}))
|
|
627
|
+
|
|
628
|
+
return React.createElement('section', { className: 'dsm-section' },
|
|
629
|
+
React.createElement('div', { className: 'dsm-head' },
|
|
630
|
+
React.createElement('div', { className: 'dsm-title-block' },
|
|
631
|
+
React.createElement('div', { className: 'dsm-title-row' },
|
|
632
|
+
React.createElement('h2', { className: 'dsm-title' }, 'AGENTS.md'),
|
|
633
|
+
React.createElement(VersionBadge, null)),
|
|
634
|
+
React.createElement('p', { className: 'dsm-desc' }, '管理多套全局指令基线;「应用」写入 ~/.dsh/AGENTS.md,新会话生效,当前会话不变。')),
|
|
635
|
+
React.createElement('div', { className: 'dsm-actions' },
|
|
636
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', onClick: function () { setCreateModal({ id: '', from: '', error: null }) } }, '新建预设'),
|
|
637
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy !== null, onClick: function () { if (importInput.current) importInput.current.click() } }, '导入'))),
|
|
638
|
+
body,
|
|
639
|
+
editModal ? React.createElement(Modal, { title: '编辑预设 · ' + editModal.id, closeLabel: '取消', onClose: function () { setEditModal(null) } },
|
|
640
|
+
React.createElement('textarea', { className: 'dsm-control', style: { minHeight: '320px' }, value: editModal.content, onChange: function (e) { setEditModal(Object.assign({}, editModal, { content: e.target.value })) } }),
|
|
641
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
642
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null, onClick: doUpdate }, '保存'))) : null,
|
|
643
|
+
createModal ? React.createElement(Modal, { title: '新建预设', closeLabel: '取消', onClose: function () { setCreateModal(null) } },
|
|
644
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
645
|
+
React.createElement('label', { className: 'dsm-label' }, 'id(小写字母/数字/连字符)'),
|
|
646
|
+
React.createElement('input', { className: 'dsm-control', value: createModal.id, onChange: function (e) { setCreateModal(Object.assign({}, createModal, { id: e.target.value })) } })),
|
|
647
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
648
|
+
React.createElement('label', { className: 'dsm-label' }, '从现有预设复制(可选,留空=空白模板)'),
|
|
649
|
+
React.createElement('input', { className: 'dsm-control', value: createModal.from, onChange: function (e) { setCreateModal(Object.assign({}, createModal, { from: e.target.value })) } })),
|
|
650
|
+
createModal && createModal.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, createModal.error) : null,
|
|
651
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
652
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: busy !== null, onClick: function () { doCreate(createModal.id, createModal.from) } }, '创建'))) : null,
|
|
653
|
+
applyConfirm ? React.createElement(Modal, { title: applyConfirm.remove ? ('删除预设 · ' + applyConfirm.id) : ('应用预设 · ' + applyConfirm.id), closeLabel: '取消', onClose: function () { setApplyConfirm(null) } },
|
|
654
|
+
applyConfirm.remove
|
|
655
|
+
? React.createElement('p', { className: 'dsm-help' }, '删除预设 ' + applyConfirm.id + ' ?此操作不可恢复。')
|
|
656
|
+
: React.createElement('div', null,
|
|
657
|
+
React.createElement('p', { className: 'dsm-help' }, '将把 ' + applyConfirm.id + ' 的内容写入 ~/.dsh/AGENTS.md。'),
|
|
658
|
+
React.createElement('div', { className: 'dsm-feedback dsm-warning' }, '新会话生效,当前会话不变;当前内容已自动备份到 __last-applied__。')),
|
|
659
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
660
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn ' + (applyConfirm.remove ? 'dsm-btn-danger' : ''), disabled: busy !== null, onClick: function () { if (applyConfirm.remove) doRemove({ id: applyConfirm.id }); else doApply(applyConfirm.id) } }, applyConfirm.remove ? '确认删除' : '确认应用'))) : null,
|
|
661
|
+
React.createElement('input', { ref: importInput, type: 'file', accept: '.md,.markdown,.txt', className: 'dsm-hidden-input', onChange: function (e) { if (e.target.files && e.target.files[0]) doImport(e.target.files[0]); e.target.value = ''; } }))
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// TOOLS 设置页:一个侧栏项,内部 tab 切换 MCP / Skills / AGENTS.md / History。
|
|
665
|
+
function ToolsSection() {
|
|
666
|
+
var tabState = React.useState('mcp')
|
|
667
|
+
var active = tabState[0], setActive = tabState[1]
|
|
668
|
+
var page = active === 'mcp' ? React.createElement(MCPPage)
|
|
669
|
+
: active === 'skills' ? React.createElement(SkillManagerSection, { t: t })
|
|
670
|
+
: active === 'agents-md' ? React.createElement(AgentsMdPage)
|
|
671
|
+
: React.createElement(HistoryPage)
|
|
672
|
+
return React.createElement('section', { className: 'dsm-section' },
|
|
673
|
+
React.createElement('div', { className: 'dsm-tabs' },
|
|
674
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'mcp' ? ' dsm-tab-active' : ''), onClick: function () { setActive('mcp') } }, 'MCP'),
|
|
675
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'skills' ? ' dsm-tab-active' : ''), onClick: function () { setActive('skills') } }, 'Skills'),
|
|
676
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'agents-md' ? ' dsm-tab-active' : ''), onClick: function () { setActive('agents-md') } }, 'AGENTS.md'),
|
|
677
|
+
React.createElement('button', { type: 'button', className: 'dsm-tab' + (active === 'history' ? ' dsm-tab-active' : ''), onClick: function () { setActive('history') } }, 'History'),
|
|
678
|
+
React.createElement('div', { style: { marginLeft: 'auto' } }, React.createElement(FeedbackLinks, null))),
|
|
679
|
+
page)
|
|
680
|
+
}
|
|
552
681
|
slots.inject('settings.section', () => slots.register(
|
|
553
|
-
{ name: 'settings.section', id: 'dsm-
|
|
554
|
-
() => React.createElement(
|
|
682
|
+
{ name: 'settings.section', id: 'dsm-tools', order: 16, label: 'TOOLS' },
|
|
683
|
+
() => React.createElement(ToolsSection)
|
|
555
684
|
))
|
|
556
685
|
|
|
557
686
|
|
|
558
687
|
// ---------- Skills 页(移植自 dsh-skills-manager,Apache-2.0;类名前缀 dsm-) ----------
|
|
559
688
|
var react = React // 上游组件使用小写 react,别名到本文件的 React
|
|
560
|
-
// 上游 UI 依赖 @deepseek-ai/dsh-client-ui-primitives 的单个图标;
|
|
561
|
-
// 这里用本地内联 SVG 替代,避免为客户端引入额外 peer 依赖。
|
|
562
|
-
var primitives = { IconListPenOutline16: function () { return h('svg', { viewBox: '0 0 16 16', width: 16, height: 16, 'aria-hidden': 'true', focusable: 'false' }, h('path', { fill: 'currentColor', d: 'M3 2h10v12H3zM5 5h6M5 8h6M5 11h4' })) } }
|
|
563
689
|
var h = React.createElement
|
|
564
690
|
|
|
565
691
|
// SKILL.md frontmatter 常见键 → 中文标签(未知键原样显示,已知键附原始键名提示)。
|
|
@@ -592,9 +718,10 @@ window.__ModuleLoader__.load({
|
|
|
592
718
|
zh: {
|
|
593
719
|
"title": "Skills", "desc": "统一加载和管理本机 Agent Skills。", "link.project": "GitHub", "link.feedback": "问题反馈",
|
|
594
720
|
"btn.create": "创建技能", "btn.import": "导入", "btn.refresh": "刷新", "btn.cancel": "取消", "btn.close": "关闭", "btn.detail": "查看详情", "btn.trash": "移到回收站", "btn.restore": "恢复", "btn.delete.forever": "永久删除", "btn.file.pick": "选择文件", "btn.folder.pick": "选择文件夹", "btn.import.now": "安装", "btn.create.now": "创建技能", "btn.disable": "停用", "btn.enable": "启用", "btn.open.editor": "用系统编辑器打开",
|
|
595
|
-
"btn.custom.add": "添加目录", "btn.custom.remove": "移除",
|
|
721
|
+
"btn.custom.add": "添加目录", "btn.custom.remove": "移除", "btn.openDir": "选择",
|
|
596
722
|
"custom.add.title": "添加自定义技能目录", "custom.add.submit": "添加",
|
|
597
723
|
"custom.add.path": "目录绝对路径", "custom.add.path.placeholder": "例如 D:\\skills\\my-skills 或 /home/me/skills",
|
|
724
|
+
"custom.add.openDir.title": "浏览并选择文件夹", "custom.add.picker.title": "选择文件夹",
|
|
598
725
|
"custom.add.label": "显示名称(可选)", "custom.add.label.placeholder": "自定义目录",
|
|
599
726
|
"custom.add.help": "以只读方式接入该目录:自动发现 SKILL.md 子目录与顶层 .md 单文件,可在来源卡片上启停整个目录或单个技能;与现有来源重叠的目录会被拒绝,目录与源文件都不会被修改。",
|
|
600
727
|
"confirm.custom.remove.title": "移除自定义目录",
|
|
@@ -607,7 +734,7 @@ window.__ModuleLoader__.load({
|
|
|
607
734
|
"source.external.note": "通过 Skills Manager 接入,启停不会改写源文件。", "source.dsh.note": "DSH 本地技能可创建、导入和移到回收站;启停只更新管理器状态。",
|
|
608
735
|
"detail.title": "技能详情", "detail.body": "正文", "detail.frontmatter": "元数据", "detail.noFrontmatter": "该技能未提供元数据。", "detail.diagnostics": "诊断", "detail.path": "源文件", "detail.noIssues": "未发现诊断问题。",
|
|
609
736
|
"create.title": "创建技能", "create.target": "创建位置", "create.name": "名称", "create.name.placeholder": "例如 code-review-helper", "create.description": "简介", "create.description.placeholder": "一句话说明什么时候使用", "create.body": "正文(Markdown)", "create.body.placeholder": "写下技能要遵循的指令、步骤和边界…", "create.chat.note": "对话中的 create_skill 仍创建用户级 DSH Skill;项目 Skill 可在这里选择活动项目后创建。",
|
|
610
|
-
"import.title": "导入技能", "upload.drop.title": "
|
|
737
|
+
"import.title": "导入技能", "upload.drop.title": "点击或拖入此处", "upload.drop.copy": "支持的文件类型:.zip、技能文件夹或单个 SKILL.md", "upload.selected.one": "{count} 个文件 · {size}", "upload.selected.other": "{count} 个文件 · {size}", "upload.remove": "移除所选内容", "upload.requirements": "文件要求", "upload.requirement.skill": "压缩包或文件夹需包含 SKILL.md", "upload.requirement.frontmatter": "SKILL.md 需包含 YAML 格式的技能名称和描述", "upload.requirement.copy": "导入时复制完整内容,不修改原始来源", "upload.importing": "正在安装…", "status.selected": "已选择", "select.file.invalid": "请选择 .zip 或单个 SKILL.md。", "select.folder.invalid": "所选文件夹中没有找到 SKILL.md。", "error.browse.absolute": "目录路径必须是绝对路径:{path}", "error.browse.unreadable": "无法读取目录:{path}", "error.browse.notDirectory": "不是目录:{path}",
|
|
611
738
|
"trash.title": "回收站", "trash.count.one": "{count} 个待处理技能", "trash.count.other": "{count} 个待处理技能", "trash.empty": "回收站为空。", "trash.deletedAt": "删除于 {time}", "trash.source": "来源:{source}",
|
|
612
739
|
"confirm.trash.title": "移到回收站?", "confirm.trash.desc": "“{name}”将从当前技能来源移入回收站,之后可以恢复到原位置。", "confirm.delete.title": "永久删除?", "confirm.delete.desc": "“{name}”将从回收站永久删除,无法恢复。",
|
|
613
740
|
"result.created": "已创建技能:{name}", "result.imported": "导入完成:{names}", "result.importPartial": "已导入:{imported};已跳过同名技能:{skipped}", "result.importSkipped": "未导入任何技能;已跳过同名技能:{names}", "result.importEmpty": "未导入任何技能。", "result.importWarnings": "{result};警告:{warnings}", "result.restored": "已恢复技能:{name}", "result.trashed": "已移到回收站:{name}", "result.deleted": "已永久删除:{name}", "result.updated": "状态已更新。", "result.opened": "已用系统默认程序打开:{path}", "error.action": "操作失败:{error}",
|
|
@@ -634,7 +761,7 @@ window.__ModuleLoader__.load({
|
|
|
634
761
|
"source.external.note": "Managed through Skills Manager; toggles never rewrite source files.", "source.dsh.note": "Local DSH skills can be created, imported, and moved to trash; toggles update manager state only.",
|
|
635
762
|
"detail.title": "Skill details", "detail.body": "Body", "detail.frontmatter": "Metadata", "detail.noFrontmatter": "This skill provides no metadata.", "detail.diagnostics": "Diagnostics", "detail.path": "Source file", "detail.noIssues": "No diagnostic issues found.",
|
|
636
763
|
"create.title": "Create skill", "create.target": "Create in", "create.name": "Name", "create.name.placeholder": "e.g. code-review-helper", "create.description": "Description", "create.description.placeholder": "One sentence describing when to use it", "create.body": "Body (Markdown)", "create.body.placeholder": "Write the instructions, steps, and boundaries…", "create.chat.note": "The conversational create_skill tool still creates a user-level DSH Skill; choose an active project here for a project Skill.",
|
|
637
|
-
"import.title": "Import skill", "upload.drop.title": "
|
|
764
|
+
"import.title": "Import skill", "upload.drop.title": "Click or drop here", "upload.drop.copy": "Supported: .zip, a skill folder, or one SKILL.md", "upload.selected.one": "{count} file · {size}", "upload.selected.other": "{count} files · {size}", "upload.remove": "Remove selection", "upload.requirements": "File requirements", "upload.requirement.skill": "Archives and folders must contain SKILL.md", "upload.requirement.frontmatter": "SKILL.md must include a YAML name and description", "upload.requirement.copy": "Import copies all content and never modifies the source", "upload.importing": "Installing…", "status.selected": "Selected", "select.file.invalid": "Choose a .zip archive or one SKILL.md.", "select.folder.invalid": "No SKILL.md was found in the selected folder.", "error.browse.absolute": "Folder path must be absolute: {path}", "error.browse.unreadable": "Could not read folder: {path}", "error.browse.notDirectory": "Not a folder: {path}",
|
|
638
765
|
"trash.title": "Trash", "trash.count.one": "{count} skill pending", "trash.count.other": "{count} skills pending", "trash.empty": "Trash is empty.", "trash.deletedAt": "Deleted {time}", "trash.source": "Source: {source}",
|
|
639
766
|
"confirm.trash.title": "Move to trash?", "confirm.trash.desc": "“{name}” will move out of its current skill source and can be restored to the same location later.", "confirm.delete.title": "Delete forever?", "confirm.delete.desc": "“{name}” will be permanently deleted from trash and cannot be recovered.",
|
|
640
767
|
"result.created": "Created skill: {name}", "result.imported": "Import complete: {names}", "result.importPartial": "Imported: {imported}; skipped existing skills: {skipped}", "result.importSkipped": "No skills were imported; existing skills were skipped: {names}", "result.importEmpty": "No skills were imported.", "result.importWarnings": "{result}; warnings: {warnings}", "result.restored": "Restored skill: {name}", "result.trashed": "Moved to trash: {name}", "result.deleted": "Permanently deleted: {name}", "result.updated": "Status updated.", "error.action": "Action failed: {error}",
|
|
@@ -741,7 +868,47 @@ function callApi(path, options) {
|
|
|
741
868
|
}
|
|
742
869
|
function Switch(props) { return h("button", { type: "button", className: "dsm-switch" + (props.on ? " dsm-switch-on" : ""), role: "switch", "aria-checked": props.on, "aria-label": props.label, disabled: props.disabled, onClick: props.onClick }); }
|
|
743
870
|
function GithubMark16() { return h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": true, focusable: "false" }, h("path", { fill: "currentColor", d: "M8 0a8 8 0 0 0-2.53 15.59c.4.074.547-.173.547-.385 0-.19-.007-.693-.01-1.36-2.226.484-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.872.87 2.328.665.072-.517.28-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.956 0-.874.31-1.588.823-2.148-.083-.202-.357-1.017.078-2.12 0 0 .672-.215 2.2.82A7.65 7.65 0 0 1 8 4.8c.68.003 1.365.092 2.004.27 1.527-1.035 2.197-.82 2.197-.82.437 1.103.162 1.918.08 2.12.513.56.822 1.274.822 2.148 0 3.076-1.872 3.752-3.654 3.95.288.248.544.735.544 1.482 0 1.07-.01 1.932-.01 2.195 0 .214.144.463.55.384A8.001 8.001 0 0 0 8 0Z" })); }
|
|
744
|
-
function Modal(props) { var ref = react.useRef(null); react.useEffect(function () { if (ref.current) ref.current.focus(); }, []); return h("div", { className: "dsm-mask", onMouseDown: function (e) { if (e.target === e.currentTarget) props.onClose(); } }, h("div", { ref: ref, tabIndex: -1, className: "dsm-modal" + (props.wide ? " dsm-modal-wide" : "") + (props.className ? " " + props.className : ""), role: "dialog", "aria-modal": "true", onKeyDown: function (e) { if (!handleModalEscape(e, props.onClose)) trapModalFocus(e.currentTarget, e); } }, h("div", { className: "dsm-modal-head" }, h("h3", { className: "dsm-modal-title" }, props.title), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: props.onClose }, props.closeLabel)), props.children)); }
|
|
871
|
+
function Modal(props) { var ref = react.useRef(null); react.useEffect(function () { if (ref.current) ref.current.focus(); }, []); return h("div", { className: "dsm-mask", onMouseDown: function (e) { if (e.target === e.currentTarget) props.onClose(); } }, h("div", { ref: ref, tabIndex: -1, className: "dsm-modal" + (props.wide ? " dsm-modal-wide" : "") + (props.className ? " " + props.className : ""), role: "dialog", "aria-modal": "true", onKeyDown: function (e) { if (!handleModalEscape(e, props.onClose)) trapModalFocus(e.currentTarget, e); } }, h("div", { className: "dsm-modal-head" }, h("h3", { className: "dsm-modal-title" }, props.title), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: props.onClose }, props.closeLabel)), props.children)); }
|
|
872
|
+
|
|
873
|
+
// 目录选择器:经 host 的 dir-list 逐级浏览目录树,选中后把绝对路径回填给调用方。
|
|
874
|
+
// 标准浏览器不暴露所选文件夹的绝对路径(File.path 仅 Electron 可用),故不走
|
|
875
|
+
// webkitdirectory,改由宿主端列目录 + 前端目录树完成「选择文件夹」。
|
|
876
|
+
function DirPickerModal(props) {
|
|
877
|
+
var stState = react.useState({ loading: true, error: null, current: '', parent: null, entries: [] })
|
|
878
|
+
var st = stState[0], setSt = stState[1]
|
|
879
|
+
var txState = react.useState(props.initial || '')
|
|
880
|
+
var text = txState[0], setText = txState[1]
|
|
881
|
+
function load(dir) {
|
|
882
|
+
setSt({ loading: true, error: null, current: dir || '', parent: null, entries: [] })
|
|
883
|
+
apiCall('dir-list', { dir: dir || '' }).then(function (res) {
|
|
884
|
+
if (res && res.ok) setSt({ loading: false, error: null, current: res.current || '', parent: res.parent || null, entries: res.entries || [] })
|
|
885
|
+
else setSt({ loading: false, error: (res && res.error) || '无法读取目录', current: dir || '', parent: null, entries: [] })
|
|
886
|
+
}).catch(function () {
|
|
887
|
+
setSt({ loading: false, error: '无法读取目录', current: dir || '', parent: null, entries: [] })
|
|
888
|
+
})
|
|
889
|
+
}
|
|
890
|
+
react.useEffect(function () { load(props.initial || '') }, [])
|
|
891
|
+
function jump() { var v = String(text || '').trim(); if (v) load(v) }
|
|
892
|
+
function goUp() { if (!st.parent) return; setText(st.parent); load(st.parent) }
|
|
893
|
+
function enter(path) { setText(path); load(path) }
|
|
894
|
+
function pick() { var target = String(st.current || '').trim() || String(text || '').trim(); if (target && props.onPick) props.onPick(target) }
|
|
895
|
+
return h(Modal, { title: props.title || '选择文件夹', closeLabel: props.closeLabel || '关闭', onClose: props.onClose },
|
|
896
|
+
h("div", { className: "dsm-form" },
|
|
897
|
+
h("label", { className: "dsm-field" },
|
|
898
|
+
h("span", { className: "dsm-label" }, '当前路径'),
|
|
899
|
+
h("div", { className: "dsm-dir-row" },
|
|
900
|
+
h("input", { className: "dsm-control", value: text, placeholder: '输入目录路径,回车跳转', onChange: function (e) { setText(e.target.value); }, onKeyDown: function (e) { if (e.key === 'Enter') { e.preventDefault(); jump(); } } }),
|
|
901
|
+
h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: st.loading || !String(text || '').trim(), onClick: jump }, '跳转'))),
|
|
902
|
+
st.error ? h("div", { className: "dsm-feedback dsm-error", role: "alert" }, st.error) : null,
|
|
903
|
+
h("div", { className: "dsm-dir-list" },
|
|
904
|
+
st.loading ? h("div", { className: "dsm-empty" }, '加载中…')
|
|
905
|
+
: st.entries.length ? st.entries.map(function (e) { return h("button", { key: e.path, type: "button", className: "dsm-dir-item", title: e.path, onClick: function () { enter(e.path); } }, e.name); })
|
|
906
|
+
: h("div", { className: "dsm-empty" }, '(当前目录没有子文件夹)')),
|
|
907
|
+
h("div", { className: "dsm-modal-actions" },
|
|
908
|
+
h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: st.loading || !st.parent, onClick: goUp }, '上级'),
|
|
909
|
+
h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: props.onClose }, '取消'),
|
|
910
|
+
h("button", { type: "button", className: "dsm-btn", disabled: st.loading || !(String(st.current || '').trim() || String(text || '').trim()), onClick: pick }, '选择此文件夹'))))
|
|
911
|
+
}
|
|
745
912
|
|
|
746
913
|
function SkillManagerSection(props) {
|
|
747
914
|
var t = props.t;
|
|
@@ -767,6 +934,10 @@ function callApi(path, options) {
|
|
|
767
934
|
function openSource() { if (!detail || !detail.path) return; setBusy(true); setResult(null); apiCall("skill-open", { path: detail.path }).then(function (res) { if (res && res.ok) setResult({ ok: true, text: t("result.opened", { path: detail.path }) }); else setResult({ ok: false, text: t("error.action", { error: translateError(t, res) }) }); }).catch(function (error) { setResult({ ok: false, text: t("error.action", { error: translateError(t, error) }) }); }).finally(function () { setBusy(false); }); }
|
|
768
935
|
function updateForm(key, value) { setForm(Object.assign({}, form, { [key]: value })); }
|
|
769
936
|
// 自定义技能目录:添加 / 移除(走 skill-custom-* ops,成功后静默刷新)。
|
|
937
|
+
function openCustomDir() {
|
|
938
|
+
if (busy) return;
|
|
939
|
+
setCustomForm(Object.assign({}, customForm, { picking: true }));
|
|
940
|
+
}
|
|
770
941
|
function submitCustomAdd() {
|
|
771
942
|
var payload = { path: String(customForm.path || "").trim(), label: String(customForm.label || "").trim() };
|
|
772
943
|
if (!payload.path) return;
|
|
@@ -805,17 +976,15 @@ function callApi(path, options) {
|
|
|
805
976
|
}
|
|
806
977
|
|
|
807
978
|
var summary = data.summary || { total: 0, enabled: 0, disabled: 0, issues: 0 };
|
|
808
|
-
var content = [h("style", { key: "css" }, CSS), h("div", { key: "head", className: "dsm-head" }, h("div", { className: "dsm-title-block" }, h("div", { className: "dsm-title-row" }, h("h2", { className: "dsm-title" }, t("title")), h(VersionBadge, null)
|
|
979
|
+
var content = [h("style", { key: "css" }, CSS), h("div", { key: "head", className: "dsm-head" }, h("div", { className: "dsm-title-block" }, h("div", { className: "dsm-title-row" }, h("h2", { className: "dsm-title" }, t("title")), h(VersionBadge, null)), h("p", { className: "dsm-desc" }, t("desc"))), h("div", { className: "dsm-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy || snapshot.loading, onClick: function () { refresh(false); } }, t("btn.refresh")), h("button", { type: "button", className: "dsm-btn", onClick: openCreate }, t("btn.create")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setResult(null); setUpload(null); setModal("import"); } }, t("btn.import")), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: function () { setResult(null); setCustomForm({ path: "", label: "" }); setModal("custom-add"); } }, t("btn.custom.add")))), h("div", { key: "summary", className: "dsm-summary dsm-summary-4" }, [[summary.total, "summary.total"], [summary.enabled, "summary.enabled"], [summary.disabled, "summary.disabled"], [summary.issues || 0, "summary.issues"]].map(function (item) { return h("div", { key: item[1], className: "dsm-stat" }, h("strong", null, item[0]), t(countKey(item[1], item[0]), { count: item[0] }).replace(String(item[0]), "")); })), h("div", { key: "filters", className: "dsm-filters" }, h("input", { className: "dsm-control dsm-search", value: query, "aria-label": t("search"), placeholder: t("search.placeholder"), onChange: function (e) { setQuery(e.target.value); } }), h("div", { className: "dsm-source-filter" }, h(SourceSelect, { value: activeSource, options: options, onChange: setSource }))), result && modal !== "import" ? h("div", { key: "result", className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error") }, result.text) : null].concat((data.warnings || []).map(function (warning, index) { return h("div", { key: "warning-" + index, className: "dsm-feedback dsm-warning", role: "alert" }, translateError(t, warning)); }), [snapshot.error ? h("div", { key: "error", className: "dsm-feedback dsm-error" }, snapshot.error) : null, snapshot.loading && !snapshot.data ? h("div", { key: "loading", className: "dsm-empty" }, t("loading")) : h("div", { key: "sources", className: "dsm-sources" }, roots.map(renderRoot)), h("button", { key: "trash", type: "button", className: "dsm-trash-row", onClick: function () { setModal("trash"); } }, h("span", null, t("trash.title")), h("span", { className: "dsm-trash-count" }, (data.trash || []).length))]);
|
|
809
980
|
|
|
810
981
|
if (modal === "create") content.push(h(Modal, { key: "create", title: t("create.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-form" }, h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("create.target")), h(SourceSelect, { value: form.root, options: createOptions, onChange: function (value) { updateForm("root", value); } })), [["name", "create.name", "create.name.placeholder"], ["description", "create.description", "create.description.placeholder"]].map(function (field) { return h("label", { key: field[0], className: "dsm-field" }, h("span", { className: "dsm-label" }, t(field[1])), h("input", { className: "dsm-control", value: form[field[0]], placeholder: t(field[2]), onChange: function (e) { updateForm(field[0], e.target.value); } })); }), h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("create.body")), h("textarea", { className: "dsm-control", value: form.body, placeholder: t("create.body.placeholder"), onChange: function (e) { updateForm("body", e.target.value); } })), h("p", { className: "dsm-help" }, t("create.chat.note"))), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { className: "dsm-btn", disabled: busy || !form.name.trim() || !form.description.trim() || !form.body.trim(), onClick: submitCreate }, t("btn.create.now")))));
|
|
811
982
|
if (modal === "import") content.push(h(Modal, { key: "import", className: "dsm-modal-import", title: t("import.title"), closeLabel: t("btn.close"), onClose: function () { pickerOpenRef.current = false; setUpload(null); setResult(null); setModal(null); } },
|
|
812
983
|
h("input", { ref: importInputRef, className: "dsm-hidden-input", type: "file", accept: ".zip,.md", onChange: function (event) { selectUploadFiles(event.target.files); event.target.value = ""; } }),
|
|
813
984
|
h("input", { ref: folderInputRef, className: "dsm-hidden-input", type: "file", multiple: true, webkitdirectory: "", directory: "", onChange: function (event) { selectUploadFiles(event.target.files); event.target.value = ""; } }),
|
|
814
|
-
h("div", { className: "dsm-dropzone", onDragOver: function (event) { event.preventDefault(); event.currentTarget.classList.add("dsm-dropzone-active"); }, onDragLeave: function (event) { event.currentTarget.classList.remove("dsm-dropzone-active"); }, onDrop: function (event) { event.preventDefault(); event.currentTarget.classList.remove("dsm-dropzone-active"); droppedFiles(event.dataTransfer).then(selectUploadFiles).catch(function (error) { setResult({ ok: false, text: translateError(t, error) }); }); } },
|
|
985
|
+
h("div", { className: "dsm-dropzone", onClick: function () { openNativePicker(importInputRef); }, onDragOver: function (event) { event.preventDefault(); event.currentTarget.classList.add("dsm-dropzone-active"); }, onDragLeave: function (event) { event.currentTarget.classList.remove("dsm-dropzone-active"); }, onDrop: function (event) { event.preventDefault(); event.currentTarget.classList.remove("dsm-dropzone-active"); droppedFiles(event.dataTransfer).then(selectUploadFiles).catch(function (error) { setResult({ ok: false, text: translateError(t, error) }); }); } },
|
|
815
986
|
h("span", { className: "dsm-dropzone-title" }, t("upload.drop.title")),
|
|
816
|
-
h("span", { className: "dsm-dropzone-copy" }, t("upload.drop.copy")),
|
|
817
|
-
h("div", { className: "dsm-upload-choices" }, h("button", { type: "button", className: "dsm-upload-link", disabled: busy, onClick: function (event) { event.stopPropagation(); openNativePicker(folderInputRef); } }, t("btn.folder.pick")), h("span", { className: "dsm-upload-divider" }, "/"), h("button", { type: "button", className: "dsm-upload-link", disabled: busy, onClick: function (event) { event.stopPropagation(); openNativePicker(importInputRef); } }, t("btn.file.pick")))
|
|
818
|
-
),
|
|
987
|
+
h("span", { className: "dsm-dropzone-copy" }, t("upload.drop.copy"))),
|
|
819
988
|
upload ? h("div", { className: "dsm-file", title: upload.name }, h("span", { className: "dsm-file-kind", "aria-hidden": "true" }, upload.kind === "zip" ? "ZIP" : upload.kind === "folder" ? "DIR" : "MD"), h("span", { className: "dsm-file-name" }, upload.name), h("span", { className: "dsm-file-meta" }, t(countKey("upload.selected", upload.count), { count: upload.count, size: upload.size < 1024 ? upload.size + " B" : Math.ceil(upload.size / 1024) + " KB" })), h("button", { type: "button", className: "dsm-file-remove", "aria-label": t("upload.remove"), onClick: function () { setUpload(null); } }, "×")) : null,
|
|
820
989
|
result ? h("div", { className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error"), role: "alert" }, result.text) : null,
|
|
821
990
|
h("div", { className: "dsm-upload-requirements" }, h("div", { className: "dsm-label" }, t("upload.requirements")), h("ul", null, h("li", null, t("upload.requirement.skill")), h("li", null, t("upload.requirement.frontmatter")), h("li", null, t("upload.requirement.copy")))),
|
|
@@ -824,7 +993,7 @@ function callApi(path, options) {
|
|
|
824
993
|
if (modal === "trash") content.push(h(Modal, { key: "trash-modal", title: t("trash.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-count" }, t(countKey("trash.count", data.trash.length), { count: data.trash.length })), data.trash.length ? data.trash.map(function (item) { return h("div", { key: item.id, className: "dsm-trash-item" }, h("div", { className: "dsm-trash-main" }, h("div", { className: "dsm-name" }, item.name), h("div", { className: "dsm-note" }, t("trash.deletedAt", { time: new Date(item.deletedAt).toLocaleString() }) + " · " + t("trash.source", { source: trashRootLabel(item) }))), h("button", { className: "dsm-btn dsm-btn-quiet", disabled: busy, onClick: function () { post("/trash-restore", { id: item.id }, "result.restored", { name: item.name }); } }, t("btn.restore")), h("button", { className: "dsm-btn dsm-btn-quiet dsm-btn-danger", disabled: busy, onClick: function () { setModal({ type: "delete-confirm", id: item.id, name: item.name }); } }, t("btn.delete.forever"))); }) : h("div", { className: "dsm-empty" }, t("trash.empty"))));
|
|
825
994
|
if (modal && modal.type === "trash-confirm") content.push(h(Modal, { key: "trash-confirm", title: t("confirm.trash.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("p", { className: "dsm-desc" }, t("confirm.trash.desc", { name: modal.name })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { className: "dsm-btn", disabled: busy, onClick: function () { post("/delete", { root: modal.root, name: modal.name }, "result.trashed", { name: modal.name }).then(function () { setModal(null); }).catch(function () {}); } }, t("btn.trash")))));
|
|
826
995
|
if (modal && modal.type === "delete-confirm") content.push(h(Modal, { key: "delete-confirm", title: t("confirm.delete.title"), closeLabel: t("btn.close"), onClose: function () { setModal("trash"); } }, h("p", { className: "dsm-desc" }, t("confirm.delete.desc", { name: modal.name })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal("trash"); } }, t("btn.cancel")), h("button", { className: "dsm-btn dsm-btn-danger", disabled: busy, onClick: function () { post("/trash-delete", { id: modal.id }, "result.deleted", { name: modal.name }).then(function () { setModal("trash"); }).catch(function () {}); } }, t("btn.delete.forever")))));
|
|
827
|
-
if (modal === "custom-add") content.push(h(Modal, { key: "custom-add", title: t("custom.add.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-form" }, h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.path")), h("input", { className: "dsm-control", value: customForm.path, placeholder: t("custom.add.path.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { path: e.target.value })); } })), h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.label")), h("input", { className: "dsm-control", value: customForm.label, placeholder: t("custom.add.label.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { label: e.target.value })); } })), h("p", { className: "dsm-help" }, t("custom.add.help")), result ? h("div", { className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error"), role: "alert" }, result.text) : null), h("div", { className: "dsm-modal-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { type: "button", className: "dsm-btn", disabled: busy || !String(customForm.path || "").trim(), onClick: submitCustomAdd }, t("custom.add.submit")))));
|
|
996
|
+
if (modal === "custom-add") content.push(h(Modal, { key: "custom-add", title: t("custom.add.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("div", { className: "dsm-form" }, h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.path")), h("div", { className: "dsm-dir-row" }, h("input", { className: "dsm-control", value: customForm.path, placeholder: t("custom.add.path.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { path: e.target.value })); } }), h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", disabled: busy, onClick: openCustomDir, title: t("custom.add.openDir.title") }, t("btn.openDir")))), h("label", { className: "dsm-field" }, h("span", { className: "dsm-label" }, t("custom.add.label")), h("input", { className: "dsm-control", value: customForm.label, placeholder: t("custom.add.label.placeholder"), onChange: function (e) { setCustomForm(Object.assign({}, customForm, { label: e.target.value })); } })), h("p", { className: "dsm-help" }, t("custom.add.help")), result ? h("div", { className: "dsm-feedback" + (result.warning ? " dsm-warning" : result.ok ? "" : " dsm-error"), role: "alert" }, result.text) : null), customForm.picking ? h(DirPickerModal, { key: "custom-dir-picker", title: t("custom.add.picker.title"), initial: String(customForm.path || ""), closeLabel: t("btn.close"), onClose: function () { setCustomForm(Object.assign({}, customForm, { picking: false })); }, onPick: function (path) { setCustomForm(Object.assign({}, customForm, { path: path, picking: false })); } }) : null, h("div", { className: "dsm-modal-actions" }, h("button", { type: "button", className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { type: "button", className: "dsm-btn", disabled: busy || !String(customForm.path || "").trim(), onClick: submitCustomAdd }, t("custom.add.submit")))));
|
|
828
997
|
if (modal && modal.type === "custom-remove-confirm") content.push(h(Modal, { key: "custom-remove-confirm", title: t("confirm.custom.remove.title"), closeLabel: t("btn.close"), onClose: function () { setModal(null); } }, h("p", { className: "dsm-desc" }, t("confirm.custom.remove.desc", { name: modal.name })), h("div", { className: "dsm-modal-actions" }, h("button", { className: "dsm-btn dsm-btn-secondary", onClick: function () { setModal(null); } }, t("btn.cancel")), h("button", { className: "dsm-btn dsm-btn-danger", disabled: busy, onClick: function () { submitCustomRemove(modal.key, modal.name); } }, t("btn.custom.remove")))));
|
|
829
998
|
return h("section", { className: "dsm-section" }, content);
|
|
830
999
|
}
|
|
@@ -842,7 +1011,445 @@ function callApi(path, options) {
|
|
|
842
1011
|
}
|
|
843
1012
|
} catch (e) { /* locale 服务缺失时退回内置词典 */ }
|
|
844
1013
|
|
|
1014
|
+
// ---------- History 页(归档会话管理:恢复 / 永久删除 / 保留期)----------
|
|
1015
|
+
// 折叠自 dsh-archive-manager 的归档会话设置页,但客户端经本插件 HTTP API
|
|
1016
|
+
// 驱动(history-list / -archive / -unarchive / -delete / -retention-*),不依赖
|
|
1017
|
+
// ui-workspace 客户端 store 或 typert remote——会话标题由 host 端 best-effort 读取。
|
|
1018
|
+
function HistoryPage() {
|
|
1019
|
+
var state = React.useState({ loading: true, items: [], workspaces: {}, retentionDays: 0, error: null })
|
|
1020
|
+
var data = state[0], setData = state[1]
|
|
1021
|
+
var modalState = React.useState(null)
|
|
1022
|
+
var modal = modalState[0], setModal = modalState[1]
|
|
1023
|
+
var searchState = React.useState('')
|
|
1024
|
+
var query = searchState[0], setQuery = searchState[1]
|
|
1025
|
+
var busyState = React.useState(false)
|
|
1026
|
+
var busy = busyState[0], setBusy = busyState[1]
|
|
1027
|
+
// 多选集合:勾选的 sessionId。最小 React 支持函数式初值(Set 惰性创建)。
|
|
1028
|
+
var selState = React.useState(function () { return new Set() })
|
|
1029
|
+
var selected = selState[0], setSelected = selState[1]
|
|
1030
|
+
// 导入对话弹窗:null = 关闭;{ busy, error, result, cwd } = 打开。
|
|
1031
|
+
var impState = React.useState(null)
|
|
1032
|
+
var imp = impState[0], setImp = impState[1]
|
|
1033
|
+
var importInputRef = React.useRef(null)
|
|
1034
|
+
// 导出对话弹窗:null = 关闭;{ busy, error, result, outDir, format, wsFilter, selectedIds } = 打开。
|
|
1035
|
+
var expState = React.useState(null)
|
|
1036
|
+
var exp = expState[0], setExp = expState[1]
|
|
1037
|
+
// 分组卡片折叠状态:key → true(收起)。默认全部展开。
|
|
1038
|
+
var collState = React.useState({})
|
|
1039
|
+
var collapsed = collState[0], setCollapsed = collState[1]
|
|
1040
|
+
|
|
1041
|
+
function refresh() {
|
|
1042
|
+
apiCall('history-list', {}).then(function (r) {
|
|
1043
|
+
if (r && r.ok) {
|
|
1044
|
+
var items = r.items || []
|
|
1045
|
+
setData({ loading: false, items: items, workspaces: r.workspaces || {}, retentionDays: r.retentionDays || 0, error: null })
|
|
1046
|
+
// 裁剪选中集合:已被外部恢复/删除的会话不再计入。
|
|
1047
|
+
setSelected(function (prev) {
|
|
1048
|
+
var has = new Set(items.map(function (it) { return it.sessionId }))
|
|
1049
|
+
var next = new Set()
|
|
1050
|
+
prev.forEach(function (id) { if (has.has(id)) next.add(id) })
|
|
1051
|
+
return next
|
|
1052
|
+
})
|
|
1053
|
+
} else setData({ loading: false, items: [], workspaces: {}, retentionDays: 0, error: (r && r.error) || '加载失败' })
|
|
1054
|
+
})
|
|
1055
|
+
}
|
|
1056
|
+
React.useEffect(function () { refresh() }, [])
|
|
1057
|
+
|
|
1058
|
+
function shortId(id) { var s = String(id || ''); return s.length > 12 ? s.slice(0, 8) + '…' + s.slice(-4) : s }
|
|
1059
|
+
|
|
1060
|
+
var filtered = data.items
|
|
1061
|
+
if (query) {
|
|
1062
|
+
var q = query.toLowerCase()
|
|
1063
|
+
filtered = data.items.filter(function (it) {
|
|
1064
|
+
return (it.title && it.title.toLowerCase().indexOf(q) >= 0)
|
|
1065
|
+
|| (it.sessionId && it.sessionId.toLowerCase().indexOf(q) >= 0)
|
|
1066
|
+
|| (it.cwd && it.cwd.toLowerCase().indexOf(q) >= 0)
|
|
1067
|
+
})
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// 按项目分组(host 提供 workspaces 时启用;否则扁平列表)。
|
|
1071
|
+
var grouped = !!(data.workspaces && Object.keys(data.workspaces).length)
|
|
1072
|
+
var visibleIds = filtered.map(function (it) { return it.sessionId })
|
|
1073
|
+
var groups = []
|
|
1074
|
+
if (grouped) {
|
|
1075
|
+
var byWs = new Map()
|
|
1076
|
+
filtered.forEach(function (it) {
|
|
1077
|
+
var wid = data.workspaces[it.workspaceId] ? it.workspaceId : null
|
|
1078
|
+
var key = wid ? ('ws:' + wid) : 'ungrouped'
|
|
1079
|
+
if (!byWs.has(key)) byWs.set(key, [])
|
|
1080
|
+
byWs.get(key).push(it)
|
|
1081
|
+
})
|
|
1082
|
+
byWs.forEach(function (items2, key) {
|
|
1083
|
+
var wid = key === 'ungrouped' ? null : key.slice(3)
|
|
1084
|
+
var wsRec = wid ? data.workspaces[wid] : null
|
|
1085
|
+
var title = wid ? ((wsRec && wsRec.title) || wid) : '未分组'
|
|
1086
|
+
items2.sort(function (a, b) { return ((b.archivedAt || b.createdAt) || 0) - ((a.archivedAt || a.createdAt) || 0) })
|
|
1087
|
+
var maxAt = 0
|
|
1088
|
+
items2.forEach(function (it) { var at = (it.archivedAt || it.createdAt) || 0; if (at > maxAt) maxAt = at })
|
|
1089
|
+
groups.push({ key: key, title: title, workspaceId: wid, path: wsRec && wsRec.path, items: items2, maxAt: maxAt })
|
|
1090
|
+
})
|
|
1091
|
+
// 组按组内最新时间戳降序;未分组固定最后。
|
|
1092
|
+
groups.sort(function (g1, g2) {
|
|
1093
|
+
if (g1.key === 'ungrouped') return 1
|
|
1094
|
+
if (g2.key === 'ungrouped') return -1
|
|
1095
|
+
return g2.maxAt - g1.maxAt
|
|
1096
|
+
})
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// 「全选」状态:当前过滤结果是否全部选中(按钮文案切换为「取消全选」)。
|
|
1100
|
+
var allVisible = filtered.length > 0 && visibleIds.every(function (id) { return selected.has(id) })
|
|
1101
|
+
|
|
1102
|
+
function doUnarchive(sessionId) {
|
|
1103
|
+
setBusy(true)
|
|
1104
|
+
apiCall('history-unarchive', { sessionId: sessionId }).then(function (r) {
|
|
1105
|
+
setBusy(false)
|
|
1106
|
+
if (r && r.ok) refresh(); else setModal({ type: 'error', message: (r && r.error) || '恢复失败' })
|
|
1107
|
+
})
|
|
1108
|
+
}
|
|
1109
|
+
function doDelete(sessionId) {
|
|
1110
|
+
setBusy(true)
|
|
1111
|
+
apiCall('history-delete', { sessionId: sessionId }).then(function (r) {
|
|
1112
|
+
setBusy(false)
|
|
1113
|
+
if (r && r.ok) { setModal(null); refresh() } else setModal({ type: 'error', message: (r && r.error) || '删除失败' })
|
|
1114
|
+
})
|
|
1115
|
+
}
|
|
1116
|
+
function setRetention(days) {
|
|
1117
|
+
apiCall('history-retention-set', { retentionDays: days }).then(function (r) {
|
|
1118
|
+
if (r && r.ok) setData(Object.assign({}, data, { retentionDays: days }))
|
|
1119
|
+
else setModal({ type: 'error', message: (r && r.error) || '设置失败' })
|
|
1120
|
+
})
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// ---------- 多选 / 批量交互 ----------
|
|
1124
|
+
function toggleSelect(id) {
|
|
1125
|
+
setSelected(function (prev) {
|
|
1126
|
+
var next = new Set(prev)
|
|
1127
|
+
if (next.has(id)) next.delete(id); else next.add(id)
|
|
1128
|
+
return next
|
|
1129
|
+
})
|
|
1130
|
+
}
|
|
1131
|
+
function toggleGroup(ids) {
|
|
1132
|
+
setSelected(function (prev) {
|
|
1133
|
+
var next = new Set(prev)
|
|
1134
|
+
var all = ids.every(function (id) { return next.has(id) })
|
|
1135
|
+
ids.forEach(function (id) { if (all) next.delete(id); else next.add(id) })
|
|
1136
|
+
return next
|
|
1137
|
+
})
|
|
1138
|
+
}
|
|
1139
|
+
function toggleAllVisible(ids) {
|
|
1140
|
+
if (!ids.length) return
|
|
1141
|
+
setSelected(function (prev) {
|
|
1142
|
+
var next = new Set(prev)
|
|
1143
|
+
var all = ids.every(function (id) { return next.has(id) })
|
|
1144
|
+
ids.forEach(function (id) { if (all) next.delete(id); else next.add(id) })
|
|
1145
|
+
return next
|
|
1146
|
+
})
|
|
1147
|
+
}
|
|
1148
|
+
function doUnarchiveBatch() {
|
|
1149
|
+
var ids = Array.from(selected)
|
|
1150
|
+
if (!ids.length) return
|
|
1151
|
+
setBusy(true)
|
|
1152
|
+
apiCall('history-unarchive-batch', { target: { scope: 'sessions', sessionIds: ids } }).then(function (r) {
|
|
1153
|
+
setBusy(false)
|
|
1154
|
+
if (r && r.ok) {
|
|
1155
|
+
var gone = new Set(r.unarchivedSessionIds || [])
|
|
1156
|
+
setSelected(function (prev) { var next = new Set(prev); gone.forEach(function (id) { next.delete(id) }); return next })
|
|
1157
|
+
refresh()
|
|
1158
|
+
} else setModal({ type: 'error', message: (r && r.error) || '恢复失败' })
|
|
1159
|
+
})
|
|
1160
|
+
}
|
|
1161
|
+
function doDeleteBatch() {
|
|
1162
|
+
var ids = Array.from(selected)
|
|
1163
|
+
if (!ids.length) return
|
|
1164
|
+
setBusy(true)
|
|
1165
|
+
apiCall('history-delete-batch', { target: { scope: 'sessions', sessionIds: ids } }).then(function (r) {
|
|
1166
|
+
setBusy(false)
|
|
1167
|
+
if (r && r.ok) { setModal(null); setSelected(new Set()); refresh() }
|
|
1168
|
+
else setModal({ type: 'error', message: (r && r.error) || '删除失败' })
|
|
1169
|
+
})
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// ---------- 导入对话(从其他 Agent)----------
|
|
1173
|
+
function doImportFile(file) {
|
|
1174
|
+
if (!file || !imp) return
|
|
1175
|
+
var reader = new FileReader()
|
|
1176
|
+
reader.onload = function () {
|
|
1177
|
+
var snap = imp
|
|
1178
|
+
setImp(Object.assign({}, imp, { busy: true, error: null, result: null }))
|
|
1179
|
+
apiCall('history-import', {
|
|
1180
|
+
fileName: String(file.name || 'transcript.txt'),
|
|
1181
|
+
content: String(reader.result || ''),
|
|
1182
|
+
cwd: String(snap.cwd || '').trim(),
|
|
1183
|
+
}).then(function (res) {
|
|
1184
|
+
if (res && res.ok) {
|
|
1185
|
+
setImp(Object.assign({}, snap, { busy: false, result: res }))
|
|
1186
|
+
refresh()
|
|
1187
|
+
} else setImp(Object.assign({}, snap, { busy: false, error: (res && res.error) || '导入失败' }))
|
|
1188
|
+
}).catch(function () {
|
|
1189
|
+
setImp(Object.assign({}, snap, { busy: false, error: '导入失败' }))
|
|
1190
|
+
})
|
|
1191
|
+
}
|
|
1192
|
+
reader.readAsText(file)
|
|
1193
|
+
}
|
|
1194
|
+
function onImportDrop(e) {
|
|
1195
|
+
if (e && e.preventDefault) e.preventDefault()
|
|
1196
|
+
if (imp && imp.busy) return
|
|
1197
|
+
droppedFiles(e && e.dataTransfer).then(function (files) {
|
|
1198
|
+
if (files && files.length) doImportFile(files[0])
|
|
1199
|
+
}).catch(function () {})
|
|
1200
|
+
}
|
|
1201
|
+
function openImportPicker() {
|
|
1202
|
+
setImp({ busy: false, error: null, result: null, cwd: '' })
|
|
1203
|
+
}
|
|
1204
|
+
// ---------- 导出对话:弹窗内选范围/工作区 → 勾选会话 → 写入指定目录 ----------
|
|
1205
|
+
function openExportPicker() {
|
|
1206
|
+
setExp({ busy: false, error: null, result: null, outDir: '', format: 'markdown', wsFilter: '', archFilter: 'all', items: [], loading: true, selectedIds: new Set() })
|
|
1207
|
+
// 拉取全部持久化会话(含未归档/冷会话),供选择导出。
|
|
1208
|
+
apiCall('history-sessions', {}).then(function (res) {
|
|
1209
|
+
setExp(function (prev) {
|
|
1210
|
+
if (!prev) return prev
|
|
1211
|
+
return Object.assign({}, prev, {
|
|
1212
|
+
loading: false,
|
|
1213
|
+
error: (res && res.ok) ? prev.error : ((res && res.error) || '加载会话列表失败'),
|
|
1214
|
+
items: (res && res.ok && Array.isArray(res.items)) ? res.items : prev.items,
|
|
1215
|
+
})
|
|
1216
|
+
})
|
|
1217
|
+
}).catch(function () {
|
|
1218
|
+
setExp(function (prev) { return prev ? Object.assign({}, prev, { loading: false, error: '加载会话列表失败' }) : prev })
|
|
1219
|
+
})
|
|
1220
|
+
// 拉取默认导出目录(桌面),仅当用户尚未输入时填充。
|
|
1221
|
+
apiCall('history-export-defaults', {}).then(function (res) {
|
|
1222
|
+
setExp(function (prev) {
|
|
1223
|
+
if (!prev || prev.outDir) return prev
|
|
1224
|
+
if (res && res.ok && typeof res.defaultDir === 'string' && res.defaultDir) {
|
|
1225
|
+
return Object.assign({}, prev, { outDir: res.defaultDir })
|
|
1226
|
+
}
|
|
1227
|
+
return prev
|
|
1228
|
+
})
|
|
1229
|
+
}).catch(function () {})
|
|
1230
|
+
}
|
|
1231
|
+
// 弹出目录选择器:选中后把导出目录回填到输入框。
|
|
1232
|
+
function openExportDir() {
|
|
1233
|
+
if (exp.busy) return
|
|
1234
|
+
setExp(Object.assign({}, exp, { pickingDir: true }))
|
|
1235
|
+
}
|
|
1236
|
+
function toggleExportSelect(id) {
|
|
1237
|
+
setExp(function (prev) {
|
|
1238
|
+
var next = new Set(prev.selectedIds)
|
|
1239
|
+
if (next.has(id)) next.delete(id); else next.add(id)
|
|
1240
|
+
return Object.assign({}, prev, { selectedIds: next })
|
|
1241
|
+
})
|
|
1242
|
+
}
|
|
1243
|
+
function doExport() {
|
|
1244
|
+
var ids = Array.from(exp.selectedIds)
|
|
1245
|
+
if (!ids.length || !String(exp.outDir || '').trim()) return
|
|
1246
|
+
setExp(Object.assign({}, exp, { busy: true, error: null, result: null }))
|
|
1247
|
+
apiCall('history-export', { sessionIds: ids, format: exp.format, outDir: String(exp.outDir).trim() }).then(function (res) {
|
|
1248
|
+
if (res && res.ok) setExp(Object.assign({}, exp, { busy: false, result: res }))
|
|
1249
|
+
else setExp(Object.assign({}, exp, { busy: false, error: (res && res.error) || '导出失败' }))
|
|
1250
|
+
}).catch(function () {
|
|
1251
|
+
setExp(Object.assign({}, exp, { busy: false, error: '导出失败' }))
|
|
1252
|
+
})
|
|
1253
|
+
}
|
|
1254
|
+
// 把选中的会话批量归档到 History(纳入保留期管理),随后刷新列表与页面。
|
|
1255
|
+
function doArchiveSelected() {
|
|
1256
|
+
var ids = Array.from(exp.selectedIds)
|
|
1257
|
+
if (!ids.length || exp.busy) return
|
|
1258
|
+
setExp(Object.assign({}, exp, { busy: true, error: null, result: null }))
|
|
1259
|
+
apiCall('history-archive-batch', { sessionIds: ids }).then(function (res) {
|
|
1260
|
+
if (res && res.ok) {
|
|
1261
|
+
refresh()
|
|
1262
|
+
return apiCall('history-sessions', {}).then(function (r2) {
|
|
1263
|
+
setExp(function (prev) {
|
|
1264
|
+
if (!prev) return prev
|
|
1265
|
+
return Object.assign({}, prev, {
|
|
1266
|
+
busy: false,
|
|
1267
|
+
selectedIds: new Set(),
|
|
1268
|
+
result: { archived: (res.archived || []).length },
|
|
1269
|
+
items: (r2 && r2.ok && Array.isArray(r2.items)) ? r2.items : prev.items,
|
|
1270
|
+
})
|
|
1271
|
+
})
|
|
1272
|
+
})
|
|
1273
|
+
}
|
|
1274
|
+
setExp(function (prev) { return prev ? Object.assign({}, prev, { busy: false, error: (res && res.error) || '归档失败' }) : prev })
|
|
1275
|
+
}).catch(function () {
|
|
1276
|
+
setExp(function (prev) { return prev ? Object.assign({}, prev, { busy: false, error: '归档失败' }) : prev })
|
|
1277
|
+
})
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
var RETENTION_OPTS = [
|
|
1281
|
+
{ value: 0, label: '永久保留' },
|
|
1282
|
+
{ value: 7, label: '7 天' },
|
|
1283
|
+
{ value: 30, label: '30 天' },
|
|
1284
|
+
]
|
|
1285
|
+
var EXPORT_FORMAT_OPTS = [
|
|
1286
|
+
{ value: 'markdown', label: 'Markdown' },
|
|
1287
|
+
{ value: 'jsonl', label: 'JSONL' },
|
|
1288
|
+
]
|
|
1289
|
+
var EXPORT_ARCH_OPTS = [
|
|
1290
|
+
{ value: 'all', label: '全部会话' },
|
|
1291
|
+
{ value: 'archived', label: '仅已归档' },
|
|
1292
|
+
{ value: 'live', label: '仅未归档' },
|
|
1293
|
+
{ value: 'missing', label: '仅目录丢失' },
|
|
1294
|
+
]
|
|
1295
|
+
// 导出弹窗内的工作区筛选选项与候选会话(随弹窗状态/数据刷新重算)。
|
|
1296
|
+
var exportWsOptions = [{ value: '', label: '全部工作区' }]
|
|
1297
|
+
if (exp && data.workspaces) {
|
|
1298
|
+
Object.keys(data.workspaces).forEach(function (wid) {
|
|
1299
|
+
exportWsOptions.push({ value: 'ws:' + wid, label: (data.workspaces[wid].title || wid) })
|
|
1300
|
+
})
|
|
1301
|
+
exportWsOptions.push({ value: 'ungrouped', label: '未分组' })
|
|
1302
|
+
}
|
|
1303
|
+
var exportItems = []
|
|
1304
|
+
if (exp) {
|
|
1305
|
+
exportItems = (exp.items || []).filter(function (it) {
|
|
1306
|
+
if (exp.archFilter === 'archived' && !it.archived) return false
|
|
1307
|
+
if (exp.archFilter === 'live' && it.archived) return false
|
|
1308
|
+
if (exp.archFilter === 'missing' && !it.cwdMissing) return false
|
|
1309
|
+
if (!exp.wsFilter) return true
|
|
1310
|
+
if (exp.wsFilter === 'ungrouped') return !it.workspaceId || !data.workspaces[it.workspaceId]
|
|
1311
|
+
var wid = exp.wsFilter.slice(3)
|
|
1312
|
+
return it.workspaceId === wid && !!data.workspaces[wid]
|
|
1313
|
+
})
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function renderRow(it) {
|
|
1317
|
+
return React.createElement('div', { className: 'dsm-hist-row', key: it.sessionId },
|
|
1318
|
+
React.createElement('input', { type: 'checkbox', className: 'dsm-hist-check', checked: selected.has(it.sessionId), onChange: function () { toggleSelect(it.sessionId) } }),
|
|
1319
|
+
React.createElement('div', { className: 'dsm-hist-main' },
|
|
1320
|
+
React.createElement('div', { className: 'dsm-hist-title' }, it.title || ('Session ' + shortId(it.sessionId))),
|
|
1321
|
+
it.cwd ? React.createElement('div', { className: 'dsm-hist-cwd', title: it.cwd }, it.cwd) : null),
|
|
1322
|
+
React.createElement('div', { className: 'dsm-hist-actions' },
|
|
1323
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { doUnarchive(it.sessionId) } }, '恢复'),
|
|
1324
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { setModal({ type: 'delete', sessionId: it.sessionId, title: it.title || ('Session ' + shortId(it.sessionId)) }) } }, '永久删除')))
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// 分组卡片:dsm-source 卡片 + 可折叠表头(与 MCP / Skills 页一致的源卡片语言)。
|
|
1328
|
+
function toggleGroupCollapse(key) {
|
|
1329
|
+
setCollapsed(function (prev) {
|
|
1330
|
+
var next = Object.assign({}, prev)
|
|
1331
|
+
next[key] = !next[key]
|
|
1332
|
+
return next
|
|
1333
|
+
})
|
|
1334
|
+
}
|
|
1335
|
+
function renderGroup(g) {
|
|
1336
|
+
var groupIds = g.items.map(function (it) { return it.sessionId })
|
|
1337
|
+
var allChecked = groupIds.length > 0 && groupIds.every(function (id) { return selected.has(id) })
|
|
1338
|
+
var open = !collapsed[g.key]
|
|
1339
|
+
return React.createElement('div', { className: 'dsm-source', key: 'g:' + g.key },
|
|
1340
|
+
React.createElement('div', { className: 'dsm-source-head dsm-hist-group-head' },
|
|
1341
|
+
React.createElement('input', { type: 'checkbox', className: 'dsm-hist-group-check', checked: allChecked, onChange: function () { toggleGroup(groupIds) }, 'aria-label': '全选组 ' + g.title }),
|
|
1342
|
+
React.createElement('button', { type: 'button', className: 'dsm-source-head-main', 'aria-expanded': open, onClick: function () { toggleGroupCollapse(g.key) } },
|
|
1343
|
+
React.createElement('span', { className: 'dsm-source-title dsm-hist-group-title', title: g.title }, g.title),
|
|
1344
|
+
React.createElement('span', { className: 'dsm-count' }, g.items.length + ' 个')),
|
|
1345
|
+
g.path ? React.createElement('span', { className: 'dsm-path', title: g.path }, g.path) : null),
|
|
1346
|
+
open ? React.createElement('div', { className: 'dsm-source-body' }, g.items.map(renderRow)) : null)
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
return React.createElement('section', { className: 'dsm-section' },
|
|
1350
|
+
React.createElement('div', { className: 'dsm-head' },
|
|
1351
|
+
React.createElement('div', { className: 'dsm-title-block' },
|
|
1352
|
+
React.createElement('div', { className: 'dsm-title-row' },
|
|
1353
|
+
React.createElement('h2', { className: 'dsm-title' }, 'History'),
|
|
1354
|
+
React.createElement(VersionBadge, null)),
|
|
1355
|
+
React.createElement('p', { className: 'dsm-desc' }, '管理已归档会话:恢复、永久删除或批量操作;归档满保留期后自动清理。')),
|
|
1356
|
+
React.createElement('div', { className: 'dsm-actions' },
|
|
1357
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: !filtered.length, onClick: function () { toggleAllVisible(visibleIds) } }, allVisible ? '取消全选' : '全选'),
|
|
1358
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openImportPicker }, '导入对话'),
|
|
1359
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: openExportPicker }, '导出对话'),
|
|
1360
|
+
selected.size > 0 ? React.createElement('div', { className: 'dsm-hist-batch' },
|
|
1361
|
+
React.createElement('span', { className: 'dsm-hist-batch-count' }, '已选 ' + selected.size + ' 项'),
|
|
1362
|
+
React.createElement('div', { className: 'dsm-hist-batch-actions' },
|
|
1363
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: busy, onClick: function () { doUnarchiveBatch() } }, '恢复所选 (' + selected.size + ')'),
|
|
1364
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { setModal({ type: 'deleteBatch', count: selected.size }) } }, '删除所选 (' + selected.size + ')'))) : null)),
|
|
1365
|
+
React.createElement('div', { className: 'dsm-summary dsm-summary-4' },
|
|
1366
|
+
[[data.items.length, '个归档'], [grouped ? groups.length : '—', '个项目'], [data.retentionDays === 0 ? '永久' : data.retentionDays + ' 天', '保留期'], [selected.size, '个已选']].map(function (item) {
|
|
1367
|
+
return React.createElement('div', { key: item[1], className: 'dsm-stat' },
|
|
1368
|
+
React.createElement('strong', null, item[0]), item[1])
|
|
1369
|
+
})),
|
|
1370
|
+
React.createElement('div', { className: 'dsm-filters' },
|
|
1371
|
+
React.createElement('input', { className: 'dsm-control dsm-search', type: 'text', placeholder: '搜索标题 / 会话 ID / 项目路径', value: query, onChange: function (e) { setQuery(e.target.value) } }),
|
|
1372
|
+
React.createElement('div', { className: 'dsm-source-filter' },
|
|
1373
|
+
React.createElement(SourceSelect, {
|
|
1374
|
+
options: RETENTION_OPTS, value: data.retentionDays, onChange: function (v) { setRetention(Number(v)) },
|
|
1375
|
+
}))),
|
|
1376
|
+
data.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(data.error)) : null,
|
|
1377
|
+
data.loading ? React.createElement('div', { className: 'dsm-empty' }, '加载中…')
|
|
1378
|
+
: filtered.length === 0 ? React.createElement('div', { className: 'dsm-empty' }, query ? '无匹配的归档会话' : '暂无归档会话')
|
|
1379
|
+
: React.createElement('div', { className: 'dsm-sources' },
|
|
1380
|
+
grouped ? groups.map(renderGroup) : React.createElement('div', { className: 'dsm-source' }, filtered.map(renderRow))),
|
|
1381
|
+
modal && modal.type === 'delete' ? React.createElement(Modal, { key: 'del', title: '永久删除?', closeLabel: '取消', onClose: function () { setModal(null) } },
|
|
1382
|
+
React.createElement('p', { className: 'dsm-help' }, '将永久删除会话「' + modal.title + '」及其全部记录,此操作不可撤销。'),
|
|
1383
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1384
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { setModal(null) } }, '取消'),
|
|
1385
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { doDelete(modal.sessionId) } }, '确认删除'))) : null,
|
|
1386
|
+
modal && modal.type === 'deleteBatch' ? React.createElement(Modal, { key: 'delb', title: '永久删除?', closeLabel: '取消', onClose: function () { setModal(null) } },
|
|
1387
|
+
React.createElement('p', { className: 'dsm-help' }, '将永久删除所选 ' + modal.count + ' 个会话及其全部记录,此操作不可撤销。'),
|
|
1388
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1389
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: busy, onClick: function () { setModal(null) } }, '取消'),
|
|
1390
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-danger', disabled: busy, onClick: function () { doDeleteBatch() } }, '确认删除'))) : null,
|
|
1391
|
+
imp ? React.createElement(Modal, { key: 'imp', className: 'dsm-modal-import', title: '导入对话', closeLabel: '关闭', onClose: function () { setImp(null) } },
|
|
1392
|
+
React.createElement('p', { className: 'dsm-help' }, '从其他 Agent 导入对话记录,生成可在 DSH 中继续对话的新会话。支持 Claude Code / Cursor JSONL、Codex Markdown 与通用文本。'),
|
|
1393
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
1394
|
+
React.createElement('label', { className: 'dsm-label' }, '项目目录(可留空)'),
|
|
1395
|
+
React.createElement('input', { className: 'dsm-control', type: 'text', placeholder: '绝对路径,可留空(会话归入未分组)', value: imp.cwd || '', onChange: function (e) { setImp(Object.assign({}, imp, { cwd: e.target.value })) } })),
|
|
1396
|
+
React.createElement('div', { className: 'dsm-dropzone', onDragOver: function (e) { if (e.preventDefault) e.preventDefault() }, onDrop: onImportDrop, onClick: function () { if (importInputRef.current) importInputRef.current.click() } },
|
|
1397
|
+
React.createElement('div', { className: 'dsm-dropzone-title' }, '选择或拖入对话文件'),
|
|
1398
|
+
React.createElement('div', { className: 'dsm-dropzone-copy' }, '.jsonl / .json / .md / .txt')),
|
|
1399
|
+
React.createElement('input', { type: 'file', ref: importInputRef, className: 'dsm-hidden-input', accept: '.jsonl,.json,.md,.markdown,.txt', onChange: function (e) { var f = e.target && e.target.files && e.target.files[0]; if (f) doImportFile(f) } }),
|
|
1400
|
+
imp.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(imp.error)) : null,
|
|
1401
|
+
imp.result ? React.createElement('div', { className: 'dsm-feedback' }, '已创建会话 ' + imp.result.sessionId + '(' + imp.result.count + ' 条消息),可在 DSH 会话列表中继续对话。') : null) : null,
|
|
1402
|
+
exp ? React.createElement(Modal, { key: 'exp', className: 'dsm-modal-import', title: '导出对话', closeLabel: '关闭', onClose: function () { setExp(null) } },
|
|
1403
|
+
React.createElement('p', { className: 'dsm-help' }, '把选中的会话导出为转录文件,写入指定目录;文件可在「导入对话」中重新导入。'),
|
|
1404
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
1405
|
+
React.createElement('label', { className: 'dsm-label' }, '导出目录(绝对路径,自动创建)'),
|
|
1406
|
+
React.createElement('div', { className: 'dsm-dir-row' },
|
|
1407
|
+
React.createElement('input', { className: 'dsm-control', type: 'text', placeholder: 'D:\\backups\\dsh\\exports', value: exp.outDir || '', onChange: function (e) { setExp(Object.assign({}, exp, { outDir: e.target.value })) } }),
|
|
1408
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', disabled: exp.busy, onClick: openExportDir, title: '浏览并选择文件夹' }, '选择')),
|
|
1409
|
+
exp.pickingDir ? React.createElement(DirPickerModal, { key: 'exp-dir-picker', title: '选择导出目录', initial: String(exp.outDir || ''), closeLabel: '关闭', onClose: function () { setExp(Object.assign({}, exp, { pickingDir: false })) }, onPick: function (path) { setExp(Object.assign({}, exp, { outDir: path, pickingDir: false })) } }) : null),
|
|
1410
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
1411
|
+
React.createElement('label', { className: 'dsm-label' }, '格式'),
|
|
1412
|
+
React.createElement(SourceSelect, { options: EXPORT_FORMAT_OPTS, value: exp.format, onChange: function (v) { setExp(Object.assign({}, exp, { format: v })) } })),
|
|
1413
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
1414
|
+
React.createElement('label', { className: 'dsm-label' }, '会话范围'),
|
|
1415
|
+
React.createElement(SourceSelect, { options: EXPORT_ARCH_OPTS, value: exp.archFilter, onChange: function (v) { setExp(Object.assign({}, exp, { archFilter: v, selectedIds: new Set() })) } })),
|
|
1416
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
1417
|
+
React.createElement('label', { className: 'dsm-label' }, '工作区'),
|
|
1418
|
+
React.createElement(SourceSelect, { options: exportWsOptions, value: exp.wsFilter, onChange: function (v) { setExp(Object.assign({}, exp, { wsFilter: v, selectedIds: new Set() })) } })),
|
|
1419
|
+
React.createElement('div', { className: 'dsm-field' },
|
|
1420
|
+
React.createElement('div', { className: 'dsm-label' }, '会话(' + exportItems.length + ' 个)'),
|
|
1421
|
+
React.createElement('div', { className: 'dsm-source' },
|
|
1422
|
+
exp.loading ? React.createElement('div', { className: 'dsm-empty' }, '加载会话列表…')
|
|
1423
|
+
: exportItems.length ? exportItems.map(function (it) {
|
|
1424
|
+
return React.createElement('label', { key: it.sessionId, className: 'dsm-hist-row' },
|
|
1425
|
+
React.createElement('input', { type: 'checkbox', className: 'dsm-hist-check', checked: exp.selectedIds.has(it.sessionId), onChange: function () { toggleExportSelect(it.sessionId) } }),
|
|
1426
|
+
React.createElement('div', { className: 'dsm-hist-main' },
|
|
1427
|
+
React.createElement('div', { className: 'dsm-hist-title' }, it.title || ('Session ' + shortId(it.sessionId))),
|
|
1428
|
+
it.cwd ? React.createElement('div', { className: 'dsm-hist-cwd' + (it.cwdMissing ? ' dsm-hist-cwd-missing' : ''), title: (it.cwdMissing ? '⚠ 工作区目录已不存在:' : '') + it.cwd }, (it.cwdMissing ? '⚠ ' : '') + it.cwd) : null),
|
|
1429
|
+
React.createElement('div', { className: 'dsm-hist-actions' },
|
|
1430
|
+
React.createElement('span', { className: 'dsm-tag' + (it.archived ? '' : ' dsm-tag-on') }, it.archived ? '已归档' : '未归档')))
|
|
1431
|
+
}) : React.createElement('div', { className: 'dsm-empty' }, '没有符合条件的会话'))),
|
|
1432
|
+
exp.error ? React.createElement('div', { className: 'dsm-feedback dsm-error' }, String(exp.error)) : null,
|
|
1433
|
+
exp.result ? React.createElement('div', { className: 'dsm-feedback' },
|
|
1434
|
+
exp.result.exported
|
|
1435
|
+
? ('已导出 ' + (exp.result.exported || []).length + ' 个会话到 ' + String(exp.outDir || '').trim()
|
|
1436
|
+
+ ((exp.result.skipped && exp.result.skipped.length) ? ';跳过 ' + exp.result.skipped.length + ' 个(' + exp.result.skipped.map(function (s) { return s.sessionId }).join('、') + ')' : ''))
|
|
1437
|
+
: ('已归档 ' + exp.result.archived + ' 个会话到 History,可到 History 页继续管理')) : null,
|
|
1438
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1439
|
+
React.createElement('span', { className: 'dsm-hist-batch-count' }, '已选 ' + exp.selectedIds.size + ' 个'),
|
|
1440
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-quiet', disabled: exp.busy || !exp.selectedIds.size, onClick: doArchiveSelected, title: '把选中的会话收进 History,纳入保留期管理' }, '归档所选'),
|
|
1441
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn', disabled: exp.busy || !exp.selectedIds.size || !String(exp.outDir || '').trim(), onClick: doExport }, exp.busy ? '处理中…' : '导出 ' + exp.selectedIds.size + ' 个会话'))) : null,
|
|
1442
|
+
modal && modal.type === 'error' ? React.createElement(Modal, { key: 'err', title: '操作失败', closeLabel: '关闭', onClose: function () { setModal(null) } },
|
|
1443
|
+
React.createElement('p', { className: 'dsm-help' }, String(modal.message || '未知错误')),
|
|
1444
|
+
React.createElement('div', { className: 'dsm-modal-actions' },
|
|
1445
|
+
React.createElement('button', { type: 'button', className: 'dsm-btn dsm-btn-secondary', onClick: function () { setModal(null) } }, '关闭'))) : null)
|
|
1446
|
+
}
|
|
1447
|
+
|
|
845
1448
|
module.exports.DICT = DICT
|
|
1449
|
+
// Test-only export: page 组件 + i18n t,让 client 测试直接渲染 page(绕开
|
|
1450
|
+
// ToolsSection 的子组件嵌套——minimal React 不支持子组件 hook 隔离)。
|
|
1451
|
+
// 生产环境浏览器 React 会正确递归渲染 ToolsSection 内的 page。
|
|
1452
|
+
module.exports._pages = { MCPPage: MCPPage, SkillManagerSection: SkillManagerSection, AgentsMdPage: AgentsMdPage, HistoryPage: HistoryPage, t: t }
|
|
846
1453
|
},
|
|
847
1454
|
}
|
|
848
1455
|
return module.exports
|