pilotswarm 0.5.64 → 0.5.66
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/package.json +2 -2
- package/ui/core/src/index.js +1 -0
- package/ui/core/src/reducer.js +4 -3
- package/ui/core/src/selectors.js +144 -9
- package/ui/core/src/session-search.js +151 -0
- package/ui/react/src/web-app.js +105 -2
- package/web/dist/assets/{abnfDiagram-VRR7QNED-gIPHF2gV.js → abnfDiagram-VRR7QNED-B-04UhgW.js} +1 -1
- package/web/dist/assets/{arc-DMW_q5Ml.js → arc-C5XCnSQg.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DAKsrxcB.js → architectureDiagram-ZJ3FMSHR-R-xjkIQE.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-BUjSLpP3.js → blockDiagram-677ZJIJ3-DrthdK1L.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-Dw2nPoes.js → c4Diagram-LMCZKHZV-DRZ4aZB3.js} +1 -1
- package/web/dist/assets/channel-trVVWAw_.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-BAL28Rv0.js → chunk-2Q5K7J3B-Df7tPwEB.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-DDlt9K8s.js → chunk-32BRIVSS-bNd88tsn.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-BLQGJGoP.js → chunk-5VM5RSS4-ylLOcwFv.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-XFMb3HRy.js → chunk-EX3LRPZG-CWgx_6ev.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-DWnKTQ72.js → chunk-JWPE2WC7-BD-w4617.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-MltzhnwP.js → chunk-MOJQB5TN-De76fp2s.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-Ce3q0-mJ.js → chunk-RYQCIY6F-CpWyBMvz.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-ThXLYY88.js → chunk-V7JOEXUC-C-NJGbbs.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-DbhAykiW.js → chunk-VR4S4FIN-DK28nbHC.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BpkNB0mD.js → chunk-XXDRQBXY-Bk5aYmwf.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-Bsdx911B.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-Bsdx911B.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-rctqAPvj.js → cose-bilkent-JH36ORCC-OLeC_lx0.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-Dg1cltg6.js → cynefin-VYW2F7L2-C-gW4wVJ.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-Ci23Sv4Z.js → cynefinDiagram-TSTJHNR4-CFbownfZ.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-BpWTGhpv.js → dagre-VKFMJZFB-e4s9tuv2.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-B69JpiAe.js → diagram-FQU43EPY-BjE9UaN5.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-BHMk1TOO.js → diagram-G47NLZAW-_3b8-LSz.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-CtvZIIsk.js → diagram-NH7WQ7WH-2B55Dr-P.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-BFvKh800.js → diagram-OA4YK3LP-DI42Mpfa.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-CgB8sWPe.js → diagram-WEI45ONY-DwdeclWS.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-DyqPNydM.js → ebnfDiagram-CCIWWBDH-D0mgBmPT.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-CwpvZQ3Q.js → erDiagram-Q63AITRT-9uDmXDxu.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-EBKSKxfC.js → flowDiagram-23GEKE2U-czg0H1o9.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-q5tMPAuC.js → ganttDiagram-NO4QXBWP-klkCbthP.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-DCRbi4KE.js → gitGraphDiagram-IHSO6WYX-KkCVvJUf.js} +1 -1
- package/web/dist/assets/{index-BfF7e08d.js → index-CZgFyGc_.js} +2 -2
- package/web/dist/assets/index-D4OiytLj.css +1 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-Bex5pSIX.js → infoDiagram-FWYZ7A6U-Dt2S9vn1.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-Dj0li_TY.js → ishikawaDiagram-FXEZZL3T-Dwm-j64z.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-BtEGEeVr.js → journeyDiagram-5HDEW3XC-BF0hgIwR.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-BTFfZvYt.js → kanban-definition-HUTT4EX6-qu3pSjQ4.js} +1 -1
- package/web/dist/assets/{linear-C3SQCsip.js → linear-D2wggRGK.js} +1 -1
- package/web/dist/assets/{mermaid.core-DALDJDu8.js → mermaid.core-CEMxYLqP.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-BZf3uw0u.js → mindmap-definition-LN4V7U3C-DtDqO1Si.js} +1 -1
- package/web/dist/assets/{papaparse.min-Bh9MZ-fk.js → papaparse.min-Ca3zFRUj.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-PuOleiMg.js → pegDiagram-2B236MQR-Bptd8cxe.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-CcFORUaW.js → pieDiagram-ENE6RG2P-Cbvo_diJ.js} +1 -1
- package/web/dist/assets/pilotswarm-BJidJRMR.js +109 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CEm0mp4y.js → quadrantDiagram-ABIIQ3AL-Dsms43Mu.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-DeH-uX0_.js → railroadDiagram-RFXS5EU6-l5T4OGip.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-CMbJVMc1.js → requirementDiagram-TGXJPOKE-D7N_xPkI.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-C9gL5w_b.js → sankeyDiagram-HTMAVEWB-BSppPWwp.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-BUo8TT74.js → sequenceDiagram-DBY2YBRQ-drRDz5Uo.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-C5lqG0ei.js → sizeCapture-X5ZJPWSS-BljhCGnZ.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-DyeISrEU.js → stateDiagram-2N3HPSRC-CssZ3S0X.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DDkaZvTf.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-DawuBk_a.js → swimlanes-5IMT3BWC-Cap4r3qG.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-CiHBj-pL.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-BZJ2YJcm.js → timeline-definition-FHXFAJF6-CRlJg6Zj.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-BMXBVCfe.js → vennDiagram-L72KCM5P-BLgXv6xY.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-DWedKsrQ.js → wardleyDiagram-EHGQE667-EbTHIyRd.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG--XdfYceC.js → xychartDiagram-FW5EYKEG-DPR0SaNZ.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/dist/assets/channel-CcOa1LtY.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-B2VhHRSV.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-B2VhHRSV.js +0 -1
- package/web/dist/assets/index-uhc851H3.css +0 -1
- package/web/dist/assets/pilotswarm-B9rLtXpI.js +0 -109
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-5FZbaloZ.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-DL8PgtmM.js +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pilotswarm",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.66",
|
|
4
4
|
"description": "PilotSwarm application package: terminal UI, browser portal + Web API server, and MCP server — one install, three bins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"ink": "^6.8.0",
|
|
82
82
|
"jose": "^6.2.2",
|
|
83
83
|
"mermaid": "^11.16.0",
|
|
84
|
-
"pilotswarm-sdk": "0.5.
|
|
84
|
+
"pilotswarm-sdk": "0.5.66",
|
|
85
85
|
"react": "^19.2.4",
|
|
86
86
|
"react-dom": "^19.2.4",
|
|
87
87
|
"ws": "^8.18.2"
|
package/ui/core/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./layout.js";
|
|
|
6
6
|
export * from "./repo-links.js";
|
|
7
7
|
export * from "./repo-import.js";
|
|
8
8
|
export * from "./session-tree.js";
|
|
9
|
+
export * from "./session-search.js";
|
|
9
10
|
export * from "./state.js";
|
|
10
11
|
export * from "./reducer.js";
|
|
11
12
|
export * from "./portal-links.js";
|
package/ui/core/src/reducer.js
CHANGED
|
@@ -1385,11 +1385,12 @@ function baseReducer(state, action) {
|
|
|
1385
1385
|
filterExceptionId: null,
|
|
1386
1386
|
ownerFilterAutoAdmitted: null,
|
|
1387
1387
|
};
|
|
1388
|
-
const selection = applyVisibleSessionSelection(state, nextSessions);
|
|
1389
1388
|
return {
|
|
1390
1389
|
...state,
|
|
1391
|
-
|
|
1392
|
-
|
|
1390
|
+
// Search filters the list, not the attached chat. Keeping
|
|
1391
|
+
// the active session stable also prevents a keystroke in
|
|
1392
|
+
// the search box from moving the list and main pane.
|
|
1393
|
+
sessions: nextSessions,
|
|
1393
1394
|
};
|
|
1394
1395
|
}
|
|
1395
1396
|
|
package/ui/core/src/selectors.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { INSPECTOR_TABS, FOCUS_REGIONS } from "./commands.js";
|
|
2
2
|
import { chatCallLine, firstCallLine } from "./chat-activity.js";
|
|
3
3
|
import { canvasKey as canvasSlotKey, parseCanvasKey } from "./state.js";
|
|
4
|
-
import { isManuallyOrderableSession } from "./session-tree.js";
|
|
4
|
+
import { buildSessionTree, isManuallyOrderableSession } from "./session-tree.js";
|
|
5
|
+
import {
|
|
6
|
+
buildSessionSearchDocument,
|
|
7
|
+
parseSessionSearchQuery,
|
|
8
|
+
scoreSessionSearchDocument,
|
|
9
|
+
} from "./session-search.js";
|
|
5
10
|
import {
|
|
6
11
|
createSplashCard,
|
|
7
12
|
isRehydrationNoticeText,
|
|
@@ -1287,6 +1292,132 @@ function matchesSearchQuery(value, query) {
|
|
|
1287
1292
|
return String(value || "").toLowerCase().includes(normalizedQuery);
|
|
1288
1293
|
}
|
|
1289
1294
|
|
|
1295
|
+
const searchedSessionFlatCache = new WeakMap();
|
|
1296
|
+
|
|
1297
|
+
function sessionGroupTitle(session, byId) {
|
|
1298
|
+
if (session?.isGroup) return session.title || "";
|
|
1299
|
+
if (session?.groupId) return byId?.[`group:${session.groupId}`]?.title || "";
|
|
1300
|
+
let current = session;
|
|
1301
|
+
const seen = new Set();
|
|
1302
|
+
while (current?.parentSessionId && !seen.has(current.parentSessionId)) {
|
|
1303
|
+
seen.add(current.parentSessionId);
|
|
1304
|
+
current = byId?.[current.parentSessionId];
|
|
1305
|
+
if (current?.groupId) return byId?.[`group:${current.groupId}`]?.title || "";
|
|
1306
|
+
}
|
|
1307
|
+
return "";
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function sessionSearchOwner(session, byId) {
|
|
1311
|
+
const direct = effectiveSessionOwner(session, byId);
|
|
1312
|
+
if (direct) return direct;
|
|
1313
|
+
let current = session;
|
|
1314
|
+
const seen = new Set();
|
|
1315
|
+
while (current?.parentSessionId && !seen.has(current.parentSessionId)) {
|
|
1316
|
+
seen.add(current.parentSessionId);
|
|
1317
|
+
current = byId?.[current.parentSessionId];
|
|
1318
|
+
if (current?.owner) return current.owner;
|
|
1319
|
+
}
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function buildSearchedSessionFlat(state, query) {
|
|
1324
|
+
const existingFlat = Array.isArray(state.sessions?.flat) ? state.sessions.flat : [];
|
|
1325
|
+
if (!query) return existingFlat;
|
|
1326
|
+
const cached = searchedSessionFlatCache.get(existingFlat);
|
|
1327
|
+
if (cached?.query === query) return cached.result;
|
|
1328
|
+
|
|
1329
|
+
const byId = state.sessions?.byId || {};
|
|
1330
|
+
const parsed = parseSessionSearchQuery(query);
|
|
1331
|
+
const expanded = buildSessionTree(
|
|
1332
|
+
Object.values(byId),
|
|
1333
|
+
new Set(),
|
|
1334
|
+
state.sessions?.orderById,
|
|
1335
|
+
state.sessions?.pinnedIds,
|
|
1336
|
+
state.sessions?.manualOrder,
|
|
1337
|
+
);
|
|
1338
|
+
const parentById = new Map();
|
|
1339
|
+
const entryById = new Map();
|
|
1340
|
+
const baseIndex = new Map();
|
|
1341
|
+
const stack = [];
|
|
1342
|
+
for (let index = 0; index < expanded.length; index += 1) {
|
|
1343
|
+
const entry = expanded[index];
|
|
1344
|
+
stack[entry.depth] = entry.sessionId;
|
|
1345
|
+
stack.length = entry.depth + 1;
|
|
1346
|
+
parentById.set(entry.sessionId, entry.depth > 0 ? stack[entry.depth - 1] : null);
|
|
1347
|
+
entryById.set(entry.sessionId, entry);
|
|
1348
|
+
baseIndex.set(entry.sessionId, index);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const directScores = new Map();
|
|
1352
|
+
for (const entry of expanded) {
|
|
1353
|
+
const session = byId[entry.sessionId] || entry.standIn;
|
|
1354
|
+
if (!session || !matchesOwnerFilter(session, state.sessions?.ownerFilter, state.auth || {}, byId)) continue;
|
|
1355
|
+
const owner = sessionSearchOwner(session, byId);
|
|
1356
|
+
const document = buildSessionSearchDocument(session, {
|
|
1357
|
+
owner,
|
|
1358
|
+
groupTitle: sessionGroupTitle(session, byId),
|
|
1359
|
+
});
|
|
1360
|
+
const score = scoreSessionSearchDocument(document, parsed);
|
|
1361
|
+
if (score > 0) directScores.set(entry.sessionId, score);
|
|
1362
|
+
}
|
|
1363
|
+
const matchedIds = new Set(directScores.keys());
|
|
1364
|
+
// A just-created or deep-linked session may be admitted explicitly while
|
|
1365
|
+
// the user's prior query still excludes it. Preserve that established
|
|
1366
|
+
// visibility contract and rank the exception first until the user edits
|
|
1367
|
+
// the search (which clears filterExceptionId in the reducer).
|
|
1368
|
+
const exceptionId = state.sessions?.filterExceptionId;
|
|
1369
|
+
if (exceptionId && entryById.has(exceptionId)) {
|
|
1370
|
+
directScores.set(exceptionId, Number.MAX_SAFE_INTEGER);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const included = new Set();
|
|
1374
|
+
const subtreeScores = new Map();
|
|
1375
|
+
for (const [sessionId, score] of directScores) {
|
|
1376
|
+
let current = sessionId;
|
|
1377
|
+
const seen = new Set();
|
|
1378
|
+
while (current && !seen.has(current)) {
|
|
1379
|
+
seen.add(current);
|
|
1380
|
+
included.add(current);
|
|
1381
|
+
subtreeScores.set(current, Math.max(subtreeScores.get(current) || 0, score));
|
|
1382
|
+
current = parentById.get(current) || null;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
const children = new Map();
|
|
1387
|
+
for (const sessionId of included) {
|
|
1388
|
+
const parentId = parentById.get(sessionId) || null;
|
|
1389
|
+
if (!children.has(parentId)) children.set(parentId, []);
|
|
1390
|
+
children.get(parentId).push(sessionId);
|
|
1391
|
+
}
|
|
1392
|
+
for (const siblings of children.values()) {
|
|
1393
|
+
siblings.sort((a, b) => (subtreeScores.get(b) || 0) - (subtreeScores.get(a) || 0)
|
|
1394
|
+
|| (baseIndex.get(a) || 0) - (baseIndex.get(b) || 0));
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const result = [];
|
|
1398
|
+
const visit = (sessionId, depth) => {
|
|
1399
|
+
const entry = entryById.get(sessionId);
|
|
1400
|
+
if (!entry) return;
|
|
1401
|
+
const childIds = children.get(sessionId) || [];
|
|
1402
|
+
result.push({
|
|
1403
|
+
...entry,
|
|
1404
|
+
depth,
|
|
1405
|
+
collapsed: false,
|
|
1406
|
+
hasChildren: childIds.length > 0,
|
|
1407
|
+
searchIncluded: true,
|
|
1408
|
+
searchMatch: matchedIds.has(sessionId),
|
|
1409
|
+
searchScore: directScores.get(sessionId) || 0,
|
|
1410
|
+
});
|
|
1411
|
+
for (const childId of childIds) visit(childId, depth + 1);
|
|
1412
|
+
};
|
|
1413
|
+
for (const rootId of children.get(null) || []) visit(rootId, 0);
|
|
1414
|
+
// A user can type an unbounded number of distinct queries while the
|
|
1415
|
+
// catalog array keeps the same identity. Keep only the latest result for
|
|
1416
|
+
// that catalog rather than retaining every intermediate keystroke.
|
|
1417
|
+
searchedSessionFlatCache.set(existingFlat, { query, result });
|
|
1418
|
+
return result;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1290
1421
|
// The transient deep-link filter exception covers the linked session AND its
|
|
1291
1422
|
// ancestor chain (real parents plus the synthetic group:<id> row) so the
|
|
1292
1423
|
// linked row renders with its tree context instead of as an orphan.
|
|
@@ -1330,22 +1461,25 @@ function sameRowDeps(a, b) {
|
|
|
1330
1461
|
}
|
|
1331
1462
|
|
|
1332
1463
|
export function selectSessionRows(state) {
|
|
1464
|
+
// Keep field punctuation and quotes for the parser (`author:"Ada Lovelace"`).
|
|
1465
|
+
// Individual values are normalized inside session-search.
|
|
1466
|
+
const query = String(state.sessions?.filterQuery || "").trim();
|
|
1467
|
+
const sessionFlat = buildSearchedSessionFlat(state, query);
|
|
1333
1468
|
const totalDescendantCounts = getTotalDescendantCounts(state.sessions.byId);
|
|
1334
|
-
const visibleDescendantCounts = getVisibleDescendantCounts(
|
|
1335
|
-
const query = normalizeSearchQuery(state.sessions?.filterQuery || "");
|
|
1469
|
+
const visibleDescendantCounts = getVisibleDescendantCounts(sessionFlat, state.sessions.byId);
|
|
1336
1470
|
const ownerFilter = state.sessions?.ownerFilter || { all: true };
|
|
1337
1471
|
const auth = state.auth || {};
|
|
1338
1472
|
const pinnedSet = new Set(Array.isArray(state.sessions?.pinnedIds) ? state.sessions.pinnedIds : []);
|
|
1339
1473
|
const selectedSet = new Set(Array.isArray(state.sessions?.selectedIds) ? state.sessions.selectedIds : []);
|
|
1340
1474
|
const filterExceptionIds = collectFilterExceptionIds(state.sessions);
|
|
1341
1475
|
|
|
1342
|
-
let memo = sessionRowMemo.get(
|
|
1476
|
+
let memo = sessionRowMemo.get(sessionFlat);
|
|
1343
1477
|
if (!memo) {
|
|
1344
1478
|
memo = new Map();
|
|
1345
|
-
sessionRowMemo.set(
|
|
1479
|
+
sessionRowMemo.set(sessionFlat, memo);
|
|
1346
1480
|
}
|
|
1347
1481
|
|
|
1348
|
-
return
|
|
1482
|
+
return sessionFlat.filter((entry) => (
|
|
1349
1483
|
// A row whose session is in neither place can render nothing but an
|
|
1350
1484
|
// owner chip and a dash - the "[?]" ghost. Drop it rather than show it.
|
|
1351
1485
|
Boolean(state.sessions.byId[entry.sessionId] || entry.standIn)
|
|
@@ -1433,10 +1567,13 @@ export function selectSessionRows(state) {
|
|
|
1433
1567
|
// `chrome` as well so a host that draws its own row (the phone's
|
|
1434
1568
|
// list) can act on it without reading the portal's descriptor.
|
|
1435
1569
|
pause: rowView.chrome?.pause || null,
|
|
1570
|
+
searchMatch: entry.searchMatch === true,
|
|
1571
|
+
searchScore: Number(entry.searchScore) || 0,
|
|
1436
1572
|
};
|
|
1437
1573
|
memo.set(entry.sessionId, { deps, row });
|
|
1438
1574
|
return row;
|
|
1439
1575
|
}).filter((row) => {
|
|
1576
|
+
if (query) return true; // searchedSessionFlat already contains only matches and their ancestors.
|
|
1440
1577
|
if (filterExceptionIds?.has(row.sessionId)) return true;
|
|
1441
1578
|
const session = state.sessions.byId[row.sessionId];
|
|
1442
1579
|
if (!matchesOwnerFilter(session, ownerFilter, auth, state.sessions.byId)) return false;
|
|
@@ -1444,11 +1581,9 @@ export function selectSessionRows(state) {
|
|
|
1444
1581
|
const ownerSearchText = effectiveOwner
|
|
1445
1582
|
? `${ownerDisplayName(effectiveOwner, "")} ${effectiveOwner.email || ""}`
|
|
1446
1583
|
: "";
|
|
1447
|
-
const summarySearchText = "";
|
|
1448
1584
|
return matchesSearchQuery(row.text, query)
|
|
1449
1585
|
|| matchesSearchQuery(row.sessionId, query)
|
|
1450
|
-
|| matchesSearchQuery(ownerSearchText, query)
|
|
1451
|
-
|| matchesSearchQuery(summarySearchText, query);
|
|
1586
|
+
|| matchesSearchQuery(ownerSearchText, query);
|
|
1452
1587
|
});
|
|
1453
1588
|
}
|
|
1454
1589
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const FIELD_ALIASES = Object.freeze({
|
|
2
|
+
author: "author",
|
|
3
|
+
owner: "author",
|
|
4
|
+
agent: "agent",
|
|
5
|
+
status: "status",
|
|
6
|
+
group: "group",
|
|
7
|
+
folder: "group",
|
|
8
|
+
model: "model",
|
|
9
|
+
id: "id",
|
|
10
|
+
topic: "topic",
|
|
11
|
+
summary: "summary",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const FREE_FIELD_WEIGHTS = Object.freeze({
|
|
15
|
+
title: 12,
|
|
16
|
+
id: 11,
|
|
17
|
+
author: 10,
|
|
18
|
+
agent: 9,
|
|
19
|
+
summary: 7,
|
|
20
|
+
group: 6,
|
|
21
|
+
model: 5,
|
|
22
|
+
status: 4,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export function normalizeSessionSearchText(value) {
|
|
26
|
+
return String(value || "")
|
|
27
|
+
.normalize("NFKD")
|
|
28
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^a-z0-9@._+-]+/g, " ")
|
|
31
|
+
.trim()
|
|
32
|
+
.replace(/\s+/g, " ");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseSessionSearchQuery(value) {
|
|
36
|
+
const source = String(value || "").trim();
|
|
37
|
+
const terms = [];
|
|
38
|
+
const filters = [];
|
|
39
|
+
const pattern = /(?:(\w+):)?(?:"([^"]+)"|(\S+))/g;
|
|
40
|
+
let match;
|
|
41
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
42
|
+
const rawField = String(match[1] || "").toLowerCase();
|
|
43
|
+
const rawValue = match[2] ?? match[3] ?? "";
|
|
44
|
+
const normalized = normalizeSessionSearchText(rawValue);
|
|
45
|
+
if (!normalized) continue;
|
|
46
|
+
const field = FIELD_ALIASES[rawField];
|
|
47
|
+
if (field) filters.push({ field, value: normalized });
|
|
48
|
+
else terms.push(normalizeSessionSearchText(rawField ? `${rawField} ${rawValue}` : rawValue));
|
|
49
|
+
}
|
|
50
|
+
return { source, terms: terms.filter(Boolean), filters };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function boundedDistance(a, b, limit) {
|
|
54
|
+
if (Math.abs(a.length - b.length) > limit) return limit + 1;
|
|
55
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
56
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
57
|
+
const current = [i];
|
|
58
|
+
let rowMinimum = current[0];
|
|
59
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
60
|
+
const value = Math.min(
|
|
61
|
+
current[j - 1] + 1,
|
|
62
|
+
previous[j] + 1,
|
|
63
|
+
previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
64
|
+
);
|
|
65
|
+
current.push(value);
|
|
66
|
+
rowMinimum = Math.min(rowMinimum, value);
|
|
67
|
+
}
|
|
68
|
+
if (rowMinimum > limit) return limit + 1;
|
|
69
|
+
previous = current;
|
|
70
|
+
}
|
|
71
|
+
return previous[b.length];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function matchStrength(haystack, needle) {
|
|
75
|
+
if (!haystack || !needle) return 0;
|
|
76
|
+
if (haystack === needle) return 16;
|
|
77
|
+
const words = haystack.split(" ");
|
|
78
|
+
if (words.includes(needle)) return 14;
|
|
79
|
+
if (haystack.includes(needle)) return needle.includes(" ") ? 13 : 11;
|
|
80
|
+
if (needle.length >= 2 && words.some((word) => word.startsWith(needle))) return 9;
|
|
81
|
+
if (needle.length >= 4 && words.some((word) => word.includes(needle))) return 7;
|
|
82
|
+
if (needle.length < 4) return 0;
|
|
83
|
+
// One adjacent transposition is two edits under Levenshtein distance
|
|
84
|
+
// ("session" → "sessoin"). Permit that for ordinary words while keeping
|
|
85
|
+
// short tokens exact/prefix-only.
|
|
86
|
+
const limit = needle.length >= 6 ? 2 : 1;
|
|
87
|
+
return words.some((word) => boundedDistance(word, needle, limit) <= limit) ? 4 : 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function summaryStrings(summaryState) {
|
|
91
|
+
if (!summaryState || typeof summaryState !== "object") return [];
|
|
92
|
+
return [summaryState.topic, summaryState.intent, summaryState.summary, summaryState.title]
|
|
93
|
+
.filter((value) => typeof value === "string" && value.trim());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildSessionSearchDocument(session, { owner = null, groupTitle = "" } = {}) {
|
|
97
|
+
const title = normalizeSessionSearchText(session?.title);
|
|
98
|
+
const summary = normalizeSessionSearchText([
|
|
99
|
+
session?.shortSummary,
|
|
100
|
+
...summaryStrings(session?.summaryState),
|
|
101
|
+
].filter(Boolean).join(" "));
|
|
102
|
+
const author = normalizeSessionSearchText([
|
|
103
|
+
owner?.displayName,
|
|
104
|
+
owner?.email,
|
|
105
|
+
owner?.subject,
|
|
106
|
+
].filter(Boolean).join(" "));
|
|
107
|
+
return {
|
|
108
|
+
title,
|
|
109
|
+
summary,
|
|
110
|
+
topic: normalizeSessionSearchText(`${title} ${summary}`),
|
|
111
|
+
author,
|
|
112
|
+
agent: normalizeSessionSearchText(session?.agentId),
|
|
113
|
+
status: normalizeSessionSearchText(session?.status),
|
|
114
|
+
group: normalizeSessionSearchText(groupTitle),
|
|
115
|
+
model: normalizeSessionSearchText(`${session?.model || ""} ${session?.reasoningEffort || ""}`),
|
|
116
|
+
id: normalizeSessionSearchText(session?.sessionId),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Score one already-authorized, browser-resident session. Structured filters
|
|
122
|
+
* and free-text terms are ANDed; each free term may match any field. Exact,
|
|
123
|
+
* prefix and substring matches beat bounded typo matches.
|
|
124
|
+
*/
|
|
125
|
+
export function scoreSessionSearchDocument(document, query) {
|
|
126
|
+
const parsed = typeof query === "string" ? parseSessionSearchQuery(query) : query;
|
|
127
|
+
if (!parsed || (parsed.terms.length === 0 && parsed.filters.length === 0)) return 1;
|
|
128
|
+
let score = 0;
|
|
129
|
+
for (const filter of parsed.filters) {
|
|
130
|
+
const strength = matchStrength(document?.[filter.field] || "", filter.value);
|
|
131
|
+
if (!strength) return 0;
|
|
132
|
+
score += strength * 14;
|
|
133
|
+
}
|
|
134
|
+
for (const term of parsed.terms) {
|
|
135
|
+
let best = 0;
|
|
136
|
+
for (const [field, weight] of Object.entries(FREE_FIELD_WEIGHTS)) {
|
|
137
|
+
// Short numbers such as "Session 2" occur in the fixed portions
|
|
138
|
+
// of every UUID. Treat IDs as an explicit/long-fragment field so
|
|
139
|
+
// the human title remains the natural interpretation; `id:...`
|
|
140
|
+
// is always available for deliberate ID lookup.
|
|
141
|
+
if (field === "id" && (/^\d+$/.test(term) || term.length < 8)) continue;
|
|
142
|
+
best = Math.max(best, matchStrength(document?.[field] || "", term) * weight);
|
|
143
|
+
}
|
|
144
|
+
if (!best) return 0;
|
|
145
|
+
score += best;
|
|
146
|
+
}
|
|
147
|
+
const phrase = parsed.terms.join(" ");
|
|
148
|
+
if (phrase && document?.title?.includes(phrase)) score += 120;
|
|
149
|
+
else if (phrase && document?.summary?.includes(phrase)) score += 60;
|
|
150
|
+
return score;
|
|
151
|
+
}
|
package/ui/react/src/web-app.js
CHANGED
|
@@ -5053,6 +5053,71 @@ function useAxisLockedPan(ref, enabled = true) {
|
|
|
5053
5053
|
}, [ref, enabled]);
|
|
5054
5054
|
}
|
|
5055
5055
|
|
|
5056
|
+
function SessionSearchControl({ query = "", onQuery, matchCount = 0, mobile = false, listRef = null }) {
|
|
5057
|
+
const [mobileOpen, setMobileOpen] = React.useState(Boolean(query));
|
|
5058
|
+
const inputRef = React.useRef(null);
|
|
5059
|
+
React.useEffect(() => {
|
|
5060
|
+
if (query) setMobileOpen(true);
|
|
5061
|
+
}, [query]);
|
|
5062
|
+
const open = () => {
|
|
5063
|
+
setMobileOpen(true);
|
|
5064
|
+
requestAnimationFrame(() => inputRef.current?.focus({ preventScroll: true }));
|
|
5065
|
+
};
|
|
5066
|
+
const closeOrClear = () => {
|
|
5067
|
+
if (query) onQuery?.("");
|
|
5068
|
+
else setMobileOpen(false);
|
|
5069
|
+
};
|
|
5070
|
+
const resultLabel = query
|
|
5071
|
+
? `${matchCount} ${matchCount === 1 ? "match" : "matches"}`
|
|
5072
|
+
: "";
|
|
5073
|
+
return React.createElement("div", {
|
|
5074
|
+
className: `ps-session-search${mobileOpen ? " is-open" : ""}`,
|
|
5075
|
+
"data-session-search": "true",
|
|
5076
|
+
},
|
|
5077
|
+
mobile ? React.createElement("button", {
|
|
5078
|
+
type: "button",
|
|
5079
|
+
className: "ps-session-search-trigger",
|
|
5080
|
+
"aria-label": "Search sessions",
|
|
5081
|
+
title: "Search sessions",
|
|
5082
|
+
onClick: open,
|
|
5083
|
+
}, React.createElement(SearchGlyph)) : null,
|
|
5084
|
+
React.createElement("div", { className: "ps-session-search-fields" },
|
|
5085
|
+
React.createElement(SearchGlyph),
|
|
5086
|
+
React.createElement("input", {
|
|
5087
|
+
ref: inputRef,
|
|
5088
|
+
type: "text",
|
|
5089
|
+
inputMode: "search",
|
|
5090
|
+
"aria-label": "Find a session",
|
|
5091
|
+
placeholder: "Find a session…",
|
|
5092
|
+
value: query,
|
|
5093
|
+
onChange: (event) => onQuery?.(event.target.value),
|
|
5094
|
+
onKeyDown: (event) => {
|
|
5095
|
+
if (event.key === "Escape") {
|
|
5096
|
+
event.preventDefault();
|
|
5097
|
+
closeOrClear();
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
if (event.key !== "ArrowDown") return;
|
|
5101
|
+
const first = listRef?.current?.querySelector?.(".ps-session-list-button");
|
|
5102
|
+
if (!first) return;
|
|
5103
|
+
event.preventDefault();
|
|
5104
|
+
first.focus({ preventScroll: true });
|
|
5105
|
+
},
|
|
5106
|
+
}),
|
|
5107
|
+
resultLabel ? React.createElement("span", {
|
|
5108
|
+
className: "ps-session-search-count",
|
|
5109
|
+
role: "status",
|
|
5110
|
+
"aria-live": "polite",
|
|
5111
|
+
}, resultLabel) : null,
|
|
5112
|
+
(query || mobile) ? React.createElement("button", {
|
|
5113
|
+
type: "button",
|
|
5114
|
+
className: "ps-session-search-clear",
|
|
5115
|
+
"aria-label": query ? "Clear session search" : "Close session search",
|
|
5116
|
+
title: query ? "Clear" : "Close",
|
|
5117
|
+
onClick: closeOrClear,
|
|
5118
|
+
}, "×") : null));
|
|
5119
|
+
}
|
|
5120
|
+
|
|
5056
5121
|
function SessionPane({ controller, actions = null, panelClassName = "", structuredRows = false, showDetailBox = null, selection = null, actionsOnly = false, actionsHost = null, onAction = null, onDialogChange = null }) {
|
|
5057
5122
|
// Mobile keeps its inline detail line and normally gets no detail box — a
|
|
5058
5123
|
// reserved footer would eat a meaningful slice of a phone screen. The
|
|
@@ -5138,6 +5203,32 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5138
5203
|
}), [viewState.activeSessionId, viewState.auth, viewState.branding, viewState.budgetPaused, viewState.canvasBySessionId, viewState.canvasPrefs, viewState.connectionMode, viewState.filterQuery, viewState.listDeselected, viewState.ownerFilter, viewState.pinnedIds, viewState.manualOrder, viewState.selectedIds, viewState.selectMode, viewState.sessionsById, viewState.sessionsFlat]);
|
|
5139
5204
|
// Hold the previous rows when a poll produced identical output.
|
|
5140
5205
|
const rows = useStableValue(computedRows);
|
|
5206
|
+
const searchScrollRef = React.useRef({ top: 0, left: 0, restore: false });
|
|
5207
|
+
const searchEnvRef = React.useRef(null);
|
|
5208
|
+
searchEnvRef.current = {
|
|
5209
|
+
query: viewState.filterQuery,
|
|
5210
|
+
setQuery: selection ? selection.onQuery : (value) => controller.setSessionFilterQuery(value),
|
|
5211
|
+
};
|
|
5212
|
+
const setSearchQuery = React.useCallback((value) => {
|
|
5213
|
+
const next = String(value || "");
|
|
5214
|
+
const current = String(searchEnvRef.current?.query || "");
|
|
5215
|
+
const list = sessionListRef.current;
|
|
5216
|
+
if (!current.trim() && next.trim() && list) {
|
|
5217
|
+
searchScrollRef.current = { top: list.scrollTop, left: list.scrollLeft, restore: false };
|
|
5218
|
+
} else if (current.trim() && !next.trim()) {
|
|
5219
|
+
searchScrollRef.current.restore = true;
|
|
5220
|
+
}
|
|
5221
|
+
searchEnvRef.current?.setQuery?.(next);
|
|
5222
|
+
}, []);
|
|
5223
|
+
React.useLayoutEffect(() => {
|
|
5224
|
+
if (viewState.filterQuery || !searchScrollRef.current.restore || !sessionListRef.current) return;
|
|
5225
|
+
sessionListRef.current.scrollTop = searchScrollRef.current.top;
|
|
5226
|
+
sessionListRef.current.scrollLeft = searchScrollRef.current.left;
|
|
5227
|
+
searchScrollRef.current.restore = false;
|
|
5228
|
+
}, [rows, viewState.filterQuery]);
|
|
5229
|
+
const searchMatchCount = viewState.filterQuery
|
|
5230
|
+
? rows.reduce((count, row) => count + (row.searchMatch ? 1 : 0), 0)
|
|
5231
|
+
: 0;
|
|
5141
5232
|
const activeSession = viewState.activeSessionId
|
|
5142
5233
|
? viewState.sessionsById[viewState.activeSessionId] || null
|
|
5143
5234
|
: null;
|
|
@@ -5845,7 +5936,6 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5845
5936
|
}) : null, actions) : panelActions,
|
|
5846
5937
|
className: combinedPanelClassName,
|
|
5847
5938
|
},
|
|
5848
|
-
selection ? React.createElement("input", { className: "ps-modal-input", "aria-label": "Find a session", placeholder: "Find a session…", value: selection.query || "", onChange: e => selection.onQuery?.(e.target.value) }) : null,
|
|
5849
5939
|
React.createElement("div", {
|
|
5850
5940
|
ref: sessionListRef,
|
|
5851
5941
|
onKeyDown: selection ? event => {
|
|
@@ -5873,7 +5963,7 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5873
5963
|
|
|
5874
5964
|
rows.length === 0
|
|
5875
5965
|
? React.createElement("div", { className: "ps-empty-state" }, viewState.filterQuery
|
|
5876
|
-
? `No sessions matched "
|
|
5966
|
+
? `No sessions matched "${viewState.filterQuery}".`
|
|
5877
5967
|
: "No sessions yet.")
|
|
5878
5968
|
: rows.map((row) => React.createElement(SessionListRow, {
|
|
5879
5969
|
key: row.sessionId,
|
|
@@ -5887,6 +5977,13 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
5887
5977
|
// touch hijacks the finger that should be scrolling the list.
|
|
5888
5978
|
drag: selection || touchInput ? null : dragHandlers,
|
|
5889
5979
|
}))),
|
|
5980
|
+
React.createElement(SessionSearchControl, {
|
|
5981
|
+
query: viewState.filterQuery,
|
|
5982
|
+
onQuery: setSearchQuery,
|
|
5983
|
+
matchCount: searchMatchCount,
|
|
5984
|
+
mobile: isMobilePane,
|
|
5985
|
+
listRef: sessionListRef,
|
|
5986
|
+
}),
|
|
5890
5987
|
(showDetailBox === null ? !isMobilePane : showDetailBox)
|
|
5891
5988
|
? React.createElement(SessionDetailBox, {
|
|
5892
5989
|
session: activeSession,
|
|
@@ -5994,6 +6091,12 @@ function FunnelGlyph() {
|
|
|
5994
6091
|
React.createElement("path", { d: "M21 4H3l7.2 8.5V19l3.6 2v-8.5L21 4z" }));
|
|
5995
6092
|
}
|
|
5996
6093
|
|
|
6094
|
+
function SearchGlyph() {
|
|
6095
|
+
return React.createElement(Glyph, null,
|
|
6096
|
+
React.createElement("circle", { cx: "11", cy: "11", r: "7" }),
|
|
6097
|
+
React.createElement("path", { d: "m20 20-4-4" }));
|
|
6098
|
+
}
|
|
6099
|
+
|
|
5997
6100
|
// Summary — a written summary. Was "≣", the same codepoint the Logs tab used.
|
|
5998
6101
|
|
|
5999
6102
|
// Back to the transcript. Replaces the 💬 emoji, which iOS force-renders in
|
package/web/dist/assets/{abnfDiagram-VRR7QNED-gIPHF2gV.js → abnfDiagram-VRR7QNED-B-04UhgW.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-
|
|
1
|
+
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-De76fp2s.js";import{p as f}from"./chunk-JWPE2WC7-BD-w4617.js";import{_ as n,l as o}from"./mermaid.core-CEMxYLqP.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-C-gW4wVJ.js";import"./pilotswarm-BJidJRMR.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-
|
|
1
|
+
import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-CEMxYLqP.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
|