From d6d5d3b0c0e268e8c52232221df58f1c5362a9f1 Mon Sep 17 00:00:00 2001 From: Aisland Date: Wed, 2 Sep 2026 02:03:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(annotate):=20=E2=91=A0=E6=8A=98=E5=8F=A0/?= =?UTF-8?q?=E6=A0=87=E6=B3=A8=E6=8C=89=E9=92=AE=E5=B7=A6=E7=A7=BB10px?= =?UTF-8?q?=E9=81=BF=E8=AE=A9=E6=BB=9A=E5=8A=A8=E6=9D=A1=20=E2=91=A1?= =?UTF-8?q?=E6=A0=87=E6=B3=A8=E5=9B=BE=E6=A0=87=E5=8E=BB=E5=8A=A0=E5=8F=B7?= =?UTF-8?q?(=E7=BA=AF=E5=AF=B9=E8=AF=9D=E6=A1=86)=20=E2=91=A2=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E5=BC=B9=E5=87=BA=E5=8D=B3=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=85=89=E6=A0=87(=E6=89=93=E5=AD=97?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E6=97=A0=E5=85=89=E6=A0=87)=20=E2=91=A3?= =?UTF-8?q?=E6=A0=87=E9=A2=98=E6=94=B9=E2=80=9C=E7=82=B9=E5=87=BB=E6=88=96?= =?UTF-8?q?=E6=8B=96=E5=8A=A8=E2=80=9D+=E9=A6=96=E6=AC=A1=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=8F=B3=E4=BE=A7=E6=B0=94=E6=B3=A1=E6=8F=90=E7=A4=BA?= =?UTF-8?q?(=E4=BB=85=E4=B8=80=E6=AC=A1)=20=E2=91=A4=E8=BF=9E=E7=BB=AD?= =?UTF-8?q?=E6=A0=87=E6=B3=A8=E6=94=B9=E4=B8=BA=E8=BF=BD=E5=8A=A0=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F(=E4=B8=8D=E9=A1=B6=E6=8E=89=E4=B8=8A=E4=B8=80?= =?UTF-8?q?=E5=A4=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 01_content/lib/client.js | 44 ++++++++++++++++++++++++-------- 01_content/src/client/locales.ts | 6 +++-- 01_content/src/client/split.tsx | 22 +++++++++++++--- 01_content/src/client/styles.ts | 6 +++-- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/01_content/lib/client.js b/01_content/lib/client.js index ef5f918..d6676fe 100644 --- a/01_content/lib/client.js +++ b/01_content/lib/client.js @@ -7898,10 +7898,10 @@ var css = xterm_default + "\n" + [ ".dsh-wt_paneBar{padding-right:26px}", ".dsh-wt_paneTitle{flex:1;min-width:0;font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary,#9aa4b2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}", // 窗格折叠按钮:常态只是一个折角小标签(无底色无描边),hover 才显示按钮范围(底+描边) - ".dsh-wt_collapseBtn{position:absolute;top:2px;right:4px;z-index:2;width:18px;height:18px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#6e7683);cursor:pointer}", + ".dsh-wt_collapseBtn{position:absolute;top:2px;right:14px;z-index:2;width:18px;height:18px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#6e7683);cursor:pointer}", ".dsh-wt_collapseBtn:hover{color:var(--dsw-alias-label-primary,#e6e8eb);background:var(--dsw-alias-fill-l2,rgba(255,255,255,.09));border-color:var(--dsw-alias-border-l1,#262b36)}", ".dsh-wt_collapseBtn svg{width:12px;height:12px;display:block}", - ".dsh-wt_annotBtn{position:absolute;top:2px;right:26px;z-index:2;width:18px;height:18px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#6e7683);cursor:pointer}", + ".dsh-wt_annotBtn{position:absolute;top:2px;right:36px;z-index:2;width:18px;height:18px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--dsw-alias-label-tertiary,#6e7683);cursor:pointer}", ".dsh-wt_annotBtn:hover{color:var(--dsw-alias-label-primary,#e6e8eb);background:var(--dsw-alias-fill-l2,rgba(255,255,255,.09));border-color:var(--dsw-alias-border-l1,#262b36)}", ".dsh-wt_annotBtn svg{width:12px;height:12px;display:block}", ".dsh-wt-annotating,.dsh-wt-annotating *{cursor:none!important}", @@ -7909,6 +7909,8 @@ var css = xterm_default + "\n" + [ ".dsh-wt_annotBubble{position:fixed;z-index:5000;left:0;top:0;transform:translate(-50%,-100%) translateY(-4px);pointer-events:none;width:30px;height:22px;border-radius:9px;background:#4f8ef7;box-shadow:0 2px 10px rgba(79,142,247,.5);display:flex;align-items:center;justify-content:center}", '.dsh-wt_annotBubble::after{content:"";position:absolute;left:50%;bottom:-5px;transform:translateX(-50%);border:6px solid #4f8ef7;border-top-width:5px;border-bottom-width:0;border-left-color:transparent;border-right-color:transparent}', ".dsh-wt_annotSel{position:fixed;z-index:4999;border:1.5px solid #4f8ef7;background:rgba(79,142,247,.16);box-shadow:0 0 0 1px rgba(79,142,247,.25);pointer-events:none}", + ".dsh-wt_annotHint{position:fixed;right:18px;bottom:56px;z-index:5002;max-width:320px;padding:9px 12px;border-radius:10px;background:rgba(16,20,28,.96);border:1px solid rgba(122,92,255,.45);box-shadow:0 8px 24px rgba(0,0,0,.5);color:#e6e8eb;font-size:12px;line-height:18px;pointer-events:none;animation:annotHintIn .25s ease-out}", + "@keyframes annotHintIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}", ".dsh-wt_annotEditor{position:fixed;z-index:5001;width:280px;box-sizing:border-box;display:flex;flex-direction:column;gap:6px;padding:8px;border-radius:10px;background:rgba(16,20,28,.96);border:1px solid rgba(255,255,255,.2);box-shadow:0 10px 30px rgba(0,0,0,.5)}", ".dsh-wt_annotInput{width:100%;box-sizing:border-box;height:56px;padding:6px 8px;border:1px solid rgba(255,255,255,.18);border-radius:7px;background:rgba(255,255,255,.05);color:#e6e8eb;font:inherit;font-size:12px;line-height:17px;resize:none;outline:none;cursor:text}", ".dsh-wt_annotInput:focus{border-color:#4f8ef7}", @@ -8572,7 +8574,8 @@ var zh = { "pane.termFail": "\u7EC8\u7AEF\u4E0D\u53EF\u7528\uFF08\u5BBF\u4E3B\u7F3A\u5C11 node-pty/ws \u6216\u8FDE\u63A5\u5931\u8D25\uFF09", "pane.closeTab": "\u5173\u95ED\u6807\u7B7E\u9875", "pane.collapse": "\u6298\u53E0\u7A97\u683C", - "annot.label": "\u6807\u6CE8\uFF1A\u70B9\u51FB\u7A97\u53E3\u4E2D\u7684\u4F4D\u7F6E\u63D0\u8981\u6C42", + "annot.label": "\u6807\u6CE8\uFF1A\u70B9\u51FB\u6216\u62D6\u52A8\u7A97\u53E3\u4E2D\u7684\u4F4D\u7F6E\u63D0\u8981\u6C42", + "annot.hint": "\u70B9\u51FB\u6216\u62D6\u52A8\u6846\u9009\u7A97\u53E3\u4E2D\u7684\u4EFB\u610F\u4F4D\u7F6E\uFF0C\u518D\u8F93\u5165\u4F60\u7684\u8981\u6C42\uFF1B\u8FDE\u7EED\u6807\u6CE8\u4F1A\u4F9D\u6B21\u8FFD\u52A0\u5230\u8F93\u5165\u6846", "annot.placeholder": "\u5BF9\u8FD9\u91CC\u63D0\u8981\u6C42\u2026\uFF08Enter \u786E\u8BA4\uFF0CShift+Enter \u6362\u884C\uFF0CEsc \u53D6\u6D88\uFF09", "annot.ok": "\u786E\u8BA4\uFF1A\u6CE8\u5165\u5BF9\u8BDD\u6846\u8F93\u5165\u6846\uFF08\u4E0D\u53D1\u9001\uFF09", "annot.cancel": "\u53D6\u6D88", @@ -8790,7 +8793,8 @@ var en = { "pane.termFail": "Terminal unavailable (host missing node-pty/ws, or connection failed)", "pane.closeTab": "Close tab", "pane.collapse": "Collapse pane", - "annot.label": "Annotate: click a spot in the window", + "annot.label": "Annotate: click or drag a spot in the window", + "annot.hint": "Click or drag to select a spot in any window, then type your request; consecutive annotations append to the input.", "annot.placeholder": "Describe what to change here\u2026 (Enter to confirm, Shift+Enter newline, Esc cancel)", "annot.ok": "Confirm: put into chat input (not sent)", "annot.cancel": "Cancel", @@ -17047,8 +17051,9 @@ function fillHostInput(text2) { const ta = document.querySelector("textarea[data-phase]") ?? Array.from(document.querySelectorAll("textarea")).pop(); if (!ta) return false; const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set; - if (setter) setter.call(ta, text2); - else ta.value = text2; + const next = ta.value && ta.value.trim() ? ta.value + "\n\n" + text2 : text2; + if (setter) setter.call(ta, next); + else ta.value = next; ta.dispatchEvent(new Event("input", { bubbles: true })); try { ta.focus(); @@ -17145,6 +17150,7 @@ function AnnotationOverlay() { const ctx = ctxOf(e.target); if (ctx) stat += "\uFF0C\u5143\u7D20 " + ctx; } + document.body.classList.remove("dsh-wt-annotating"); const ex = Math.max(8, Math.min(e.clientX + 10, window.innerWidth - 288)); const ey = Math.max(8, Math.min(e.clientY + 14, window.innerHeight - 148)); setAnnot({ started: true, drawing: false, ex, ey, stat }); @@ -17163,7 +17169,28 @@ function AnnotationOverlay() { window.removeEventListener("keydown", onKey, true); }; }, [s.on, s.started]); + const [hintOnce] = (0, import_react.useState)(() => { + try { + return localStorage.getItem("dsh.worktable.annotHint.v1") !== "1"; + } catch { + return false; + } + }); + const [hintVisible, setHintVisible] = (0, import_react.useState)(false); + (0, import_react.useEffect)(() => { + if (!s.on || !hintOnce) return; + setHintVisible(true); + try { + localStorage.setItem("dsh.worktable.annotHint.v1", "1"); + } catch { + } + const t = window.setTimeout(() => setHintVisible(false), 4200); + return () => window.clearTimeout(t); + }, [s.on, hintOnce]); if (!s.on) return null; + { + hintVisible && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-wt_annotUi dsh-wt_annotHint", children: T("annot.hint") }); + } if (s.drawing) { const L = Math.min(s.bx0, s.bx1); const T2 = Math.min(s.by0, s.by1); @@ -19621,10 +19648,7 @@ function WorkspaceLayer(props) { title: T("annot.label"), "aria-label": T("annot.label"), onClick: () => startAnnot(windowLabelOf(row, index)), - children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 16 16", "aria-hidden": true, children: [ - /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 5.6c0-1.2 1-2.2 2.2-2.2h7.6c1.2 0 2.2 1 2.2 2.2v3.6c0 1.2-1 2.2-2.2 2.2H7.5L5 13.6l.3-2.4H4.2c-1.2 0-2.2-1-2.2-2.2z", fill: "none", stroke: "currentColor", strokeWidth: "1.2", strokeLinejoin: "round" }), - /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 5.3v3.4M6.3 7h3.4", fill: "none", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }) - ] }) + children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 5.6c0-1.2 1-2.2 2.2-2.2h7.6c1.2 0 2.2 1 2.2 2.2v3.6c0 1.2-1 2.2-2.2 2.2H7.5L5 13.6l.3-2.4H4.2c-1.2 0-2.2-1-2.2-2.2z", fill: "none", stroke: "currentColor", strokeWidth: "1.2", strokeLinejoin: "round" }) }) } ), /* @__PURE__ */ (0, import_jsx_runtime.jsx)( diff --git a/01_content/src/client/locales.ts b/01_content/src/client/locales.ts index 443302b..8cfceba 100644 --- a/01_content/src/client/locales.ts +++ b/01_content/src/client/locales.ts @@ -182,7 +182,8 @@ export const zh = { 'pane.termFail': '终端不可用(宿主缺少 node-pty/ws 或连接失败)', 'pane.closeTab': '关闭标签页', 'pane.collapse': '折叠窗格', - 'annot.label': '标注:点击窗口中的位置提要求', + 'annot.label': '标注:点击或拖动窗口中的位置提要求', + 'annot.hint': '点击或拖动框选窗口中的任意位置,再输入你的要求;连续标注会依次追加到输入框', 'annot.placeholder': '对这里提要求…(Enter 确认,Shift+Enter 换行,Esc 取消)', 'annot.ok': '确认:注入对话框输入框(不发送)', 'annot.cancel': '取消', @@ -405,7 +406,8 @@ export const en = { 'pane.termFail': 'Terminal unavailable (host missing node-pty/ws, or connection failed)', 'pane.closeTab': 'Close tab', 'pane.collapse': 'Collapse pane', - 'annot.label': 'Annotate: click a spot in the window', + 'annot.label': 'Annotate: click or drag a spot in the window', + 'annot.hint': 'Click or drag to select a spot in any window, then type your request; consecutive annotations append to the input.', 'annot.placeholder': 'Describe what to change here… (Enter to confirm, Shift+Enter newline, Esc cancel)', 'annot.ok': 'Confirm: put into chat input (not sent)', 'annot.cancel': 'Cancel', diff --git a/01_content/src/client/split.tsx b/01_content/src/client/split.tsx index 14f0afc..a3f68a7 100644 --- a/01_content/src/client/split.tsx +++ b/01_content/src/client/split.tsx @@ -505,8 +505,10 @@ function fillHostInput(text: string): boolean { ?? Array.from(document.querySelectorAll('textarea')).pop() if (!ta) return false const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set - if (setter) setter.call(ta, text) - else ta.value = text + // 连续标注:已有内容不顶掉,换行追加(用户可以同一对话连续标注多处) + const next = ta.value && ta.value.trim() ? ta.value + '\n\n' + text : text + if (setter) setter.call(ta, next) + else ta.value = next ta.dispatchEvent(new Event('input', { bubbles: true })) try { ta.focus() @@ -600,6 +602,7 @@ function AnnotationOverlay() { const ctx = ctxOf(e.target) if (ctx) stat += ',元素 ' + ctx } + document.body.classList.remove('dsh-wt-annotating') const ex = Math.max(8, Math.min(e.clientX + 10, window.innerWidth - 288)) const ey = Math.max(8, Math.min(e.clientY + 14, window.innerHeight - 148)) setAnnot({ started: true, drawing: false, ex, ey, stat }) @@ -617,7 +620,20 @@ function AnnotationOverlay() { } // eslint-disable-next-line react-hooks/exhaustive-deps }, [s.on, s.started]) + // 首次进入标注:右侧气泡提示「拖动也可框选」,仅显示一次 + const [hintOnce] = useState(() => { try { return localStorage.getItem('dsh.worktable.annotHint.v1') !== '1' } catch { return false } }) + const [hintVisible, setHintVisible] = useState(false) + useEffect(() => { + if (!s.on || !hintOnce) return + setHintVisible(true) + try { localStorage.setItem('dsh.worktable.annotHint.v1', '1') } catch {} + const t = window.setTimeout(() => setHintVisible(false), 4200) + return () => window.clearTimeout(t) + }, [s.on, hintOnce]) if (!s.on) return null + {hintVisible && ( +
{T('annot.hint')}
+ )} if (s.drawing) { const L = Math.min(s.bx0, s.bx1) const T = Math.min(s.by0, s.by1) @@ -2942,7 +2958,7 @@ function WorkspaceLayer(props: { spec: LayoutSpec; geom: Geom | null; chatW: num aria-label={T('annot.label')} onClick={() => startAnnot(windowLabelOf(row, index))} > - +