@windypro-rourou/dsh-eda 0.1.2 → 0.1.4
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 +13 -9
- package/cordis.patch.yml +4 -4
- package/lib/capabilities.js +1 -1
- package/lib/client.js +543 -452
- package/lib/index.js +477 -73
- package/package.json +1 -1
- package/skill/INDEX.md +2 -0
- package/src/json-gen.js +197 -0
- package/src/nl-to-design.js +140 -5
- package/src/validate.js +24 -9
package/lib/client.js
CHANGED
|
@@ -1,172 +1,205 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-lichuang-eda — browser half
|
|
2
|
+
* dsh-lichuang-eda — browser half (DSH 0.1.x / 0.2 desktop adapted).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - 连接教程(可折叠,默认收起,精简步骤)
|
|
8
|
-
* - 活动监视(主体):当前会话 agent 对官方 API 的实时调用流(动作/耗时/结果)
|
|
9
|
-
* - 离线导出(底部小结):仅兜底,导出可导入的标准版 JSON
|
|
4
|
+
* Design read: a technical side-panel for the shell's overlay layer — dark-first,
|
|
5
|
+
* editorial and restrained, wearing the host's own tokens + UI primitives so it
|
|
6
|
+
* reads as native chrome rather than a bolted-on widget.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
8
|
+
* 0.2 adaptation (the "挡到" fix):
|
|
9
|
+
* - the panel is registered into the `shell.overlay` SLOT (position:absolute,
|
|
10
|
+
* z-index 30) instead of a fixed body overlay with a 9999 z-index — it no
|
|
11
|
+
* longer covers the conversation, other panels, or the desktop chrome;
|
|
12
|
+
* - the entry point is a `conversation.session.header.utilities` pill (0.2's
|
|
13
|
+
* left sidebar is slot-owned); the 0.1.x sidebar DOM entry is kept as a
|
|
14
|
+
* compatibility path and the two never fight;
|
|
15
|
+
* - the panel is drag-resizable (120–760px, persisted) and follows the host
|
|
16
|
+
* theme through `--dsw-alias-*` tokens.
|
|
17
|
+
*
|
|
18
|
+
* Bundle format: `window.__ModuleLoader__.load({id, factory})` (lazy CJS).
|
|
14
19
|
*/
|
|
15
20
|
window.__ModuleLoader__.load({
|
|
16
21
|
id: "@windypro-rourou/dsh-eda",
|
|
17
22
|
factory: (require) => {
|
|
18
23
|
var module = { exports: {} };
|
|
19
24
|
var exports = module.exports;
|
|
25
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
20
26
|
|
|
21
|
-
const
|
|
27
|
+
const React = require("react");
|
|
28
|
+
const { createElement: h, useEffect, useMemo, useRef, useState } = React;
|
|
22
29
|
const { createRoot } = require("react-dom/client");
|
|
23
30
|
|
|
31
|
+
// ── host UI kit (same components the native chrome uses); degrade to our
|
|
32
|
+
// own token-based markup when a build does not ship it. ────────────────
|
|
33
|
+
let P = null;
|
|
34
|
+
try { P = require("@deepseek-ai/dsh-client-ui-primitives"); } catch (e) { P = null; }
|
|
35
|
+
const dsButton = P && P.Button ? P.Button : null;
|
|
36
|
+
const dsPill = P && P.Pill ? P.Pill : null;
|
|
37
|
+
const dsTooltip = P && P.Tooltip ? P.Tooltip : null;
|
|
38
|
+
const dsIcon = (name) => (P && P[name] ? P[name] : null);
|
|
39
|
+
|
|
24
40
|
//#region styles
|
|
41
|
+
/**
|
|
42
|
+
* Token-first styling: every colour comes from `--dsw-alias-*` with a
|
|
43
|
+
* dark-first fallback, so the panel inherits light/dark from the shell.
|
|
44
|
+
* Reused accent = one blue; no gradients, no heavy shadows (taste rule:
|
|
45
|
+
* restraint over decoration).
|
|
46
|
+
*/
|
|
25
47
|
const STYLE = `
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
.dsh-eda-root {
|
|
49
|
+
--eda-bg: var(--dsw-alias-bg-layer-1, #151922);
|
|
50
|
+
--eda-bg-base: var(--dsw-alias-bg-base, #0f1115);
|
|
51
|
+
--eda-bg-soft: var(--dsw-alias-bg-layer-2, #1a1f2b);
|
|
52
|
+
--eda-fg: var(--dsw-alias-label-primary, #d7dde6);
|
|
53
|
+
--eda-fg-2: var(--dsw-alias-label-secondary, #b6bdc9);
|
|
54
|
+
--eda-fg-3: var(--dsw-alias-label-tertiary, #8b93a3);
|
|
55
|
+
--eda-line: var(--dsw-alias-border-l1, #262a33);
|
|
56
|
+
--eda-line-soft: var(--dsw-alias-border-l2, rgba(255,255,255,.06));
|
|
57
|
+
--eda-accent: #4c8dff;
|
|
58
|
+
--eda-ok: var(--dsw-alias-state-success-primary, #22c55e);
|
|
59
|
+
--eda-warn: var(--dsw-alias-state-warn-primary, #eab308);
|
|
60
|
+
--eda-err: var(--dsw-alias-state-error-primary, #ef4444);
|
|
61
|
+
--eda-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
62
|
+
--eda-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", system-ui, sans-serif;
|
|
63
|
+
|
|
64
|
+
position: absolute; top: 0; right: 0; bottom: 0;
|
|
65
|
+
width: var(--eda-w, 400px); max-width: 96vw;
|
|
66
|
+
display: flex; flex-direction: column; min-height: 0;
|
|
67
|
+
background: var(--eda-bg); color: var(--eda-fg);
|
|
68
|
+
border-left: 1px solid var(--eda-line);
|
|
69
|
+
font: 13px/1.55 var(--eda-sans);
|
|
70
|
+
font-variant-numeric: tabular-nums;
|
|
71
|
+
z-index: 30;
|
|
72
|
+
box-shadow: -14px 0 40px rgba(0,0,0,.22);
|
|
73
|
+
animation: edaIn .16s cubic-bezier(.22,.61,.36,1);
|
|
30
74
|
}
|
|
75
|
+
.dsh-eda-root[hidden] { display: none !important; }
|
|
76
|
+
@keyframes edaIn { from { transform: translateX(14px); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
77
|
+
@media (prefers-reduced-motion: reduce) { .dsh-eda-root { animation: none; } }
|
|
78
|
+
|
|
79
|
+
.dsh-eda-grip { position: absolute; left: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; z-index: 6; }
|
|
80
|
+
.dsh-eda-grip:hover { background: linear-gradient(90deg, transparent, rgba(76,141,255,.35)); }
|
|
81
|
+
|
|
82
|
+
/* top bar — sticky, quiet, 48px */
|
|
83
|
+
.eda-top { flex: none; height: 48px; display: flex; align-items: center; gap: 10px; padding: 0 12px 0 14px;
|
|
84
|
+
border-bottom: 1px solid var(--eda-line); background: var(--eda-bg); }
|
|
85
|
+
.eda-mark { flex: none; width: 24px; height: 24px; border-radius: 7px; display: grid; place-items: center;
|
|
86
|
+
color: #fff; background: linear-gradient(160deg, #3b7bf6, #2b5fd0); font-size: 11px; font-weight: 700; letter-spacing: .02em; }
|
|
87
|
+
.eda-title { min-width: 0; flex: 1; }
|
|
88
|
+
.eda-title b { display: block; font-size: 13px; font-weight: 600; letter-spacing: .01em; }
|
|
89
|
+
.eda-title span { display: block; font-size: 10.5px; color: var(--eda-fg-3); font-family: var(--eda-mono); }
|
|
90
|
+
.eda-iconbtn { flex: none; width: 26px; height: 26px; display: grid; place-items: center; border-radius: 7px;
|
|
91
|
+
border: 1px solid transparent; background: transparent; color: var(--eda-fg-3); cursor: pointer; font-size: 12px; line-height: 1; }
|
|
92
|
+
.eda-iconbtn:hover { color: var(--eda-fg); background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
93
|
+
|
|
94
|
+
.eda-scroll { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 14px 14px 18px;
|
|
95
|
+
display: flex; flex-direction: column; gap: 12px; scrollbar-width: thin; }
|
|
96
|
+
.eda-scroll::-webkit-scrollbar { width: 9px; }
|
|
97
|
+
.eda-scroll::-webkit-scrollbar-thumb { background: var(--eda-line); border-radius: 9px; border: 2px solid transparent; background-clip: content-box; }
|
|
98
|
+
|
|
99
|
+
.eda-card { border: 1px solid var(--eda-line); border-radius: 10px; background: var(--eda-bg-soft); padding: 12px 13px; }
|
|
100
|
+
.eda-card-title { font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
|
|
101
|
+
color: var(--eda-fg-3); margin: 0 0 10px; }
|
|
102
|
+
.eda-note { font-size: 11.5px; color: var(--eda-fg-2); margin: 9px 0 0; line-height: 1.6; }
|
|
103
|
+
.eda-muted { font-size: 11px; color: var(--eda-fg-3); }
|
|
104
|
+
.eda-mono { font-family: var(--eda-mono); }
|
|
105
|
+
|
|
106
|
+
/* status row */
|
|
107
|
+
.eda-state { display: flex; align-items: center; gap: 9px; }
|
|
108
|
+
.eda-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--eda-fg-3); }
|
|
109
|
+
.eda-dot.on { background: var(--eda-ok); box-shadow: 0 0 0 3px rgba(34,197,94,.16); }
|
|
110
|
+
.eda-dot.off { background: var(--eda-warn); }
|
|
111
|
+
.eda-dot.wait { background: var(--eda-fg-3); animation: edaPulse 1.3s ease-in-out infinite; }
|
|
112
|
+
@keyframes edaPulse { 0%,100% { opacity: .4 } 50% { opacity: 1 } }
|
|
113
|
+
.eda-state b { font-size: 12.5px; font-weight: 600; }
|
|
114
|
+
.eda-state small { font-family: var(--eda-mono); font-size: 10.5px; color: var(--eda-fg-3); margin-left: 7px; }
|
|
115
|
+
|
|
116
|
+
.eda-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
|
|
117
|
+
.eda-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
118
|
+
border: 1px solid var(--eda-line); border-radius: 8px; padding: 5px 11px; background: transparent;
|
|
119
|
+
color: var(--eda-fg); font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
|
|
120
|
+
transition: background .12s ease, border-color .12s ease, opacity .12s ease; }
|
|
121
|
+
.eda-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
122
|
+
.eda-btn:disabled { opacity: .45; cursor: default; }
|
|
123
|
+
.eda-btn.primary { background: var(--eda-accent); border-color: transparent; color: #fff; }
|
|
124
|
+
.eda-btn.primary:hover:not(:disabled) { background: #3f80f5; }
|
|
125
|
+
.eda-btn.big { width: 100%; padding: 8px 12px; font-size: 12.5px; }
|
|
126
|
+
.eda-btn.tiny { padding: 2px 8px; font-size: 10.5px; font-weight: 500; }
|
|
127
|
+
.eda-link { color: var(--eda-accent); text-decoration: none; font-size: 11.5px; }
|
|
128
|
+
.eda-link:hover { text-decoration: underline; }
|
|
129
|
+
|
|
130
|
+
/* connected CTA */
|
|
131
|
+
.eda-cta { display: flex; align-items: center; gap: 10px; margin-top: 11px; padding: 9px 11px;
|
|
132
|
+
border: 1px solid color-mix(in srgb, var(--eda-accent) 34%, transparent); border-radius: 9px;
|
|
133
|
+
background: color-mix(in srgb, var(--eda-accent) 8%, transparent); cursor: pointer; }
|
|
134
|
+
.eda-cta:hover { background: color-mix(in srgb, var(--eda-accent) 13%, transparent); }
|
|
135
|
+
.eda-cta-t { min-width: 0; flex: 1; }
|
|
136
|
+
.eda-cta-t i { display: block; font-style: normal; font-size: 10px; color: var(--eda-fg-3); letter-spacing: .04em; }
|
|
137
|
+
.eda-cta-t b { font-size: 12.5px; font-weight: 600; color: var(--eda-accent); }
|
|
138
|
+
.eda-cta-c { flex: none; font-size: 10.5px; color: var(--eda-fg-3); }
|
|
139
|
+
.eda-cta.ok .eda-cta-c { color: var(--eda-ok); }
|
|
140
|
+
|
|
141
|
+
/* guide */
|
|
142
|
+
.eda-steps { display: flex; flex-direction: column; gap: 10px; }
|
|
143
|
+
.eda-step { display: flex; gap: 10px; }
|
|
144
|
+
.eda-step-no { flex: none; width: 18px; height: 18px; border-radius: 50%; display: grid; place-items: center;
|
|
145
|
+
font-family: var(--eda-mono); font-size: 10px; color: var(--eda-fg-3); border: 1px solid var(--eda-line); margin-top: 1px; }
|
|
146
|
+
.eda-step b { display: block; font-size: 12px; font-weight: 600; }
|
|
147
|
+
.eda-step p { margin: 2px 0 0; font-size: 11px; color: var(--eda-fg-3); line-height: 1.55; }
|
|
148
|
+
|
|
149
|
+
/* timeline */
|
|
150
|
+
.eda-tl-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
|
|
151
|
+
.eda-tl-head .eda-card-title { flex: 1; margin: 0; }
|
|
152
|
+
.eda-select { font: inherit; font-size: 10.5px; font-family: var(--eda-mono); color: var(--eda-fg-2);
|
|
153
|
+
background: var(--eda-bg-base); border: 1px solid var(--eda-line); border-radius: 6px; padding: 2px 5px; max-width: 132px; }
|
|
154
|
+
.eda-tl { position: relative; margin: 6px 0 0; }
|
|
155
|
+
.eda-row { position: relative; display: flex; gap: 10px; padding: 8px 0 8px 2px; cursor: pointer;
|
|
156
|
+
border-bottom: 1px solid var(--eda-line-soft); }
|
|
157
|
+
.eda-row:last-child { border-bottom: 0; }
|
|
158
|
+
.eda-row:hover { background: color-mix(in srgb, var(--eda-fg) 4%, transparent); }
|
|
159
|
+
.eda-row-dot { flex: none; width: 7px; height: 7px; margin-top: 6px; border-radius: 50%; background: var(--eda-fg-3); position: relative; z-index: 1; }
|
|
160
|
+
.eda-row.ok .eda-row-dot { background: var(--eda-ok); }
|
|
161
|
+
.eda-row.err .eda-row-dot { background: var(--eda-err); }
|
|
162
|
+
.eda-row.pend .eda-row-dot { background: var(--eda-warn); animation: edaPulse 1s ease-in-out infinite; }
|
|
163
|
+
.eda-row-body { min-width: 0; flex: 1; }
|
|
164
|
+
.eda-row-line { display: flex; align-items: baseline; gap: 8px; }
|
|
165
|
+
.eda-seq { flex: none; font-family: var(--eda-mono); font-size: 10px; color: var(--eda-fg-3); }
|
|
166
|
+
.eda-row-name { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
167
|
+
.eda-row-meta { flex: none; font-family: var(--eda-mono); font-size: 10px; color: var(--eda-fg-3); }
|
|
168
|
+
.eda-row-meta .eda-pend { color: var(--eda-warn); }
|
|
169
|
+
.eda-row-code, .eda-row-out { margin-top: 3px; font-family: var(--eda-mono); font-size: 10.5px; color: var(--eda-fg-3);
|
|
170
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
171
|
+
.eda-row-out.err { color: var(--eda-err); }
|
|
172
|
+
.eda-row-full { margin-top: 7px; padding: 8px 9px; border-radius: 8px; background: var(--eda-bg-base);
|
|
173
|
+
border: 1px solid var(--eda-line-soft); font-family: var(--eda-mono); font-size: 10.5px; line-height: 1.6;
|
|
174
|
+
color: var(--eda-fg-2); white-space: pre-wrap; word-break: break-word; max-height: 260px; overflow: auto; }
|
|
175
|
+
.eda-row-full i { display: block; font-style: normal; font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase;
|
|
176
|
+
color: var(--eda-fg-3); margin: 6px 0 2px; }
|
|
177
|
+
.eda-row-full i:first-child { margin-top: 0; }
|
|
178
|
+
.eda-empty { font-size: 11.5px; color: var(--eda-fg-3); line-height: 1.6; }
|
|
179
|
+
.eda-hints { margin-top: 9px; display: flex; flex-direction: column; gap: 5px; }
|
|
180
|
+
.eda-hint { display: flex; gap: 7px; font-size: 11px; color: var(--eda-fg-2); }
|
|
181
|
+
.eda-hint s { text-decoration: none; color: var(--eda-fg-3); }
|
|
182
|
+
.eda-tl-foot { margin-top: 7px; font-size: 10.5px; color: var(--eda-fg-3); }
|
|
183
|
+
|
|
184
|
+
.eda-foot { flex: none; padding: 9px 14px; border-top: 1px solid var(--eda-line);
|
|
185
|
+
font-family: var(--eda-mono); font-size: 10px; color: var(--eda-fg-3); text-align: center; }
|
|
186
|
+
|
|
187
|
+
/* header pill (0.2 entry point) */
|
|
188
|
+
.eda-pill { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 9px;
|
|
189
|
+
border: 1px solid var(--eda-line); border-radius: 999px; background: transparent; color: inherit;
|
|
190
|
+
font: inherit; font-size: 11.5px; cursor: pointer; transition: background .12s ease; }
|
|
191
|
+
.eda-pill:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
192
|
+
.eda-pill[data-active="true"] { border-color: color-mix(in srgb, var(--eda-accent) 46%, transparent);
|
|
193
|
+
background: color-mix(in srgb, var(--eda-accent) 12%, transparent); }
|
|
194
|
+
.eda-pill .eda-dot { width: 6px; height: 6px; }
|
|
195
|
+
.eda-pill[hidden] { display: none !important; }
|
|
196
|
+
|
|
197
|
+
/* 0.1.x sidebar entry */
|
|
198
|
+
[data-dsh-eda-entry] { display: flex; align-items: center; gap: 8px; width: 100%; padding: 9px 12px; margin: 2px 0;
|
|
199
|
+
border: 0; border-radius: 8px; background: transparent; color: inherit; font: inherit; cursor: pointer; text-align: left; }
|
|
31
200
|
[data-dsh-eda-entry]:hover { background: rgba(128,128,128,.14); }
|
|
32
201
|
[data-dsh-eda-entry][data-active] { background: rgba(128,128,128,.22); }
|
|
33
|
-
[data-dsh-eda-entry] .eda-entry-icon { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; flex: none; }
|
|
34
202
|
[data-dsh-eda-entry] .eda-entry-label { font-size: 13px; line-height: 1.2; opacity: .92; }
|
|
35
|
-
|
|
36
|
-
.dsh-eda-view { position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 94vw); z-index: 9999; }
|
|
37
|
-
.dsh-eda-view[hidden] { display: none !important; }
|
|
38
|
-
.dsh-eda-panel {
|
|
39
|
-
position: absolute; inset: 0; background: light-dark(#fbfbfc, #121417);
|
|
40
|
-
color: light-dark(#1c1f24, #e3e6ea);
|
|
41
|
-
border-left: 1px solid light-dark(rgba(0,0,0,.09), rgba(255,255,255,.10));
|
|
42
|
-
font: 13px/1.6 -apple-system, "Segoe UI", "Microsoft YaHei", system-ui, sans-serif;
|
|
43
|
-
color-scheme: light dark;
|
|
44
|
-
display: flex; flex-direction: column;
|
|
45
|
-
}
|
|
46
|
-
.eda-panel { display: flex; flex-direction: column; height: 100%; min-height: 0; }
|
|
47
|
-
|
|
48
|
-
.eda-head {
|
|
49
|
-
flex: none; display: flex; align-items: center; gap: 10px;
|
|
50
|
-
padding: 13px 16px; border-bottom: 1px solid light-dark(rgba(0,0,0,.08), rgba(255,255,255,.09));
|
|
51
|
-
}
|
|
52
|
-
.eda-head .eda-mark { width: 26px; height: 26px; border-radius: 6px; flex: none; background: #2563eb; color: #fff; display: grid; place-items: center; font-size: 12px; font-weight: 700; }
|
|
53
|
-
.eda-head .eda-tt { min-width: 0; flex: 1; }
|
|
54
|
-
.eda-head h1 { font-size: 14px; font-weight: 700; margin: 0; line-height: 1.3; }
|
|
55
|
-
.eda-head .eda-sub { font-size: 11px; opacity: .55; margin: 0; }
|
|
56
|
-
.eda-close { border: 0; background: transparent; color: inherit; cursor: pointer; font-size: 15px; line-height: 1; padding: 5px 8px; border-radius: 6px; opacity: .6; }
|
|
57
|
-
.eda-close:hover { opacity: 1; background: light-dark(rgba(0,0,0,.06), rgba(255,255,255,.08)); }
|
|
58
|
-
|
|
59
|
-
.eda-scroll { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 13px 16px 18px; display: flex; flex-direction: column; gap: 12px; }
|
|
60
|
-
|
|
61
|
-
.eda-card { border: 1px solid light-dark(rgba(0,0,0,.09), rgba(255,255,255,.10)); border-radius: 10px; padding: 12px 14px; background: light-dark(#ffffff, #17191d); }
|
|
62
|
-
.eda-card-title { font-size: 12px; font-weight: 600; opacity: .75; margin: 0 0 8px; }
|
|
63
|
-
|
|
64
|
-
.eda-note { font-size: 11.5px; opacity: .62; margin: 8px 0 0; line-height: 1.6; }
|
|
65
|
-
.eda-muted { font-size: 11px; opacity: .5; }
|
|
66
|
-
|
|
67
|
-
.eda-status { display: flex; align-items: center; gap: 8px; }
|
|
68
|
-
.eda-status .eda-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
|
|
69
|
-
.eda-status .eda-dot.on { background: #16a34a; }
|
|
70
|
-
.eda-status .eda-dot.off { background: #d97706; }
|
|
71
|
-
.eda-status .eda-dot.wait { background: #9ca3af; animation: edaPulse 1.2s ease-in-out infinite; }
|
|
72
|
-
.eda-status .eda-txt { font-size: 13px; font-weight: 600; }
|
|
73
|
-
.eda-status .eda-txt small { font-weight: 400; opacity: .55; margin-left: 6px; }
|
|
74
|
-
@keyframes edaPulse { 0%,100% { opacity: .45; } 50% { opacity: 1; } }
|
|
75
|
-
|
|
76
|
-
.eda-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
77
|
-
.eda-btn {
|
|
78
|
-
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
79
|
-
border: 1px solid transparent; border-radius: 8px; padding: 6px 12px;
|
|
80
|
-
font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
|
|
81
|
-
transition: background .12s ease, border-color .12s ease;
|
|
82
|
-
}
|
|
83
|
-
.eda-btn.primary { background: #2563eb; color: #fff; }
|
|
84
|
-
.eda-btn.primary:hover { background: #1d4ed8; }
|
|
85
|
-
.eda-btn.primary:disabled { opacity: .5; cursor: default; }
|
|
86
|
-
.eda-btn.ghost { background: transparent; border-color: light-dark(rgba(0,0,0,.14), rgba(255,255,255,.16)); color: inherit; }
|
|
87
|
-
.eda-btn.ghost:hover { background: light-dark(rgba(0,0,0,.04), rgba(255,255,255,.06)); }
|
|
88
|
-
.eda-btn.ghost:disabled { opacity: .45; cursor: default; }
|
|
89
|
-
.eda-btn.big { width: 100%; padding: 8px 13px; font-size: 13px; }
|
|
90
|
-
.eda-link { color: #2563eb; text-decoration: none; font-size: 12px; }
|
|
91
|
-
.eda-link:hover { text-decoration: underline; }
|
|
92
|
-
|
|
93
|
-
/* connect wizard (collapsed by default, compact) */
|
|
94
|
-
.eda-steps { display: flex; flex-direction: column; gap: 9px; margin-top: 2px; }
|
|
95
|
-
.eda-step { display: flex; gap: 9px; }
|
|
96
|
-
.eda-step .eda-step-no { width: 19px; height: 19px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: 10.5px; font-weight: 700; background: light-dark(#eef2f7, #24272c); opacity: .85; }
|
|
97
|
-
.eda-step .eda-step-body { min-width: 0; flex: 1; }
|
|
98
|
-
.eda-step .eda-step-name { font-size: 12.5px; font-weight: 600; }
|
|
99
|
-
.eda-step .eda-step-hint { font-size: 11px; opacity: .58; margin-top: 1px; line-height: 1.5; }
|
|
100
|
-
.eda-step .eda-step-action { margin-top: 5px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
101
|
-
.eda-step .eda-step-result { font-size: 11.5px; opacity: .7; }
|
|
102
|
-
|
|
103
|
-
/* activity monitor (main) */
|
|
104
|
-
.eda-act { display: flex; flex-direction: column; gap: 0; }
|
|
105
|
-
.eda-act-item { display: flex; gap: 9px; padding: 9px 0; border-bottom: 1px solid light-dark(rgba(0,0,0,.05), rgba(255,255,255,.06)); cursor: pointer; }
|
|
106
|
-
.eda-act-item:hover { background: light-dark(rgba(0,0,0,.03), rgba(255,255,255,.03)); }
|
|
107
|
-
.eda-act-item:last-child { border-bottom: 0; }
|
|
108
|
-
.eda-act-item .eda-act-dot { flex: none; margin-top: 5px; width: 7px; height: 7px; border-radius: 50%; background: #9ca3af; }
|
|
109
|
-
.eda-act-item.ok .eda-act-dot { background: #16a34a; }
|
|
110
|
-
.eda-act-item.err .eda-act-dot { background: #dc2626; }
|
|
111
|
-
.eda-act-item.pend .eda-act-dot { background: #f59e0b; animation: edaPulse 1s ease-in-out infinite; }
|
|
112
|
-
.eda-act-item .eda-act-body { min-width: 0; flex: 1; }
|
|
113
|
-
.eda-act-item .eda-act-line { display: flex; align-items: baseline; gap: 8px; }
|
|
114
|
-
.eda-act-item .eda-act-seq { font-family: ui-monospace, Consolas, monospace; font-size: 10px; opacity: .45; flex: none; }
|
|
115
|
-
.eda-act-item .eda-act-name { font-size: 12.5px; font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
116
|
-
.eda-act-item .eda-act-meta { font-size: 10.5px; opacity: .5; }
|
|
117
|
-
.eda-revoke { padding: 2px 8px !important; font-size: 10px !important; }
|
|
118
|
-
.eda-hints { display: flex; flex-direction: column; gap: 5px; margin-top: 2px; }
|
|
119
|
-
.eda-hint { font-size: 11px; opacity: .68; }
|
|
120
|
-
.eda-act-item .eda-act-meta .eda-pend { color: #f59e0b; opacity: 1; font-weight: 600; }
|
|
121
|
-
.eda-act-item .eda-act-code { font-family: ui-monospace, Consolas, monospace; font-size: 10.5px; opacity: .55; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
122
|
-
.eda-act-item .eda-act-out { font-size: 11px; opacity: .65; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
123
|
-
.eda-act-item .eda-act-out.err { color: #dc2626; opacity: .9; }
|
|
124
|
-
.eda-act-item .eda-act-full {
|
|
125
|
-
white-space: pre-wrap; word-break: break-all; font-family: ui-monospace, Consolas, monospace;
|
|
126
|
-
font-size: 10.5px; line-height: 1.55; opacity: .8; margin-top: 5px; padding: 7px 9px;
|
|
127
|
-
background: light-dark(rgba(0,0,0,.045), rgba(255,255,255,.05)); border-radius: 7px;
|
|
128
|
-
max-height: 240px; overflow: auto;
|
|
129
|
-
}
|
|
130
|
-
.eda-act-item .eda-act-full .eda-act-full-label { font-family: inherit; font-size: 10px; opacity: .5; margin: 0 0 2px; }
|
|
131
|
-
.eda-act-empty { font-size: 11.5px; opacity: .5; padding: 6px 0; }
|
|
132
|
-
|
|
133
|
-
/* activity header row: title + session switcher */
|
|
134
|
-
.eda-act-head { display: flex; align-items: center; gap: 8px; }
|
|
135
|
-
.eda-act-head .eda-card-title { flex: 1; margin: 0; }
|
|
136
|
-
.eda-sess {
|
|
137
|
-
font: inherit; font-size: 10.5px; border: 1px solid light-dark(rgba(0,0,0,.14), rgba(255,255,255,.16));
|
|
138
|
-
border-radius: 6px; background: transparent; color: inherit; padding: 2px 6px; max-width: 130px; cursor: pointer;
|
|
139
|
-
}
|
|
140
|
-
.eda-act-hint { font-size: 10.5px; opacity: .5; margin: 6px 0 0; }
|
|
141
|
-
|
|
142
|
-
/* 醒目对话入口 (connected CTA) */
|
|
143
|
-
.eda-cta {
|
|
144
|
-
display: flex; align-items: center; gap: 10px; margin-top: 10px; padding: 9px 12px;
|
|
145
|
-
border: 1px solid rgba(37,99,235,.35); border-radius: 10px; cursor: pointer;
|
|
146
|
-
background: linear-gradient(180deg, rgba(37,99,235,.10), rgba(37,99,235,.03));
|
|
147
|
-
}
|
|
148
|
-
.eda-cta:hover { background: rgba(37,99,235,.14); }
|
|
149
|
-
.eda-cta .eda-cta-bubble { font-size: 16px; flex: none; }
|
|
150
|
-
.eda-cta .eda-cta-text { min-width: 0; flex: 1; }
|
|
151
|
-
.eda-cta .eda-cta-title { font-size: 10.5px; opacity: .6; }
|
|
152
|
-
.eda-cta .eda-cta-phrase { font-family: inherit; font-size: 12.5px; font-weight: 700; color: #2563eb; }
|
|
153
|
-
.eda-cta .eda-cta-copy { flex: none; font-size: 10.5px; opacity: .55; }
|
|
154
|
-
.eda-cta.ok .eda-cta-copy { color: #16a34a; opacity: 1; }
|
|
155
|
-
|
|
156
|
-
/* offline export (footer, small) */
|
|
157
|
-
.eda-prompt textarea {
|
|
158
|
-
width: 100%; box-sizing: border-box; resize: vertical; min-height: 56px; border-radius: 8px;
|
|
159
|
-
border: 1px solid light-dark(rgba(0,0,0,.14), rgba(255,255,255,.16));
|
|
160
|
-
background: light-dark(#fafafa, #121317); color: inherit; font: inherit; font-size: 12px; line-height: 1.6;
|
|
161
|
-
padding: 8px 10px; outline: none;
|
|
162
|
-
}
|
|
163
|
-
.eda-prompt textarea:focus { border-color: #2563eb; }
|
|
164
|
-
.eda-prompt textarea::placeholder { opacity: .45; }
|
|
165
|
-
.eda-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
|
|
166
|
-
.eda-chip { border: 1px solid light-dark(rgba(0,0,0,.11), rgba(255,255,255,.13)); border-radius: 999px; padding: 3px 9px; font-size: 11px; cursor: pointer; color: inherit; background: transparent; }
|
|
167
|
-
.eda-chip:hover { border-color: rgba(37,99,235,.55); }
|
|
168
|
-
|
|
169
|
-
.eda-foot { font-size: 11px; opacity: .5; text-align: center; margin: 2px 0 0; }
|
|
170
203
|
`;
|
|
171
204
|
//#endregion
|
|
172
205
|
|
|
@@ -174,16 +207,37 @@ window.__ModuleLoader__.load({
|
|
|
174
207
|
const LINKS = {
|
|
175
208
|
editor: "https://pro.lceda.cn/editor",
|
|
176
209
|
ext: "https://jlc-ext.com/item/oshwhub/run-api-gateway",
|
|
177
|
-
skill: "https://github.com/easyeda/easyeda-api-skill",
|
|
178
210
|
};
|
|
179
|
-
|
|
180
|
-
//#region data
|
|
181
211
|
const CONNECT_STEPS = [
|
|
182
|
-
{
|
|
183
|
-
{
|
|
184
|
-
{
|
|
185
|
-
{
|
|
212
|
+
{ n: 1, t: "一键安装官方桥", d: "官方 easyeda-api-skill → 本机 ~/.dsh/eda/bridge/(离线依赖,无需 npm)。" },
|
|
213
|
+
{ n: 2, t: "启动官方桥", d: "Bridge Server 监听 127.0.0.1:49620-49629,插件自动探测。" },
|
|
214
|
+
{ n: 3, t: "网页版装扩展 Run API Gateway", d: "编辑器 →「高级」→「扩展管理器」→ 安装;再开「外部交互」+「显示在顶部菜单」。" },
|
|
215
|
+
{ n: 4, t: "验证", d: "刷新编辑器页面;状态点变绿 = 已连上你的云画板。" },
|
|
216
|
+
];
|
|
217
|
+
const HINTS = [
|
|
218
|
+
"放置任意大类元件(R/C/L/二极管/LED/MCU…)并框内定位",
|
|
219
|
+
"引脚级连线 + VCC/GND 网络标志",
|
|
220
|
+
"原理图 / PCB 的 DRC、网表、BOM 导出",
|
|
221
|
+
"PCB 元件 / 过孔 / 走线(含底层铜)+ 现场截图",
|
|
222
|
+
"随时「紧急保存」到本地并留动作日志",
|
|
186
223
|
];
|
|
224
|
+
const ICON_EDA = "M3 3.6h7.2l2.8 2.8v6h-10z M10.2 3.6v2.8h2.8 M5 8.4h3 M5 10.4h2";
|
|
225
|
+
|
|
226
|
+
//#region store
|
|
227
|
+
/** Tiny external store: panel open/closed, shared by the pill and the overlay. */
|
|
228
|
+
function PanelStore() {
|
|
229
|
+
const listeners = new Set();
|
|
230
|
+
let open = false;
|
|
231
|
+
this.get = () => open;
|
|
232
|
+
this.set = (v) => { const next = !!v; if (next === open) return; open = next; for (const fn of listeners) fn(); };
|
|
233
|
+
this.toggle = () => this.set(!open);
|
|
234
|
+
this.subscribe = (fn) => { listeners.add(fn); return () => listeners.delete(fn); };
|
|
235
|
+
}
|
|
236
|
+
function useStore(store) {
|
|
237
|
+
const [v, setV] = useState(store.get());
|
|
238
|
+
useEffect(() => store.subscribe(() => setV(store.get())), [store]);
|
|
239
|
+
return v;
|
|
240
|
+
}
|
|
187
241
|
//#endregion
|
|
188
242
|
|
|
189
243
|
//#region hooks
|
|
@@ -192,12 +246,10 @@ window.__ModuleLoader__.load({
|
|
|
192
246
|
const [error, setError] = useState(false);
|
|
193
247
|
useEffect(() => {
|
|
194
248
|
let alive = true;
|
|
195
|
-
const load = () =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
.catch(() => { if (alive) { setStatus(null); setError(true); } });
|
|
200
|
-
};
|
|
249
|
+
const load = () => fetch(API_BASE + "/status")
|
|
250
|
+
.then((r) => (r.ok ? r.json() : Promise.reject(r.status)))
|
|
251
|
+
.then((b) => { if (alive) { setStatus(b); setError(false); } })
|
|
252
|
+
.catch(() => { if (alive) { setStatus(null); setError(true); } });
|
|
201
253
|
load();
|
|
202
254
|
const t = setInterval(load, 4000);
|
|
203
255
|
return () => { alive = false; clearInterval(t); };
|
|
@@ -205,14 +257,8 @@ window.__ModuleLoader__.load({
|
|
|
205
257
|
return [status, error];
|
|
206
258
|
}
|
|
207
259
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
* through the official API right now). `pinnedSid` = '' means "follow
|
|
211
|
-
* the freshest session" (the panel's default for 仅当前会话).
|
|
212
|
-
*/
|
|
213
|
-
function useActivity() {
|
|
214
|
-
const [feed, setFeed] = useState({ activities: [], sessions: [], currentSid: '', error: false });
|
|
215
|
-
const [pinnedSid, setPinnedSid] = useState("");
|
|
260
|
+
function useActivity(pinnedSid) {
|
|
261
|
+
const [feed, setFeed] = useState({ activities: [], sessions: [], currentSid: "", error: false });
|
|
216
262
|
useEffect(() => {
|
|
217
263
|
let alive = true;
|
|
218
264
|
const load = () => {
|
|
@@ -221,7 +267,12 @@ window.__ModuleLoader__.load({
|
|
|
221
267
|
.then((r) => (r.ok ? r.json() : Promise.reject(r.status)))
|
|
222
268
|
.then((b) => {
|
|
223
269
|
if (!alive) return;
|
|
224
|
-
setFeed({
|
|
270
|
+
setFeed({
|
|
271
|
+
activities: Array.isArray(b.activities) ? b.activities : [],
|
|
272
|
+
sessions: Array.isArray(b.sessions) ? b.sessions : [],
|
|
273
|
+
currentSid: b.currentSid ?? "",
|
|
274
|
+
error: false,
|
|
275
|
+
});
|
|
225
276
|
})
|
|
226
277
|
.catch(() => { if (alive) setFeed((prev) => ({ ...prev, error: true })); });
|
|
227
278
|
};
|
|
@@ -229,340 +280,385 @@ window.__ModuleLoader__.load({
|
|
|
229
280
|
const t = setInterval(load, 3000);
|
|
230
281
|
return () => { alive = false; clearInterval(t); };
|
|
231
282
|
}, [pinnedSid]);
|
|
232
|
-
return
|
|
283
|
+
return feed;
|
|
233
284
|
}
|
|
234
285
|
//#endregion
|
|
235
286
|
|
|
236
287
|
//#region components
|
|
237
|
-
/**
|
|
238
|
-
function
|
|
288
|
+
/** Brand mark: host icon when available, else a two-stroke chip glyph. */
|
|
289
|
+
function Mark() {
|
|
290
|
+
const Icon = dsIcon("IconBoardOutline16") || dsIcon("IconComponentOutline16");
|
|
291
|
+
if (Icon) return h("span", { className: "eda-mark" }, h(Icon, null));
|
|
292
|
+
return h("span", { className: "eda-mark" }, "EDA");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function SendIcon() {
|
|
296
|
+
return h("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.3, "aria-hidden": "true" },
|
|
297
|
+
h("path", { d: "M2.6 3.4l10.8 4.6-10.8 4.6 1.7-4.6z", strokeLinejoin: "round" }));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Header pill — the 0.2 entry point (slot-owned header utilities). */
|
|
301
|
+
function HeaderPill({ store }) {
|
|
302
|
+
const open = useStore(store);
|
|
303
|
+
const [status] = useStatus();
|
|
304
|
+
const ready = status?.connected === true;
|
|
305
|
+
const cls = "eda-pill";
|
|
306
|
+
const inner = [
|
|
307
|
+
h("span", { className: "eda-dot " + (status === null ? "wait" : ready ? "on" : "off"), key: "d" }),
|
|
308
|
+
h("span", { key: "t" }, "嘉立创 EDA"),
|
|
309
|
+
];
|
|
310
|
+
if (dsButton) {
|
|
311
|
+
return h(dsButton, {
|
|
312
|
+
appearance: "ghost", size: "small", "data-active": open ? "true" : "false",
|
|
313
|
+
onClick: () => store.toggle(), title: "嘉立创 EDA 助手", className: cls,
|
|
314
|
+
}, inner);
|
|
315
|
+
}
|
|
316
|
+
return h("button", {
|
|
317
|
+
type: "button", className: cls, "data-active": open ? "true" : "false",
|
|
318
|
+
onClick: () => store.toggle(), title: "嘉立创 EDA 助手",
|
|
319
|
+
}, inner);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Connection card: one status line, then the three platform actions, then
|
|
324
|
+
* (when connected) the copy-the-prompt CTA. Errors stay inline and quiet.
|
|
325
|
+
*/
|
|
326
|
+
function ConnectCard({ status, error, install, bridge, onInstall, onStart }) {
|
|
239
327
|
const ready = status?.connected === true;
|
|
240
328
|
const [copied, setCopied] = useState(false);
|
|
241
|
-
const dot = status === null ? (error ? "off" : "wait") : (ready ? "on" : "off");
|
|
242
|
-
const state = status === null ? (error ? "状态服务未响应" : "检测中…") : (ready ? "已连接 · " + (status.port ? "127.0.0.1:" + status.port : "") : "未连接");
|
|
243
|
-
const win = status?.health?.match(/edaWindowCount:(\d+)/)?.[1] ?? null;
|
|
244
329
|
const inst = status?.bridgeInstalled === true;
|
|
245
|
-
const
|
|
330
|
+
const dot = status === null ? (error ? "off" : "wait") : ready ? "on" : "off";
|
|
331
|
+
const label = status === null ? (error ? "状态服务未响应" : "检测中…") : ready ? "已连接" : "未连接";
|
|
332
|
+
const port = ready && status?.port ? "127.0.0.1:" + status.port : "";
|
|
333
|
+
const win = status?.health?.match(/edaWindowCount:(\d+)/)?.[1];
|
|
334
|
+
const copy = () => {
|
|
246
335
|
const text = "嘉立创EDA,启动!帮我画一个 LED 点亮电路";
|
|
247
|
-
try { navigator.clipboard?.writeText(text); } catch { /* clipboard
|
|
336
|
+
try { navigator.clipboard?.writeText(text); } catch (e) { /* clipboard unavailable */ }
|
|
248
337
|
setCopied(true);
|
|
249
338
|
setTimeout(() => setCopied(false), 1600);
|
|
250
339
|
};
|
|
251
340
|
return h("div", { className: "eda-card" },
|
|
252
|
-
h("div", { className: "eda-
|
|
341
|
+
h("div", { className: "eda-state" },
|
|
253
342
|
h("span", { className: "eda-dot " + dot }),
|
|
254
|
-
h("
|
|
343
|
+
h("b", null, label),
|
|
344
|
+
h("small", null, [port, win !== undefined ? "窗口 " + win : ""].filter(Boolean).join(" · "))),
|
|
255
345
|
h("div", { className: "eda-actions" },
|
|
256
346
|
h("button", {
|
|
257
|
-
className: "eda-btn " + (inst ? "
|
|
258
|
-
disabled: inst ||
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
347
|
+
className: "eda-btn " + (inst ? "" : "primary"),
|
|
348
|
+
disabled: inst || install?.busy, onClick: onInstall,
|
|
349
|
+
title: inst ? "官方桥已安装(随本机持久)" : "下载官方 easyeda-api-skill 到本机",
|
|
350
|
+
}, inst ? "官方桥已装 ✓" : install?.busy ? "安装中…" : "一键安装官方桥"),
|
|
351
|
+
h("button", { className: "eda-btn", disabled: bridge?.busy, onClick: onStart },
|
|
352
|
+
bridge?.busy ? "启动中…" : "启动桥"),
|
|
263
353
|
h("a", { className: "eda-link", href: LINKS.editor, target: "_blank", rel: "noreferrer" }, "打开编辑器"),
|
|
264
354
|
h("a", { className: "eda-link", href: LINKS.ext, target: "_blank", rel: "noreferrer" }, "扩展直达")),
|
|
265
355
|
ready
|
|
266
|
-
? h("div", { className: "eda-cta" + (copied ? " ok" : ""), onClick:
|
|
267
|
-
|
|
268
|
-
h(
|
|
269
|
-
|
|
270
|
-
h("
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
356
|
+
? h("div", { className: "eda-cta" + (copied ? " ok" : ""), onClick: copy,
|
|
357
|
+
title: "点击复制,然后粘贴到对话里发送" },
|
|
358
|
+
h(SendIcon, null),
|
|
359
|
+
h("span", { className: "eda-cta-t" },
|
|
360
|
+
h("i", null, "去对话里说"),
|
|
361
|
+
h("b", null, "「嘉立创EDA,启动!」")),
|
|
362
|
+
h("span", { className: "eda-cta-c" }, copied ? "已复制 ✓" : "复制"))
|
|
363
|
+
: h("p", { className: "eda-note" }, "连上画板后,在对话里描述需求即可实时生成;离线导出仅作兜底。"),
|
|
364
|
+
install?.result ? h("p", { className: "eda-note" }, install.result) : null,
|
|
365
|
+
bridge?.result ? h("p", { className: "eda-note" }, bridge.result) : null);
|
|
276
366
|
}
|
|
277
367
|
|
|
278
|
-
|
|
279
|
-
function ConnectWizard({ open, setOpen }) {
|
|
368
|
+
function ConnectGuide({ open, setOpen }) {
|
|
280
369
|
if (!open) {
|
|
281
|
-
return h("button", { className: "eda-btn
|
|
370
|
+
return h("button", { className: "eda-btn", style: { width: "100%" }, onClick: () => setOpen(true) },
|
|
371
|
+
"连接教程 · 4 步(网页版官方栈)");
|
|
282
372
|
}
|
|
283
373
|
return h("div", { className: "eda-card" },
|
|
284
|
-
h("div", { className: "eda-card-title" }, "
|
|
374
|
+
h("div", { className: "eda-card-title" }, "连接教程"),
|
|
285
375
|
h("div", { className: "eda-steps" }, CONNECT_STEPS.map((s) =>
|
|
286
|
-
h("div", { className: "eda-step", key: s.
|
|
287
|
-
h("
|
|
288
|
-
h("div",
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
376
|
+
h("div", { className: "eda-step", key: s.n },
|
|
377
|
+
h("span", { className: "eda-step-no" }, String(s.n)),
|
|
378
|
+
h("div", null, h("b", null, s.t), h("p", null, s.d))))),
|
|
379
|
+
h("p", { className: "eda-note" },
|
|
380
|
+
h("a", { className: "eda-link", href: LINKS.editor, target: "_blank", rel: "noreferrer", style: { marginRight: 12 } }, "编辑器"),
|
|
381
|
+
h("a", { className: "eda-link", href: LINKS.ext, target: "_blank", rel: "noreferrer", style: { marginRight: 12 } }, "扩展"),
|
|
382
|
+
h("button", { className: "eda-link", onClick: () => setOpen(false),
|
|
383
|
+
style: { border: 0, background: "transparent", cursor: "pointer", padding: 0, font: "inherit" } }, "收起")));
|
|
294
384
|
}
|
|
295
385
|
|
|
296
386
|
/**
|
|
297
|
-
*
|
|
298
|
-
* numbered
|
|
299
|
-
* 撤回 (deletes the primitives that step created) or expanded; the card
|
|
300
|
-
* has 清空记录. When nothing has happened yet, a static capability
|
|
301
|
-
* preview keeps the panel informative (not blank).
|
|
387
|
+
* Record-style timeline (persisted on disk → never blank). Each step is
|
|
388
|
+
* numbered, expandable, and revocable while the bridge is connected.
|
|
302
389
|
*/
|
|
303
|
-
function
|
|
390
|
+
function Timeline({ feed, pinned, setSid, connected, onRevoke, busyId, onClear, clearing, note }) {
|
|
304
391
|
const [expanded, setExpanded] = useState(() => new Set());
|
|
305
|
-
const toggle = (id) => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
const sids = Array.isArray(sessions) ? sessions : [];
|
|
314
|
-
const hasMulti = sids.length > 1;
|
|
392
|
+
const toggle = (id) => setExpanded((prev) => {
|
|
393
|
+
const next = new Set(prev);
|
|
394
|
+
if (next.has(id)) next.delete(id); else next.add(id);
|
|
395
|
+
return next;
|
|
396
|
+
});
|
|
397
|
+
const list = feed.activities;
|
|
398
|
+
const sids = feed.sessions;
|
|
399
|
+
const multi = sids.length > 1;
|
|
315
400
|
return h("div", { className: "eda-card" },
|
|
316
|
-
h("div", { className: "eda-
|
|
317
|
-
h("div", { className: "eda-card-title" }, "Agent
|
|
318
|
-
|
|
401
|
+
h("div", { className: "eda-tl-head" },
|
|
402
|
+
h("div", { className: "eda-card-title" }, "Agent · 官方 API"),
|
|
403
|
+
multi
|
|
319
404
|
? h("select", {
|
|
320
|
-
className: "eda-
|
|
321
|
-
value: pinned ?? "",
|
|
322
|
-
onChange: (e) => setSid(e.target.value),
|
|
405
|
+
className: "eda-select", value: pinned ?? "", onChange: (e) => setSid(e.target.value),
|
|
323
406
|
title: "切换会话(默认跟随最新)",
|
|
324
407
|
},
|
|
325
|
-
h("option", { value: "" }, "
|
|
326
|
-
sids.map((s) => h("option", { value: s.sid ?? "", key: (s.sid || "
|
|
327
|
-
:
|
|
408
|
+
h("option", { value: "" }, "跟随最新"),
|
|
409
|
+
sids.map((s) => h("option", { value: s.sid ?? "", key: (s.sid || "platform") + s.lastTs }, s.label ?? "会话")))
|
|
410
|
+
: null,
|
|
328
411
|
list.length > 0
|
|
329
|
-
? h("button", { className: "eda-btn
|
|
412
|
+
? h("button", { className: "eda-btn tiny", disabled: clearing, onClick: onClear, title: "清空时间线(含磁盘历史)" },
|
|
413
|
+
clearing ? "清空…" : "清空")
|
|
330
414
|
: null),
|
|
331
415
|
list.length === 0
|
|
332
416
|
? h("div", null,
|
|
333
|
-
h("div", { className: "eda-
|
|
334
|
-
? "
|
|
335
|
-
: "
|
|
336
|
-
h("div", { className: "eda-hints" },
|
|
337
|
-
|
|
338
|
-
.map((t, i) => h("div", { className: "eda-hint", key: i }, "• " + t))))
|
|
417
|
+
h("div", { className: "eda-empty" }, feed.error
|
|
418
|
+
? "实时活动流未就绪(服务端模块未加载)。"
|
|
419
|
+
: "还没有记录。历史写入本机 activity.jsonl,面板不会空着。可以让 agent:"),
|
|
420
|
+
h("div", { className: "eda-hints" }, HINTS.map((t, i) =>
|
|
421
|
+
h("div", { className: "eda-hint", key: i }, h("s", null, "·"), h("span", null, t)))))
|
|
339
422
|
: h("div", null,
|
|
340
|
-
h("div", { className: "eda-
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
const
|
|
423
|
+
h("div", { className: "eda-tl" }, list.slice(0, 60).map((a, i) => {
|
|
424
|
+
const pend = a.status === "pending";
|
|
425
|
+
const open = expanded.has(a.id);
|
|
426
|
+
const revokable = connected && !pend && Array.isArray(a.revoke?.created) && a.revoke.created.length > 0;
|
|
427
|
+
const time = String(a.ts ?? "").slice(11, 19);
|
|
428
|
+
const meta = pend
|
|
429
|
+
? h("span", { className: "eda-pend" }, "执行中…")
|
|
430
|
+
: [time, a.durationMs ? a.durationMs + "ms" : "", a.tool ?? ""].filter(Boolean).join(" · ");
|
|
345
431
|
return h("div", {
|
|
346
|
-
className: "eda-
|
|
347
|
-
key: a.id ?? i,
|
|
348
|
-
onClick: () => toggle(a.id),
|
|
432
|
+
className: "eda-row " + (pend ? "pend" : a.ok === false ? "err" : a.ok ? "ok" : ""),
|
|
433
|
+
key: a.id ?? i, onClick: () => toggle(a.id),
|
|
349
434
|
},
|
|
350
|
-
h("span", { className: "eda-
|
|
351
|
-
h("div", { className: "eda-
|
|
352
|
-
h("div", { className: "eda-
|
|
353
|
-
h("span", { className: "eda-
|
|
354
|
-
h("span", { className: "eda-
|
|
355
|
-
h("span", { className: "eda-
|
|
356
|
-
? h("span", { className: "eda-pend" }, "执行中…")
|
|
357
|
-
: (t + (a.durationMs ? " · " + a.durationMs + "ms" : "") + " · " + (a.tool ?? ""))),
|
|
435
|
+
h("span", { className: "eda-row-dot" }),
|
|
436
|
+
h("div", { className: "eda-row-body" },
|
|
437
|
+
h("div", { className: "eda-row-line" },
|
|
438
|
+
h("span", { className: "eda-seq" }, "#" + (a.id ?? i)),
|
|
439
|
+
h("span", { className: "eda-row-name" }, a.action ?? a.tool ?? "调用 API"),
|
|
440
|
+
h("span", { className: "eda-row-meta" }, meta),
|
|
358
441
|
revokable
|
|
359
442
|
? h("button", {
|
|
360
|
-
className: "eda-btn
|
|
361
|
-
disabled: busyId === a.id,
|
|
443
|
+
className: "eda-btn tiny", disabled: busyId === a.id,
|
|
362
444
|
onClick: (e) => { e.stopPropagation(); onRevoke(a.id); },
|
|
363
445
|
title: "撤回该步(删除它新建的图元)",
|
|
364
|
-
}, busyId === a.id ? "
|
|
446
|
+
}, busyId === a.id ? "撤回…" : "撤回")
|
|
365
447
|
: null),
|
|
366
|
-
a.code ? h("div", { className: "eda-
|
|
367
|
-
!
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
448
|
+
a.code ? h("div", { className: "eda-row-code" }, a.code) : null,
|
|
449
|
+
!pend && a.ok === false && a.error
|
|
450
|
+
? h("div", { className: "eda-row-out err" }, a.error)
|
|
451
|
+
: !pend && a.result ? h("div", { className: "eda-row-out" }, a.result) : null,
|
|
452
|
+
open ? h("div", { className: "eda-row-full" },
|
|
453
|
+
a.code ? h("div", null, h("i", null, "代码"), a.code) : null,
|
|
454
|
+
a.revoke ? h("div", null, h("i", null, "撤回数据"),
|
|
455
|
+
`新建 ${a.revoke.created.length} 图元` + (a.revoke.deletedCount ? ` · 删除 ${a.revoke.deletedCount}(不可恢复)` : "")) : null,
|
|
456
|
+
a.result ? h("div", null, h("i", null, "结果"), a.result) : null,
|
|
457
|
+
a.error ? h("div", null, h("i", null, "错误"), a.error) : null) : null));
|
|
374
458
|
})),
|
|
375
|
-
h("div", { className: "eda-
|
|
459
|
+
h("div", { className: "eda-tl-foot" }, (multi ? "下拉切换会话 · " : "") + "点击条目展开 · 撤回只删该步新建图元")),
|
|
460
|
+
note ? h("p", { className: "eda-note", style: { wordBreak: "break-word" } }, note) : null);
|
|
376
461
|
}
|
|
377
462
|
|
|
378
|
-
/**
|
|
379
|
-
|
|
380
|
-
* AND the agent action log to local disk — a safety net if the cloud sync
|
|
381
|
-
* ever fails. Works (log-only) even when the bridge is disconnected.
|
|
382
|
-
*/
|
|
383
|
-
function SnapshotCard({ connected, busy, onSave, lastOut }) {
|
|
463
|
+
/** Emergency save: board state (.epro2 + SVG + netlist/BOM) + action log → disk. */
|
|
464
|
+
function SnapshotCard({ connected, state, onSave }) {
|
|
384
465
|
return h("div", { className: "eda-card" },
|
|
385
|
-
h("div", { className: "eda-card-title" }, "
|
|
386
|
-
h("button", { className: "eda-btn primary big", disabled: busy, onClick: onSave },
|
|
387
|
-
busy ? "保存中…" : "🛟
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
: "画板未连接:仍会保存 agent 动作日志(每一步留档)。"));
|
|
466
|
+
h("div", { className: "eda-card-title" }, "紧急保存"),
|
|
467
|
+
h("button", { className: "eda-btn primary big", disabled: state.busy, onClick: onSave },
|
|
468
|
+
state.busy ? "保存中…" : "🛟 保存画板到本地"),
|
|
469
|
+
h("p", { className: "eda-muted", style: { marginTop: 8 } }, connected
|
|
470
|
+
? "专业版 .epro2(可完整恢复)+ 预览 SVG + 网表/BOM。云端没同步上也能找回最后的工程。"
|
|
471
|
+
: "画板未连接:仍会保存 agent 动作日志,每一步留档。"),
|
|
472
|
+
state.out ? h("p", { className: "eda-note", style: { wordBreak: "break-all" } }, state.out) : null);
|
|
393
473
|
}
|
|
394
|
-
//#endregion
|
|
395
474
|
|
|
396
|
-
|
|
397
|
-
function EdaPanel({
|
|
475
|
+
/** Panel shell: top bar → scroll body → footer. */
|
|
476
|
+
function EdaPanel({ store }) {
|
|
398
477
|
const [status, error] = useStatus();
|
|
399
478
|
const ready = status?.connected === true;
|
|
400
|
-
const
|
|
401
|
-
const
|
|
402
|
-
const [
|
|
403
|
-
const [
|
|
404
|
-
const [
|
|
405
|
-
const [
|
|
479
|
+
const [pinned, setPinned] = useState("");
|
|
480
|
+
const feed = useActivity(pinned);
|
|
481
|
+
const [guide, setGuide] = useState(false);
|
|
482
|
+
const [install, setInstall] = useState({ busy: false, result: null });
|
|
483
|
+
const [bridge, setBridge] = useState({ busy: false, result: null });
|
|
484
|
+
const [save, setSave] = useState({ busy: false, out: null });
|
|
485
|
+
const [revoke, setRevoke] = useState({ busyId: null, out: null });
|
|
406
486
|
const [clearing, setClearing] = useState(false);
|
|
407
487
|
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
488
|
+
const post = (path) => fetch(API_BASE + path, {
|
|
489
|
+
method: "POST", headers: { "content-type": "application/json" }, body: "{}",
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const doInstall = async () => {
|
|
493
|
+
if (install.busy) return;
|
|
494
|
+
setInstall({ busy: true, result: null });
|
|
411
495
|
try {
|
|
412
|
-
const res = await
|
|
413
|
-
if (!res.ok) {
|
|
496
|
+
const res = await post("/install");
|
|
497
|
+
if (!res.ok) { setInstall({ busy: false, result: "安装接口未就绪(旧模块),可对 AI 说「连接我的画板」。" }); return; }
|
|
414
498
|
const out = await res.json();
|
|
415
|
-
|
|
416
|
-
} catch (
|
|
499
|
+
setInstall({ busy: false, result: out.ok ? "✅ " + (out.script ?? "官方桥已安装") : "❌ " + (out.error ?? "安装失败") });
|
|
500
|
+
} catch (e) { setInstall({ busy: false, result: "请求失败:" + String(e) }); }
|
|
417
501
|
};
|
|
418
|
-
const
|
|
419
|
-
if (
|
|
420
|
-
|
|
502
|
+
const doStart = async () => {
|
|
503
|
+
if (bridge.busy) return;
|
|
504
|
+
setBridge({ busy: true, result: null });
|
|
421
505
|
try {
|
|
422
|
-
const res = await
|
|
423
|
-
if (!res.ok) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
506
|
+
const res = await post("/bridge");
|
|
507
|
+
if (!res.ok) { setBridge({ busy: false, result: "启动接口未就绪(旧模块),可对 AI 说「连接我的画板」。" }); return; }
|
|
508
|
+
const out = await res.json();
|
|
509
|
+
setBridge({ busy: false, result: out.ok ? `已启动(${out.state})` : `未启动:${out.error || out.note || "请先安装桥接"}` });
|
|
510
|
+
} catch (e) { setBridge({ busy: false, result: "请求失败:" + String(e) }); }
|
|
427
511
|
};
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
setInstallState({ busy: true, result: null });
|
|
512
|
+
const doSave = async () => {
|
|
513
|
+
if (save.busy) return;
|
|
514
|
+
setSave({ busy: true, out: null });
|
|
432
515
|
try {
|
|
433
|
-
const res = await
|
|
434
|
-
if (!res.ok) {
|
|
516
|
+
const res = await post("/snapshot");
|
|
517
|
+
if (!res.ok) { setSave({ busy: false, out: "紧急保存接口未就绪(旧模块),可对 AI 说「紧急保存」。" }); return; }
|
|
435
518
|
const out = await res.json();
|
|
436
|
-
|
|
437
|
-
|
|
519
|
+
setSave({
|
|
520
|
+
busy: false,
|
|
521
|
+
out: out.ok
|
|
522
|
+
? `✅ ${out.files?.length ?? 0} 个文件 → ${out.dir ?? ""}` + ((out.errors?.length ?? 0) > 0 ? `(${out.errors.length} 项降级)` : "")
|
|
523
|
+
: "❌ " + String(out.error ?? "保存失败"),
|
|
524
|
+
});
|
|
525
|
+
} catch (e) { setSave({ busy: false, out: "请求失败:" + String(e) }); }
|
|
438
526
|
};
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
setBridgeState({ busy: true, result: null });
|
|
527
|
+
const doRevoke = async (id) => {
|
|
528
|
+
if (revoke.busyId !== null) return;
|
|
529
|
+
setRevoke({ busyId: id, out: null });
|
|
443
530
|
try {
|
|
444
|
-
const res = await fetch(API_BASE + "/
|
|
445
|
-
|
|
531
|
+
const res = await fetch(API_BASE + "/activity/revoke?id=" + encodeURIComponent(id), {
|
|
532
|
+
method: "POST", headers: { "content-type": "application/json" }, body: "{}",
|
|
533
|
+
});
|
|
534
|
+
if (!res.ok) { setRevoke({ busyId: null, out: "撤回接口未就绪(旧模块)。" }); return; }
|
|
446
535
|
const out = await res.json();
|
|
447
|
-
|
|
448
|
-
} catch (
|
|
536
|
+
setRevoke({ busyId: null, out: out.ok ? "↩️ 已撤回:" + (out.note ?? "") : "⚠️ " + String(out.error ?? "撤回失败") });
|
|
537
|
+
} catch (e) { setRevoke({ busyId: null, out: "请求失败:" + String(e) }); }
|
|
449
538
|
};
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
setSaveState({ busy: true, out: null });
|
|
539
|
+
const doClear = async () => {
|
|
540
|
+
if (clearing) return;
|
|
541
|
+
setClearing(true);
|
|
454
542
|
try {
|
|
455
|
-
const res = await
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
else setSaveState({ busy: false, out: `✅ 已保存 ${out.files?.length ?? 0} 个文件 → ${out.dir ?? ""}${(out.errors?.length ?? 0) > 0 ? `(${out.errors.length} 项降级:${out.errors.slice(0, 2).join(";")})` : ""}` });
|
|
460
|
-
} catch (err) { setSaveState({ busy: false, out: "请求失败:" + String(err) }); }
|
|
543
|
+
const res = await post("/activity/clear");
|
|
544
|
+
setRevoke({ busyId: null, out: res.ok ? "🗑️ 时间线已清空" : "清空接口未就绪(旧模块)。" });
|
|
545
|
+
} catch (e) { setRevoke({ busyId: null, out: "请求失败:" + String(e) }); }
|
|
546
|
+
finally { setClearing(false); }
|
|
461
547
|
};
|
|
462
548
|
|
|
463
|
-
return h("div", { className: "eda-
|
|
464
|
-
h("div", { className: "eda-
|
|
465
|
-
h(
|
|
466
|
-
h("div", { className: "eda-
|
|
467
|
-
h("
|
|
468
|
-
h("
|
|
469
|
-
h("button", { className: "eda-
|
|
549
|
+
return h("div", { className: "dsh-eda-root" },
|
|
550
|
+
h("div", { className: "eda-top" },
|
|
551
|
+
h(Mark, null),
|
|
552
|
+
h("div", { className: "eda-title" },
|
|
553
|
+
h("b", null, "嘉立创 EDA"),
|
|
554
|
+
h("span", null, "official bridge · v" + (status?.version ?? "—"))),
|
|
555
|
+
h("button", { className: "eda-iconbtn", onClick: () => store.set(false), title: "关闭面板", "aria-label": "关闭" }, "✕")),
|
|
470
556
|
h("div", { className: "eda-scroll" },
|
|
471
|
-
h(ConnectCard, { status, error, onInstall: doInstall,
|
|
472
|
-
h(
|
|
473
|
-
h(
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
557
|
+
h(ConnectCard, { status, error, install, bridge, onInstall: doInstall, onStart: doStart }),
|
|
558
|
+
h(ConnectGuide, { open: guide, setOpen: setGuide }),
|
|
559
|
+
h(Timeline, {
|
|
560
|
+
feed, pinned, setSid: setPinned, connected: ready,
|
|
561
|
+
onRevoke: doRevoke, busyId: revoke.busyId, onClear: doClear, clearing,
|
|
562
|
+
note: revoke.out,
|
|
563
|
+
}),
|
|
564
|
+
h(SnapshotCard, { connected: ready, state: save, onSave: doSave })),
|
|
565
|
+
h("div", { className: "eda-foot" }, "对话里生成(官方 eda.* API) · 本地留档(紧急保存)"));
|
|
477
566
|
}
|
|
478
|
-
//#endregion
|
|
479
567
|
|
|
480
|
-
|
|
481
|
-
|
|
568
|
+
/** Overlay wrapper: mounted in `shell.overlay`; `hidden` toggles visibility so
|
|
569
|
+
* the feed keeps running while the panel is closed (same surface as 0.1.x). */
|
|
570
|
+
function EdaOverlay({ store }) {
|
|
571
|
+
const open = useStore(store);
|
|
572
|
+
const [width, setWidth] = useState(() => {
|
|
573
|
+
try { return Math.min(760, Math.max(120, Number(localStorage.getItem("dsh-eda:width")) || 400)); } catch (e) { return 400; }
|
|
574
|
+
});
|
|
575
|
+
const drag = useRef(null);
|
|
576
|
+
const startDrag = (e) => {
|
|
577
|
+
e.preventDefault();
|
|
578
|
+
const move = (ev) => {
|
|
579
|
+
const next = Math.min(760, Math.max(120, window.innerWidth - ev.clientX));
|
|
580
|
+
drag.current = next;
|
|
581
|
+
setWidth(next);
|
|
582
|
+
};
|
|
583
|
+
const up = () => {
|
|
584
|
+
window.removeEventListener("mousemove", move);
|
|
585
|
+
window.removeEventListener("mouseup", up);
|
|
586
|
+
if (drag.current !== null) { try { localStorage.setItem("dsh-eda:width", String(drag.current)); } catch (err) { /* ignore */ } }
|
|
587
|
+
};
|
|
588
|
+
window.addEventListener("mousemove", move);
|
|
589
|
+
window.addEventListener("mouseup", up);
|
|
590
|
+
};
|
|
591
|
+
return h("div", { className: "dsh-eda-root", hidden: !open, style: { "--eda-w": width + "px" } },
|
|
592
|
+
h("div", { className: "dsh-eda-grip", onMouseDown: startDrag, title: "拖动调整宽度" }),
|
|
593
|
+
h(EdaPanel, { store }));
|
|
594
|
+
}
|
|
595
|
+
//#endregion
|
|
482
596
|
|
|
597
|
+
//#region mounts (0.1.x compatibility)
|
|
483
598
|
function sidebarRoot() {
|
|
484
599
|
const column = document.querySelector('[data-pane="sidebar"], [class*="sidebarCol"]');
|
|
485
600
|
if (column === null) return undefined;
|
|
486
601
|
const logoOwner = column.querySelector('[class*="logoRow"]')?.parentElement;
|
|
487
602
|
return logoOwner ?? (column.firstElementChild ?? undefined);
|
|
488
603
|
}
|
|
489
|
-
|
|
490
604
|
function newSessionButton(root) {
|
|
491
605
|
const nested = root.querySelector('button[class*="newSession"]');
|
|
492
606
|
if (nested !== null) return nested;
|
|
493
|
-
for (const child of root.children)
|
|
494
|
-
if (child.tagName === "BUTTON") return child;
|
|
495
|
-
}
|
|
607
|
+
for (const child of root.children) if (child.tagName === "BUTTON") return child;
|
|
496
608
|
return undefined;
|
|
497
609
|
}
|
|
498
|
-
|
|
499
|
-
function createEntry(controller) {
|
|
610
|
+
function createEntry(store) {
|
|
500
611
|
const entry = document.createElement("button");
|
|
501
612
|
entry.type = "button";
|
|
502
613
|
entry.dataset.dshEdaEntry = "";
|
|
503
614
|
entry.setAttribute("aria-label", "嘉立创 EDA 助手");
|
|
504
|
-
entry.setAttribute("title", "嘉立创 EDA
|
|
505
|
-
entry.innerHTML = '<span class="eda-entry-icon"
|
|
506
|
-
entry.addEventListener("click", () =>
|
|
615
|
+
entry.setAttribute("title", "嘉立创 EDA 助手(官方画板平台)");
|
|
616
|
+
entry.innerHTML = '<span class="eda-entry-icon"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.2" aria-hidden="true"><path d="' + ICON_EDA + '" stroke-linejoin="round" stroke-linecap="round"/></svg></span><span class="eda-entry-label">嘉立创 EDA</span>';
|
|
617
|
+
entry.addEventListener("click", () => store.toggle());
|
|
507
618
|
return entry;
|
|
508
619
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const button = newSessionButton(root);
|
|
512
|
-
if (button === undefined) return false;
|
|
513
|
-
if (entry.parentElement !== root) {
|
|
514
|
-
const row = button.closest('[class*="logoRow"]');
|
|
515
|
-
const base = (row !== null && row.parentElement === root) ? row : button;
|
|
516
|
-
const family = Array.from(root.children).filter(
|
|
517
|
-
(el) => el instanceof HTMLElement && el.matches("[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-logcat-entry], [data-dsh-eda-entry]"),
|
|
518
|
-
);
|
|
519
|
-
const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
|
|
520
|
-
root.insertBefore(entry, anchor);
|
|
521
|
-
}
|
|
522
|
-
return true;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function mountSidebarEntry(controller) {
|
|
526
|
-
const entry = createEntry(controller);
|
|
620
|
+
function mountSidebarEntry(store) {
|
|
621
|
+
const entry = createEntry(store);
|
|
527
622
|
let root;
|
|
528
623
|
let placed = false;
|
|
529
624
|
let rootObserver;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
625
|
+
const place = () => {
|
|
626
|
+
const button = newSessionButton(root);
|
|
627
|
+
if (button === undefined) return false;
|
|
628
|
+
if (entry.parentElement !== root) {
|
|
629
|
+
const row = button.closest('[class*="logoRow"]');
|
|
630
|
+
const base = row !== null && row.parentElement === root ? row : button;
|
|
631
|
+
const family = Array.from(root.children).filter((el) =>
|
|
632
|
+
el instanceof HTMLElement && el.matches("[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-logcat-entry], [data-dsh-eda-entry]"));
|
|
633
|
+
const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
|
|
634
|
+
root.insertBefore(entry, anchor);
|
|
536
635
|
}
|
|
537
|
-
|
|
538
|
-
|
|
636
|
+
return true;
|
|
637
|
+
};
|
|
638
|
+
const tryPlace = () => {
|
|
639
|
+
if (root !== undefined && (!root.isConnected || (placed && !document.body.contains(entry)))) {
|
|
539
640
|
rootObserver?.disconnect();
|
|
540
641
|
root = undefined;
|
|
541
642
|
placed = false;
|
|
542
643
|
}
|
|
644
|
+
if (placed) return;
|
|
543
645
|
root ??= sidebarRoot();
|
|
544
646
|
if (root === undefined) return;
|
|
545
|
-
placed =
|
|
647
|
+
placed = place();
|
|
546
648
|
if (placed) {
|
|
547
649
|
rootObserver = new MutationObserver(() => {
|
|
548
650
|
if (root === undefined || !root.isConnected) { placed = false; tryPlace(); return; }
|
|
549
|
-
if (!root.contains(entry)) placed =
|
|
651
|
+
if (!root.contains(entry)) placed = place();
|
|
550
652
|
});
|
|
551
653
|
rootObserver.observe(root, { childList: true, subtree: true });
|
|
552
654
|
}
|
|
553
655
|
};
|
|
554
|
-
|
|
555
|
-
const waitObserver = new MutationObserver(() => { tryPlace(); });
|
|
656
|
+
const waitObserver = new MutationObserver(() => tryPlace());
|
|
556
657
|
waitObserver.observe(document.body, { childList: true, subtree: true });
|
|
557
|
-
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
else delete entry.dataset.active;
|
|
561
|
-
};
|
|
562
|
-
const unsubscribe = controller.subscribe(syncActive);
|
|
563
|
-
syncActive();
|
|
658
|
+
const sync = () => { if (store.get()) entry.dataset.active = "true"; else delete entry.dataset.active; };
|
|
659
|
+
const unsubscribe = store.subscribe(sync);
|
|
660
|
+
sync();
|
|
564
661
|
tryPlace();
|
|
565
|
-
|
|
566
662
|
return () => {
|
|
567
663
|
waitObserver.disconnect();
|
|
568
664
|
rootObserver?.disconnect();
|
|
@@ -571,86 +667,81 @@ window.__ModuleLoader__.load({
|
|
|
571
667
|
};
|
|
572
668
|
}
|
|
573
669
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const panel = document.createElement("div");
|
|
587
|
-
panel.className = "dsh-eda-panel";
|
|
588
|
-
container.appendChild(panel);
|
|
589
|
-
document.body.appendChild(container);
|
|
590
|
-
root = createRoot(panel);
|
|
591
|
-
root.render(h(EdaPanel, { controller }));
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
ensure();
|
|
595
|
-
|
|
596
|
-
const applyOpen = () => {
|
|
597
|
-
if (container !== undefined) container.hidden = !controller.getSnapshot().panelOpen;
|
|
598
|
-
};
|
|
599
|
-
const unsubscribe = controller.subscribe(applyOpen);
|
|
600
|
-
applyOpen();
|
|
601
|
-
|
|
670
|
+
/**
|
|
671
|
+
* Fallback mount for shells without the slots service (and for the unit
|
|
672
|
+
* test's DOM stub): a body-level overlay. Still absolute-in-shell friendly:
|
|
673
|
+
* z-index stays low (40) and the panel keeps its own close affordance.
|
|
674
|
+
*/
|
|
675
|
+
function mountFallback(store) {
|
|
676
|
+
const container = document.createElement("div");
|
|
677
|
+
container.dataset.dshEdaView = "";
|
|
678
|
+
container.className = "dsh-eda-fallback";
|
|
679
|
+
const root = createRoot(container);
|
|
680
|
+
document.body.appendChild(container);
|
|
681
|
+
root.render(h(EdaOverlay, { store }));
|
|
602
682
|
return () => {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
root = undefined;
|
|
606
|
-
container?.remove();
|
|
607
|
-
container = undefined;
|
|
683
|
+
root.unmount();
|
|
684
|
+
container.remove();
|
|
608
685
|
};
|
|
609
686
|
}
|
|
610
687
|
//#endregion
|
|
611
688
|
|
|
612
|
-
//#region controller
|
|
613
|
-
/** Minimal open/close controller: panelOpen + a tiny subscribe store. */
|
|
614
|
-
function PanelController() {
|
|
615
|
-
let panelOpen = false;
|
|
616
|
-
const listeners = new Set();
|
|
617
|
-
this.toggle = () => { panelOpen = !panelOpen; for (const fn of listeners) fn(); };
|
|
618
|
-
this.getSnapshot = () => ({ panelOpen });
|
|
619
|
-
this.subscribe = (fn) => { listeners.add(fn); return () => { listeners.delete(fn); }; };
|
|
620
|
-
}
|
|
621
|
-
//#endregion
|
|
622
|
-
|
|
623
689
|
//#region entry
|
|
624
|
-
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
625
690
|
const inject = ["slots"];
|
|
626
691
|
|
|
627
692
|
/**
|
|
628
|
-
* Mount the
|
|
693
|
+
* Mount the panel: 0.2 registers into `shell.overlay` + a header utility pill;
|
|
694
|
+
* 0.1.x keeps the sidebar DOM entry. Anything unavailable degrades quietly.
|
|
629
695
|
* @param ctx - client root context.
|
|
630
696
|
*/
|
|
631
697
|
function apply(ctx) {
|
|
632
698
|
const style = document.createElement("style");
|
|
633
|
-
style.textContent = STYLE;
|
|
699
|
+
style.textContent = STYLE + "\n.dsh-eda-fallback { position: fixed; inset: 0; pointer-events: none; z-index: 40; }\n.dsh-eda-fallback > .dsh-eda-root { pointer-events: auto; }";
|
|
634
700
|
style.dataset.dshEdaStyle = "";
|
|
635
701
|
document.head.appendChild(style);
|
|
636
702
|
|
|
637
|
-
const
|
|
703
|
+
const store = new PanelStore();
|
|
638
704
|
const disposers = [];
|
|
705
|
+
let slotRegistered = false;
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
const slots = typeof ctx.get === "function" ? ctx.get("slots") : undefined;
|
|
709
|
+
if (slots !== undefined && slots !== null) {
|
|
710
|
+
disposers.push(slots.inject("conversation.session.header.utilities", () => slots.register(
|
|
711
|
+
{ name: "conversation.session.header.utilities", id: "eda-toggle", order: 30 },
|
|
712
|
+
() => h(HeaderPill, { store }),
|
|
713
|
+
)));
|
|
714
|
+
disposers.push(slots.inject("shell.overlay", () => slots.register(
|
|
715
|
+
{ name: "shell.overlay", id: "eda", order: 90 },
|
|
716
|
+
() => h(EdaOverlay, { store }),
|
|
717
|
+
)));
|
|
718
|
+
slotRegistered = true;
|
|
719
|
+
}
|
|
720
|
+
} catch (error) {
|
|
721
|
+
console.warn("[dsh-eda] slot registration failed, falling back to DOM mount", error);
|
|
722
|
+
}
|
|
723
|
+
|
|
639
724
|
try {
|
|
640
|
-
disposers.push(mountSidebarEntry(
|
|
641
|
-
disposers.push(mountPanel(controller));
|
|
725
|
+
disposers.push(mountSidebarEntry(store));
|
|
642
726
|
} catch (error) {
|
|
643
|
-
console.warn("[dsh-eda]
|
|
727
|
+
console.warn("[dsh-eda] sidebar entry failed", error);
|
|
644
728
|
}
|
|
729
|
+
if (!slotRegistered) {
|
|
730
|
+
try {
|
|
731
|
+
disposers.push(mountFallback(store));
|
|
732
|
+
} catch (error) {
|
|
733
|
+
console.warn("[dsh-eda] fallback mount failed", error);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
645
737
|
ctx.effect(() => () => {
|
|
646
738
|
for (const dispose of disposers.splice(0)) dispose();
|
|
647
739
|
style.remove();
|
|
648
|
-
}, "dsh-eda:
|
|
740
|
+
}, "dsh-eda: browser UI");
|
|
649
741
|
}
|
|
650
|
-
//#endregion
|
|
651
742
|
|
|
652
743
|
exports.apply = apply;
|
|
653
744
|
exports.inject = inject;
|
|
654
745
|
return module.exports;
|
|
655
|
-
}
|
|
746
|
+
},
|
|
656
747
|
});
|