dsh-reports-panel 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +57 -2
- package/cordis.patch.yml +4 -0
- package/lib/client.js +407 -0
- package/lib/index.js +12 -0
- package/package.json +53 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bayan1377
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-reports-panel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Панель «Отчёты» в левом сайдбаре DeepSeek Harness: файлы из папки `Отчёты` рабочей
|
|
4
|
+
папки сессии — под рукой, без переключения на файловый менеджер.
|
|
5
|
+
|
|
6
|
+
## Что делает
|
|
7
|
+
|
|
8
|
+
- Читает список файлов из папки `Отчёты` текущей рабочей папки сессии через Remote
|
|
9
|
+
`workspaceFiles` — то есть показывает ровно то, что агент положил рядом с работой.
|
|
10
|
+
- Рисует ленту в левой панели, клик открывает файл.
|
|
11
|
+
- Есть кнопка «Обновить», состояния «Загружаю…», «Отчётов пока нет. Появятся после
|
|
12
|
+
обработки файлов.» и понятные ошибки: «Нет активной сессии — откройте чат и
|
|
13
|
+
обновите.», «Не удалось прочитать отчёты.».
|
|
14
|
+
- На хосте — только строка в Loader: ни сервисов, ни конфигурации, ни своих маршрутов.
|
|
15
|
+
|
|
16
|
+
## Установка
|
|
17
|
+
|
|
18
|
+
Нужны Node.js 20+ и `pnpm` (DSH ставит плагины через него):
|
|
19
|
+
|
|
20
|
+
```powershell
|
|
21
|
+
dsh plugin --profile desktop add dsh-reports-panel
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Обновление — двумя шагами, `add` поверх стоящего пакета DSH отклонит:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
dsh plugin --profile desktop remove dsh-reports-panel
|
|
28
|
+
dsh plugin --profile desktop add dsh-reports-panel
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Перезапустите DSH после установки: плагин добавляет строку в сайдбар через
|
|
32
|
+
`@deepseek-ai/dsh-client-ui-sidebar`.
|
|
33
|
+
|
|
34
|
+
## Настройка
|
|
35
|
+
|
|
36
|
+
Настройки нет. Папка берётся по имени `Отчёты` в рабочей папке сессии — если её нет,
|
|
37
|
+
панель показывает пустое состояние, а не ошибку.
|
|
38
|
+
|
|
39
|
+
## Устройство
|
|
40
|
+
|
|
41
|
+
- `lib/index.js` — хост: пустой `apply()`, нужен только для строки в Loader.
|
|
42
|
+
- `lib/client.js` — браузерная половина: сайдбар, чтение `workspaceFiles`, рендер.
|
|
43
|
+
- `cordis.patch.yml` — вставка плагина в стек слоёв профиля.
|
|
44
|
+
|
|
45
|
+
## Выключить
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
dsh plugin --profile desktop remove dsh-reports-panel
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Лицензия
|
|
52
|
+
|
|
53
|
+
MIT.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
English: reports feed in the DSH left sidebar — lists files from the `Отчёты` folder of
|
|
58
|
+
the session workspace through the `workspaceFiles` remote, with refresh and empty states.
|
package/cordis.patch.yml
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/* Reports panel — левая панель «Отчёты».
|
|
2
|
+
*
|
|
3
|
+
* Читает папку «Отчёты» рабочей папки сессии через Remote `workspaceFiles`
|
|
4
|
+
* и показывает список карточек отчётов. Клик по карточке раскрывает текст
|
|
5
|
+
* отчёта прямо под этой карточкой, повторный клик — сворачивает и оставляет
|
|
6
|
+
* только карточки. Открытым может быть один отчёт; текст кешируется, чтобы
|
|
7
|
+
* повторное раскрытие не ходило в диск.
|
|
8
|
+
*
|
|
9
|
+
* Как и automation-log, панель defensive: отсутствие слота, сессии или Remote
|
|
10
|
+
* не должно валить загрузку клиента.
|
|
11
|
+
*/
|
|
12
|
+
window.__ModuleLoader__.load({
|
|
13
|
+
id: "dsh-reports-panel",
|
|
14
|
+
factory: (require) => {
|
|
15
|
+
var module = { exports: {} };
|
|
16
|
+
var exports = module.exports;
|
|
17
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
18
|
+
const react = require("react");
|
|
19
|
+
|
|
20
|
+
const PANEL_ID = "reports";
|
|
21
|
+
const FOLDER = "Отчёты";
|
|
22
|
+
const LABEL = "Отчёты";
|
|
23
|
+
const SUBTITLE = "Отчёты по обработанным файлам. Нажмите на карточку, чтобы прочитать.";
|
|
24
|
+
const EMPTY = "Отчётов пока нет.";
|
|
25
|
+
const EMPTY_HINT = "Появятся после обработки файлов.";
|
|
26
|
+
const RETRY = "Обновить";
|
|
27
|
+
const LOADING = "Загружаю…";
|
|
28
|
+
const FAILED = "Не удалось прочитать отчёты.";
|
|
29
|
+
const NO_SESSION = "Нет активной сессии — откройте чат и обновите.";
|
|
30
|
+
const POLL_MS = 20000;
|
|
31
|
+
|
|
32
|
+
const COLOR = {
|
|
33
|
+
primary: "var(--dsw-alias-label-primary, #e6e6e6)",
|
|
34
|
+
secondary: "var(--dsw-alias-label-secondary, #9aa0a6)",
|
|
35
|
+
tertiary: "var(--dsw-alias-label-tertiary, #7c8288)",
|
|
36
|
+
border: "var(--dsw-alias-border-l1, rgba(255,255,255,0.10))",
|
|
37
|
+
hover: "var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.06))",
|
|
38
|
+
accent: "var(--dsw-alias-brand-primary, #4d6bfe)"
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Подпись отчёта: имя файла без .md. */
|
|
42
|
+
function titleOf(name) {
|
|
43
|
+
return String(name || "").replace(/\.md$/i, "");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Кеш текстов: имя отчёта → уже прочитанный текст. */
|
|
47
|
+
const texts = new Map();
|
|
48
|
+
|
|
49
|
+
const store = {
|
|
50
|
+
state: { phase: "loading", rows: [], open: "", text: "", loading: false, error: null },
|
|
51
|
+
listeners: new Set(),
|
|
52
|
+
set(next) {
|
|
53
|
+
store.state = next;
|
|
54
|
+
for (const listener of store.listeners) {
|
|
55
|
+
try {
|
|
56
|
+
listener();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.warn("[reports-panel] listener failed", error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
subscribe(fn) {
|
|
63
|
+
store.listeners.add(fn);
|
|
64
|
+
return () => store.listeners.delete(fn);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let inFlight = null;
|
|
69
|
+
let panelContext = null;
|
|
70
|
+
|
|
71
|
+
/** Сервис сессий: ctx.sessions либо ctx.get('sessions'). */
|
|
72
|
+
function sessionsService(ctx) {
|
|
73
|
+
try {
|
|
74
|
+
if (ctx && ctx.sessions) return ctx.sessions;
|
|
75
|
+
} catch (error) { }
|
|
76
|
+
try {
|
|
77
|
+
if (ctx && typeof ctx.get === "function") {
|
|
78
|
+
const service = ctx.get("sessions");
|
|
79
|
+
if (service) return service;
|
|
80
|
+
}
|
|
81
|
+
} catch (error) { }
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** id сессии, которую держит mainView (текущая вкладка). */
|
|
86
|
+
function pickSessionId(ctx) {
|
|
87
|
+
const service = sessionsService(ctx);
|
|
88
|
+
if (!service) return null;
|
|
89
|
+
let snapshot = null;
|
|
90
|
+
try {
|
|
91
|
+
if (service.list && typeof service.list.getSnapshot === "function") {
|
|
92
|
+
snapshot = service.list.getSnapshot();
|
|
93
|
+
}
|
|
94
|
+
} catch (error) { }
|
|
95
|
+
if (!snapshot) {
|
|
96
|
+
try {
|
|
97
|
+
if (typeof service.getSnapshot === "function") snapshot = service.getSnapshot();
|
|
98
|
+
} catch (error) { }
|
|
99
|
+
}
|
|
100
|
+
if (!snapshot) return null;
|
|
101
|
+
const byId = snapshot.byId || (snapshot.sessions && snapshot.sessions.byId);
|
|
102
|
+
if (!byId) return null;
|
|
103
|
+
const rows = Object.values(byId);
|
|
104
|
+
const main = rows.find((row) => row && row.retainedBy && row.retainedBy.mainView > 0);
|
|
105
|
+
if (main && main.id) return main.id;
|
|
106
|
+
return rows.length && rows[0].id ? rows[0].id : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Список markdown-файлов в папке отчётов. */
|
|
110
|
+
function listFolder(ctx) {
|
|
111
|
+
const sessionId = pickSessionId(ctx);
|
|
112
|
+
if (!sessionId) throw new Error(NO_SESSION);
|
|
113
|
+
return Promise.resolve()
|
|
114
|
+
.then(() => ctx.remote.workspaceFiles.list(sessionId, FOLDER, undefined))
|
|
115
|
+
.then((raw) => {
|
|
116
|
+
const value = raw && raw.ok === true ? raw.value : raw;
|
|
117
|
+
if (!value || !Array.isArray(value.entries)) throw new Error("список не получен");
|
|
118
|
+
return value.entries
|
|
119
|
+
.filter((entry) => entry && typeof entry.name === "string" && /\.md$/i.test(entry.name))
|
|
120
|
+
.map((entry) => ({ name: entry.name, path: entry.path || (FOLDER + "/" + entry.name) }))
|
|
121
|
+
.sort((a, b) => b.name.localeCompare(a.name));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Клик по карточке: раскрыть отчёт под ней или свернуть открытый. */
|
|
126
|
+
function toggleReport(ctx, row) {
|
|
127
|
+
const current = store.state;
|
|
128
|
+
if (current.open === row.name) {
|
|
129
|
+
store.set({ ...current, open: "", text: "", loading: false, error: null });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const cached = texts.get(row.name);
|
|
133
|
+
if (typeof cached === "string") {
|
|
134
|
+
store.set({ ...current, open: row.name, text: cached, loading: false, error: null });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
store.set({ ...current, open: row.name, text: "", loading: true, error: null });
|
|
138
|
+
loadText(ctx, row);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Чтение одного отчёта (первая страница текста) с кешированием. */
|
|
142
|
+
function loadText(ctx, row) {
|
|
143
|
+
const sessionId = pickSessionId(ctx);
|
|
144
|
+
if (!sessionId) {
|
|
145
|
+
store.set({ ...store.state, open: "", text: "", loading: false, error: NO_SESSION });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
Promise.resolve()
|
|
149
|
+
.then(() => ctx.remote.workspaceFiles.read(sessionId, row.path, { offset: 1, limit: 160 }, undefined))
|
|
150
|
+
.then((raw) => {
|
|
151
|
+
const value = raw && raw.ok === true ? raw.value : raw;
|
|
152
|
+
if (!value || typeof value.text !== "string") throw new Error("текст не получен");
|
|
153
|
+
texts.set(row.name, value.text);
|
|
154
|
+
if (store.state.open === row.name) {
|
|
155
|
+
store.set({ ...store.state, text: value.text, loading: false, error: null });
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
.catch((error) => {
|
|
159
|
+
const message = "Не удалось прочитать: " + String(error && error.message || error);
|
|
160
|
+
if (store.state.open === row.name) {
|
|
161
|
+
store.set({ ...store.state, text: message, loading: false });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Обновить список (одно чтение на всех). */
|
|
167
|
+
function refresh(ctx) {
|
|
168
|
+
if (inFlight) return inFlight;
|
|
169
|
+
inFlight = Promise.resolve()
|
|
170
|
+
.then(() => listFolder(ctx))
|
|
171
|
+
.then((rows) => {
|
|
172
|
+
const current = store.state;
|
|
173
|
+
const openStillExists = Boolean(current.open) && rows.some((row) => row.name === current.open);
|
|
174
|
+
store.set({
|
|
175
|
+
phase: "ready",
|
|
176
|
+
rows,
|
|
177
|
+
open: openStillExists ? current.open : "",
|
|
178
|
+
text: openStillExists ? current.text : "",
|
|
179
|
+
loading: openStillExists ? current.loading : false,
|
|
180
|
+
error: null
|
|
181
|
+
});
|
|
182
|
+
})
|
|
183
|
+
.catch((error) => {
|
|
184
|
+
console.warn("[reports-panel] list failed", error);
|
|
185
|
+
store.set({
|
|
186
|
+
phase: "error",
|
|
187
|
+
rows: store.state.rows,
|
|
188
|
+
open: store.state.open,
|
|
189
|
+
text: store.state.text,
|
|
190
|
+
loading: store.state.loading,
|
|
191
|
+
error: String(error && error.message || error)
|
|
192
|
+
});
|
|
193
|
+
})
|
|
194
|
+
.finally(() => {
|
|
195
|
+
inFlight = null;
|
|
196
|
+
});
|
|
197
|
+
return inFlight;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function useReports(ctx) {
|
|
201
|
+
const [, setTick] = react.useState(0);
|
|
202
|
+
react.useEffect(() => store.subscribe(() => setTick((value) => value + 1)), []);
|
|
203
|
+
react.useEffect(() => {
|
|
204
|
+
refresh(ctx);
|
|
205
|
+
const timer = setInterval(() => refresh(ctx), POLL_MS);
|
|
206
|
+
const onVisible = () => {
|
|
207
|
+
if (document.visibilityState === "visible") refresh(ctx);
|
|
208
|
+
};
|
|
209
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
210
|
+
return () => {
|
|
211
|
+
clearInterval(timer);
|
|
212
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
213
|
+
};
|
|
214
|
+
}, []);
|
|
215
|
+
return store.state;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const CHEVRON = { width: 12, fontSize: 10, lineHeight: "20px", color: COLOR.secondary, flex: "0 0 auto", textAlign: "center" };
|
|
219
|
+
|
|
220
|
+
/** Карточка отчёта: заголовок-кнопка и, если открыта, текст под ней. */
|
|
221
|
+
function ReportRow({ row, active, loading, text, onToggle }) {
|
|
222
|
+
const [hover, setHover] = react.useState(false);
|
|
223
|
+
return react.createElement("div", {
|
|
224
|
+
"data-report": row.name,
|
|
225
|
+
"data-open": active ? "true" : "false",
|
|
226
|
+
style: {
|
|
227
|
+
marginBottom: 8,
|
|
228
|
+
border: "1px solid " + (hover || active ? COLOR.accent : COLOR.border),
|
|
229
|
+
borderRadius: 10,
|
|
230
|
+
background: hover && !active ? COLOR.hover : "transparent",
|
|
231
|
+
overflow: "hidden"
|
|
232
|
+
}
|
|
233
|
+
}, [
|
|
234
|
+
react.createElement("div", {
|
|
235
|
+
key: "head",
|
|
236
|
+
role: "button",
|
|
237
|
+
tabIndex: 0,
|
|
238
|
+
"aria-expanded": active,
|
|
239
|
+
onClick: () => onToggle(row),
|
|
240
|
+
onKeyDown: (event) => {
|
|
241
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
onToggle(row);
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
onMouseEnter: () => setHover(true),
|
|
247
|
+
onMouseLeave: () => setHover(false),
|
|
248
|
+
style: {
|
|
249
|
+
display: "flex",
|
|
250
|
+
alignItems: "center",
|
|
251
|
+
gap: 6,
|
|
252
|
+
padding: "10px 12px",
|
|
253
|
+
textAlign: "left",
|
|
254
|
+
cursor: "pointer",
|
|
255
|
+
outline: "none",
|
|
256
|
+
color: COLOR.primary,
|
|
257
|
+
font: "inherit"
|
|
258
|
+
}
|
|
259
|
+
}, [
|
|
260
|
+
react.createElement("span", { key: "chevron", "aria-hidden": "true", style: CHEVRON }, active ? "▾" : "▸"),
|
|
261
|
+
react.createElement("span", {
|
|
262
|
+
key: "title",
|
|
263
|
+
style: { fontSize: 14, lineHeight: "20px", fontWeight: 550 }
|
|
264
|
+
}, titleOf(row.name))
|
|
265
|
+
]),
|
|
266
|
+
active ? react.createElement("div", {
|
|
267
|
+
key: "body",
|
|
268
|
+
"data-report-body": row.name,
|
|
269
|
+
style: { borderTop: "1px solid " + COLOR.border }
|
|
270
|
+
}, loading ? react.createElement("div", {
|
|
271
|
+
style: { padding: "12px 14px", color: COLOR.secondary, fontSize: 12 }
|
|
272
|
+
}, LOADING) : react.createElement("pre", {
|
|
273
|
+
style: {
|
|
274
|
+
margin: 0,
|
|
275
|
+
padding: "12px 16px 14px",
|
|
276
|
+
color: COLOR.primary,
|
|
277
|
+
font: "12px/1.55 ui-monospace, Consolas, monospace",
|
|
278
|
+
whiteSpace: "pre-wrap",
|
|
279
|
+
wordBreak: "break-word",
|
|
280
|
+
maxHeight: 480,
|
|
281
|
+
overflowY: "auto"
|
|
282
|
+
}
|
|
283
|
+
}, text)) : null
|
|
284
|
+
]);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function ReportsPanel(props) {
|
|
288
|
+
const ctx = props && props.ctx ? props.ctx : panelContext;
|
|
289
|
+
const state = useReports(ctx);
|
|
290
|
+
return react.createElement("div", {
|
|
291
|
+
style: {
|
|
292
|
+
height: "100%",
|
|
293
|
+
overflowY: "auto",
|
|
294
|
+
boxSizing: "border-box",
|
|
295
|
+
padding: "calc(var(--dsh-frame-top-clearance, 0px) + 28px) 24px 32px"
|
|
296
|
+
}
|
|
297
|
+
}, react.createElement("div", {
|
|
298
|
+
style: { maxWidth: 820, margin: "0 auto" }
|
|
299
|
+
}, [
|
|
300
|
+
react.createElement("div", {
|
|
301
|
+
key: "header",
|
|
302
|
+
style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 16, marginBottom: 18 }
|
|
303
|
+
}, [
|
|
304
|
+
react.createElement("div", { key: "text" }, [
|
|
305
|
+
react.createElement("h2", {
|
|
306
|
+
key: "title",
|
|
307
|
+
style: { margin: 0, fontSize: 20, lineHeight: "26px", fontWeight: 600, color: COLOR.primary }
|
|
308
|
+
}, LABEL),
|
|
309
|
+
react.createElement("div", {
|
|
310
|
+
key: "sub",
|
|
311
|
+
style: { marginTop: 4, fontSize: 13, lineHeight: "18px", color: COLOR.secondary }
|
|
312
|
+
}, SUBTITLE)
|
|
313
|
+
]),
|
|
314
|
+
react.createElement("button", {
|
|
315
|
+
key: "refresh",
|
|
316
|
+
type: "button",
|
|
317
|
+
onClick: () => refresh(ctx),
|
|
318
|
+
style: {
|
|
319
|
+
padding: "6px 12px", borderRadius: 8, border: "1px solid " + COLOR.border,
|
|
320
|
+
background: "transparent", color: COLOR.secondary, font: "inherit", fontSize: 12, cursor: "pointer"
|
|
321
|
+
}
|
|
322
|
+
}, RETRY)
|
|
323
|
+
]),
|
|
324
|
+
state.phase === "loading" && state.rows.length === 0 ? react.createElement("div", {
|
|
325
|
+
key: "loading", style: { color: COLOR.secondary, fontSize: 13 }
|
|
326
|
+
}, LOADING) : null,
|
|
327
|
+
state.phase === "error" ? react.createElement("div", {
|
|
328
|
+
key: "error",
|
|
329
|
+
style: {
|
|
330
|
+
marginBottom: 12, padding: "10px 12px", borderRadius: 10,
|
|
331
|
+
border: "1px solid " + COLOR.border, color: COLOR.secondary, fontSize: 12
|
|
332
|
+
}
|
|
333
|
+
}, FAILED + " " + (state.error || "")) : null,
|
|
334
|
+
state.rows.length === 0 && state.phase !== "loading" ? react.createElement("div", {
|
|
335
|
+
key: "empty",
|
|
336
|
+
style: { padding: "24px 12px", color: COLOR.secondary, fontSize: 13, textAlign: "center" }
|
|
337
|
+
}, [react.createElement("div", { key: "a" }, EMPTY), react.createElement("div", {
|
|
338
|
+
key: "b", style: { marginTop: 4, color: COLOR.tertiary, fontSize: 12 }
|
|
339
|
+
}, EMPTY_HINT)]) : null,
|
|
340
|
+
react.createElement("div", { key: "rows" }, state.rows.map((row) => react.createElement(ReportRow, {
|
|
341
|
+
key: row.name,
|
|
342
|
+
row,
|
|
343
|
+
active: state.open === row.name,
|
|
344
|
+
loading: state.loading,
|
|
345
|
+
text: state.open === row.name ? state.text : "",
|
|
346
|
+
onToggle: (item) => toggleReport(ctx, item)
|
|
347
|
+
})))
|
|
348
|
+
]));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function ReportsIcon({ size }) {
|
|
352
|
+
const px = typeof size === "number" && size > 0 ? size : 16;
|
|
353
|
+
return react.createElement("svg", {
|
|
354
|
+
width: px, height: px, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true"
|
|
355
|
+
}, [
|
|
356
|
+
react.createElement("path", {
|
|
357
|
+
key: "a", d: "M4 1.8h5.2L13 5.6v8.6H4z", stroke: "currentColor", strokeWidth: 1.3, strokeLinejoin: "round"
|
|
358
|
+
}),
|
|
359
|
+
react.createElement("path", {
|
|
360
|
+
key: "b", d: "M9 1.9v4h3.9", stroke: "currentColor", strokeWidth: 1.3, strokeLinejoin: "round"
|
|
361
|
+
}),
|
|
362
|
+
react.createElement("path", {
|
|
363
|
+
key: "c", d: "M6 8.4h5M6 10.8h3.4", stroke: "currentColor", strokeWidth: 1.3, strokeLinecap: "round"
|
|
364
|
+
})
|
|
365
|
+
]);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function place(ctx, attempt = 0) {
|
|
369
|
+
try {
|
|
370
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
371
|
+
name: "sidebar.panellist",
|
|
372
|
+
id: PANEL_ID,
|
|
373
|
+
order: 30,
|
|
374
|
+
label: () => LABEL
|
|
375
|
+
}, ReportsIcon));
|
|
376
|
+
ctx.slots.inject("main", () => ctx.slots.register({
|
|
377
|
+
name: "main",
|
|
378
|
+
key: PANEL_ID
|
|
379
|
+
}, ReportsPanel));
|
|
380
|
+
return true;
|
|
381
|
+
} catch (error) {
|
|
382
|
+
if (attempt < 20) {
|
|
383
|
+
setTimeout(() => place(ctx, attempt + 1), 1000);
|
|
384
|
+
} else {
|
|
385
|
+
console.warn("[reports-panel] could not place the panel", error);
|
|
386
|
+
}
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const inject = ["slots", "remote", "remote.workspaceFiles"];
|
|
392
|
+
|
|
393
|
+
function apply(ctx) {
|
|
394
|
+
try {
|
|
395
|
+
panelContext = ctx;
|
|
396
|
+
refresh(ctx);
|
|
397
|
+
place(ctx);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.warn("[reports-panel] apply failed", error);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
exports.apply = apply;
|
|
404
|
+
exports.inject = inject;
|
|
405
|
+
return module.exports;
|
|
406
|
+
}
|
|
407
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Host half of dsh-reports-panel.
|
|
2
|
+
*
|
|
3
|
+
* Browser-only плагин: клиентская часть читает папку «Отчёты» через Remote
|
|
4
|
+
* `workspaceFiles` и рисует ленту в левой панели. Хосту нужна только строка
|
|
5
|
+
* в Loader — сервисов и конфигурации нет.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Loader display name. */
|
|
9
|
+
export const name = 'reports-panel'
|
|
10
|
+
|
|
11
|
+
/** No host service or configuration is required. */
|
|
12
|
+
export function apply() {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-reports-panel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Reports feed in the DSH left sidebar: files from the «Отчёты» folder of the session workspace, opened with one click",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"dsh": {
|
|
14
|
+
"bundle": {
|
|
15
|
+
"patch": "./cordis.patch.yml"
|
|
16
|
+
},
|
|
17
|
+
"client": {
|
|
18
|
+
"platform": "web",
|
|
19
|
+
"inject": [
|
|
20
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"lib",
|
|
26
|
+
"cordis.patch.yml",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"dsh-plugin",
|
|
35
|
+
"dsh",
|
|
36
|
+
"deepseek-harness",
|
|
37
|
+
"deepseek",
|
|
38
|
+
"harness",
|
|
39
|
+
"reports",
|
|
40
|
+
"documents",
|
|
41
|
+
"sidebar",
|
|
42
|
+
"workspace",
|
|
43
|
+
"feed"
|
|
44
|
+
],
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/Bayan1377/dsh-reports-panel.git"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/Bayan1377/dsh-reports-panel#readme",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/Bayan1377/dsh-reports-panel/issues"
|
|
52
|
+
},
|
|
53
|
+
"author": "bayan1377",
|
|
54
|
+
"license": "MIT"
|
|
55
|
+
}
|