dsh-brief-sidebar 0.9.6 → 0.9.8
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/dsh.plugin.json +1 -1
- package/lib/client.js +86 -7
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +8 -6
- package/lib/index.mjs.map +1 -1
- package/lib/types/client/summary/search-refs.d.ts +23 -1
- package/lib/types/projection/keys.d.ts +2 -2
- package/lib/types/projection/search-ref-fold.d.ts +10 -2
- package/package.json +1 -1
- package/src/client/summary/SearchRefSection.tsx +91 -46
- package/src/client/summary/search-refs.ts +69 -2
- package/src/projection/keys.ts +2 -2
- package/src/projection/register.ts +12 -4
- package/src/projection/search-ref-fold.ts +12 -6
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-brief-sidebar",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"main": "./lib/index.mjs",
|
|
5
5
|
"description": "会话简报侧栏页(brief):把会话的 todo 列表(`todos` 投影的进展看板)、产物列表(`dshSummaryDeliverables` 投影的实时产出文件)与记忆槽位(`memorySlots` 投影,来自 dsh-prime-memory,开关可关)一起渲染成右侧栏的一个「概要」tab,并遮蔽 DSH 官方那条 composer 上方的 todo 面板。只读展示,不做写回。",
|
|
6
6
|
"engines": {
|
package/lib/client.js
CHANGED
|
@@ -1622,18 +1622,30 @@ window.__ModuleLoader__.load({
|
|
|
1622
1622
|
function readTool(value) {
|
|
1623
1623
|
return typeof value === "string" && value.length > 0 ? value : "advanced_search";
|
|
1624
1624
|
}
|
|
1625
|
+
/**
|
|
1626
|
+
* The queries that surfaced a source, newest first.
|
|
1627
|
+
*
|
|
1628
|
+
* Two legacy shapes are repaired rather than dropped: a frame written before
|
|
1629
|
+
* the cluster change carries a single `query` string, and a drifted value
|
|
1630
|
+
* (non-array, or an array of junk) reads as "member of no cluster" — the row
|
|
1631
|
+
* still shows, it just joins the tail group.
|
|
1632
|
+
*/
|
|
1633
|
+
function readQueries(value) {
|
|
1634
|
+
if (Array.isArray(value)) return [...new Set(value.filter((q) => typeof q === "string" && q.length > 0))];
|
|
1635
|
+
if (typeof value === "string" && value.length > 0) return [value];
|
|
1636
|
+
return [];
|
|
1637
|
+
}
|
|
1625
1638
|
/** Narrow one source row; `undefined` = malformed, dropped. */
|
|
1626
1639
|
function readItem$1(value) {
|
|
1627
1640
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1628
1641
|
const raw = value;
|
|
1629
1642
|
if (typeof raw["url"] !== "string" || raw["url"].length === 0) return void 0;
|
|
1630
1643
|
if (typeof raw["host"] !== "string") return void 0;
|
|
1631
|
-
if (typeof raw["query"] !== "string") return void 0;
|
|
1632
1644
|
if (!isCounter$2(raw["count"]) || !isCounter$2(raw["lastTurn"])) return void 0;
|
|
1633
1645
|
const item = {
|
|
1634
1646
|
url: raw["url"],
|
|
1635
1647
|
host: raw["host"],
|
|
1636
|
-
|
|
1648
|
+
queries: readQueries(raw["queries"] ?? raw["query"]),
|
|
1637
1649
|
tool: readTool(raw["tool"]),
|
|
1638
1650
|
count: raw["count"],
|
|
1639
1651
|
lastTurn: raw["lastTurn"]
|
|
@@ -1699,6 +1711,43 @@ window.__ModuleLoader__.load({
|
|
|
1699
1711
|
return view.latest?.urls.includes(urlKey(url)) === true;
|
|
1700
1712
|
}
|
|
1701
1713
|
/**
|
|
1714
|
+
* Cluster the result bubbles under the queries that surfaced them.
|
|
1715
|
+
*
|
|
1716
|
+
* Membership is the source's own `queries` list, so a source a merged
|
|
1717
|
+
* multi-query `web_search` brought back appears under each of them — that is
|
|
1718
|
+
* what happened, and hiding the repeat would make the counts under the headers
|
|
1719
|
+
* disagree with the cards on screen. Queries come newest-first from the fold;
|
|
1720
|
+
* a source no listed query claims (its query aged out of the capped list, or a
|
|
1721
|
+
* pre-cluster frame) lands in a tail group rather than disappearing.
|
|
1722
|
+
*/
|
|
1723
|
+
function clusterByQuery(view) {
|
|
1724
|
+
const latestTurn = view.latest?.turn;
|
|
1725
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1726
|
+
const out = [];
|
|
1727
|
+
for (const row of view.queries) {
|
|
1728
|
+
const items = view.sources.filter((item) => item.queries.includes(row.query));
|
|
1729
|
+
for (const item of items) claimed.add(item.url);
|
|
1730
|
+
out.push({
|
|
1731
|
+
query: row.query,
|
|
1732
|
+
tool: row.tool,
|
|
1733
|
+
turn: row.turn,
|
|
1734
|
+
hits: row.hits,
|
|
1735
|
+
items,
|
|
1736
|
+
latest: latestTurn !== void 0 && row.turn === latestTurn
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
const rest = view.sources.filter((item) => !claimed.has(item.url));
|
|
1740
|
+
if (rest.length > 0) out.push({
|
|
1741
|
+
query: "",
|
|
1742
|
+
tool: rest[0]?.tool ?? "",
|
|
1743
|
+
turn: rest[0]?.lastTurn ?? 0,
|
|
1744
|
+
hits: rest.length,
|
|
1745
|
+
items: rest,
|
|
1746
|
+
latest: false
|
|
1747
|
+
});
|
|
1748
|
+
return out;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1702
1751
|
* The href to render for a source URL, or `undefined` when it must not be a link.
|
|
1703
1752
|
*
|
|
1704
1753
|
* Search results are untrusted external data: a `javascript:` or `data:` URL in
|
|
@@ -2230,6 +2279,7 @@ window.__ModuleLoader__.load({
|
|
|
2230
2279
|
function SearchRefSection(props) {
|
|
2231
2280
|
const { t, ctx, scope, hideHeader = false } = props;
|
|
2232
2281
|
const view = readSearchRefs(useProjectionValue(ctx, scope?.sessionId, SEARCH_REFS_KEY));
|
|
2282
|
+
const [openMap, setOpenMap] = (0, react.useState)({});
|
|
2233
2283
|
if (view === void 0) return null;
|
|
2234
2284
|
if (view.queries.length === 0 && view.sources.length === 0) return (0, react.createElement)("div", {
|
|
2235
2285
|
style: SECTION_STYLE$3,
|
|
@@ -2239,10 +2289,8 @@ window.__ModuleLoader__.load({
|
|
|
2239
2289
|
color: "var(--dsw-alias-label-secondary)"
|
|
2240
2290
|
} }, t("searchRefs.empty")), (0, react.createElement)("div", null, t("searchRefs.emptyHint"))));
|
|
2241
2291
|
const body = [];
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
style: LIST_STYLE$2
|
|
2245
|
-
}, view.sources.map((item) => {
|
|
2292
|
+
/** One result bubble; shared by every cluster so the cards stay identical. */
|
|
2293
|
+
const renderBubble = (item) => {
|
|
2246
2294
|
const href = safeHref(item.url);
|
|
2247
2295
|
const label = item.title ?? item.url;
|
|
2248
2296
|
const link = href === void 0 ? (0, react.createElement)("span", { style: TITLE_STYLE$2 }, label) : (0, react.createElement)("a", {
|
|
@@ -2260,7 +2308,38 @@ window.__ModuleLoader__.load({
|
|
|
2260
2308
|
style: BUBBLE_STYLE$1,
|
|
2261
2309
|
"data-dsh-brief-sidebar": "search-refs-bubble"
|
|
2262
2310
|
}, link, (0, react.createElement)("span", { style: HOST_STYLE }, item.host), item.snippet !== void 0 && (0, react.createElement)("p", { style: SNIPPET_STYLE }, item.snippet), (latest || item.count > 1) && (0, react.createElement)("div", { style: BUBBLE_FOOT_STYLE }, latest && (0, react.createElement)("span", { style: TAG_STYLE }, t("searchRefs.latestTag")), item.count > 1 && (0, react.createElement)("span", { style: HOST_STYLE }, `\u00d7${item.count}`))));
|
|
2263
|
-
}
|
|
2311
|
+
};
|
|
2312
|
+
if (view.sources.length > 0) for (const cluster of clusterByQuery(view)) {
|
|
2313
|
+
const key = cluster.query === "" ? "\0tail" : cluster.query;
|
|
2314
|
+
const shown = expandCount(t("searchRefs.hits"), cluster.items.length);
|
|
2315
|
+
if (cluster.items.length === 0) {
|
|
2316
|
+
body.push((0, react.createElement)(GroupHeader, {
|
|
2317
|
+
key: `h:${key}`,
|
|
2318
|
+
text: `${cluster.query} · ${shown}`,
|
|
2319
|
+
marker: "search-refs-cluster-empty"
|
|
2320
|
+
}));
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
const open = openMap[key] ?? cluster.latest;
|
|
2324
|
+
body.push((0, react.createElement)(FoldToggle, {
|
|
2325
|
+
key: `h:${key}`,
|
|
2326
|
+
label: cluster.query === "" ? expandCount(t("searchRefs.sourceGroup"), cluster.items.length) : `${cluster.query} · ${shown}`,
|
|
2327
|
+
open,
|
|
2328
|
+
marker: "search-refs-cluster",
|
|
2329
|
+
onToggle: () => {
|
|
2330
|
+
setOpenMap((prev) => ({
|
|
2331
|
+
...prev,
|
|
2332
|
+
[key]: !open
|
|
2333
|
+
}));
|
|
2334
|
+
}
|
|
2335
|
+
}));
|
|
2336
|
+
if (!open) continue;
|
|
2337
|
+
body.push((0, react.createElement)("ul", {
|
|
2338
|
+
key: `b:${key}`,
|
|
2339
|
+
style: LIST_STYLE$2,
|
|
2340
|
+
"data-dsh-brief-sidebar": "search-refs-cluster-items"
|
|
2341
|
+
}, cluster.items.map(renderBubble)));
|
|
2342
|
+
}
|
|
2264
2343
|
else body.push((0, react.createElement)("div", {
|
|
2265
2344
|
key: "ns",
|
|
2266
2345
|
style: FOOTER_STYLE$2,
|