@peers-app/peers-ui 0.25.21 → 0.26.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/dist/mention-configs.d.ts +7 -0
- package/dist/mention-configs.js +8 -2
- package/dist/screens/assistants/assistant-config.d.ts +1 -0
- package/dist/screens/assistants/assistant-config.js +20 -3
- package/dist/screens/assistants/assistant-config.test.d.ts +1 -0
- package/dist/screens/assistants/assistant-config.test.js +57 -0
- package/dist/screens/settings/settings-page.js +1 -59
- package/dist/screens/settings/settings-page.test.d.ts +1 -0
- package/dist/screens/settings/settings-page.test.js +19 -0
- package/dist/screens/tools/tool-catalog-ipc.d.ts +118 -0
- package/dist/screens/tools/tool-catalog-ipc.js +48 -0
- package/dist/screens/tools/tool-catalog-ipc.test.d.ts +1 -0
- package/dist/screens/tools/tool-catalog-ipc.test.js +46 -0
- package/dist/screens/tools/tool-details.d.ts +3 -4
- package/dist/screens/tools/tool-details.js +14 -33
- package/dist/screens/tools/tool-info.d.ts +5 -4
- package/dist/screens/tools/tool-info.js +10 -30
- package/dist/screens/tools/tool-list.d.ts +1 -0
- package/dist/screens/tools/tool-list.js +2 -26
- package/dist/screens/tools/tool-schema.d.ts +5 -3
- package/dist/screens/tools/tool-schema.js +5 -14
- package/package.json +3 -3
- package/src/mention-configs.ts +9 -5
- package/src/screens/assistants/assistant-config.test.tsx +54 -0
- package/src/screens/assistants/assistant-config.tsx +42 -11
- package/src/screens/settings/settings-page.test.tsx +24 -0
- package/src/screens/settings/settings-page.tsx +1 -255
- package/src/screens/tools/tool-catalog-ipc.test.ts +46 -0
- package/src/screens/tools/tool-catalog-ipc.ts +51 -0
- package/src/screens/tools/tool-details.tsx +18 -67
- package/src/screens/tools/tool-info.tsx +39 -53
- package/src/screens/tools/tool-list.tsx +4 -37
- package/src/screens/tools/tool-schema.tsx +40 -45
- package/dist/screens/tools/tool-code.d.ts +0 -4
- package/dist/screens/tools/tool-code.js +0 -28
- package/src/screens/tools/tool-code.tsx +0 -30
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
Users,
|
|
9
9
|
} from "@peers-app/peers-sdk";
|
|
10
10
|
import type React from "react";
|
|
11
|
-
import { useEffect,
|
|
11
|
+
import { useEffect, useState } from "react";
|
|
12
12
|
import { Input } from "../../components/input";
|
|
13
13
|
import { Tabs } from "../../components/tabs";
|
|
14
14
|
import { Tooltip } from "../../components/tooltip";
|
|
@@ -20,9 +20,6 @@ import { IDENTITY_ACCOUNT_PATH, IDENTITY_PAIR_PATH } from "../identity/identity-
|
|
|
20
20
|
import { getNetworkControlConsumer } from "../network-viewer/network-viewer-ipc";
|
|
21
21
|
import { ColorModeDropdown } from "./color-mode-dropdown";
|
|
22
22
|
|
|
23
|
-
/** Electron file picker attaches a filesystem path on `File`. */
|
|
24
|
-
type FileWithElectronPath = File & { path?: string };
|
|
25
|
-
|
|
26
23
|
type PeersHostWindow = Window & {
|
|
27
24
|
electronAPI?: unknown;
|
|
28
25
|
ReactNativeWebView?: unknown;
|
|
@@ -106,7 +103,6 @@ const AdvancedSettingsTab: React.FC = () => {
|
|
|
106
103
|
<ResetDeviceSyncInfos />
|
|
107
104
|
<DeleteLocalDatabase />
|
|
108
105
|
<CleanupOldDataDir />
|
|
109
|
-
<ImportOldPeersData />
|
|
110
106
|
<ResetChangeTracking />
|
|
111
107
|
</>
|
|
112
108
|
);
|
|
@@ -403,256 +399,6 @@ const ResetChangeTracking: React.FC = () => {
|
|
|
403
399
|
);
|
|
404
400
|
};
|
|
405
401
|
|
|
406
|
-
const IMPORT_TOOL_ID = "00mh0wlipkdbeaw8imptsk001";
|
|
407
|
-
|
|
408
|
-
interface IDryRunResult {
|
|
409
|
-
totalRecords: number;
|
|
410
|
-
totalTasks: number;
|
|
411
|
-
totalLogEntries: number;
|
|
412
|
-
groups: {
|
|
413
|
-
groupId: string;
|
|
414
|
-
groupName: string;
|
|
415
|
-
context: "personal" | "home";
|
|
416
|
-
taskCount: number;
|
|
417
|
-
activeCount: number;
|
|
418
|
-
doneCount: number;
|
|
419
|
-
rootTaskCount: number;
|
|
420
|
-
subtaskCount: number;
|
|
421
|
-
logEntryCount: number;
|
|
422
|
-
}[];
|
|
423
|
-
statusMapping: {
|
|
424
|
-
inProgressToDone: number;
|
|
425
|
-
inProgressKeep: number;
|
|
426
|
-
queuedToDone: number;
|
|
427
|
-
queuedKeep: number;
|
|
428
|
-
backlogToDone: number;
|
|
429
|
-
backlogKeep: number;
|
|
430
|
-
noStatusToDone: number;
|
|
431
|
-
noStatusToBacklog: number;
|
|
432
|
-
};
|
|
433
|
-
userMapping: {
|
|
434
|
-
mark: number;
|
|
435
|
-
blair: number;
|
|
436
|
-
other: number;
|
|
437
|
-
};
|
|
438
|
-
warnings: string[];
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const ImportOldPeersData: React.FC = () => {
|
|
442
|
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
443
|
-
const [filePath, setFilePath] = useState<string>("");
|
|
444
|
-
const [_fileName, setFileName] = useState<string>("");
|
|
445
|
-
const [loading, setLoading] = useState(false);
|
|
446
|
-
const [result, setResult] = useState<IDryRunResult | null>(null);
|
|
447
|
-
const [error, setError] = useState<string>("");
|
|
448
|
-
|
|
449
|
-
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
450
|
-
const file = e.target.files?.[0];
|
|
451
|
-
if (!file) return;
|
|
452
|
-
// Electron exposes .path on File objects
|
|
453
|
-
const path = (file as FileWithElectronPath).path;
|
|
454
|
-
if (path) {
|
|
455
|
-
setFilePath(path);
|
|
456
|
-
setFileName(file.name);
|
|
457
|
-
} else {
|
|
458
|
-
setFileName(file.name);
|
|
459
|
-
setFilePath("");
|
|
460
|
-
setError("File path not available. This feature requires the Electron desktop app.");
|
|
461
|
-
}
|
|
462
|
-
setResult(null);
|
|
463
|
-
setError("");
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
const handleDryRun = async () => {
|
|
467
|
-
if (!filePath) return;
|
|
468
|
-
setLoading(true);
|
|
469
|
-
setError("");
|
|
470
|
-
setResult(null);
|
|
471
|
-
try {
|
|
472
|
-
const response = await rpcServerCalls.runTool(IMPORT_TOOL_ID, {
|
|
473
|
-
filePath,
|
|
474
|
-
dryRun: true,
|
|
475
|
-
});
|
|
476
|
-
if (response?.result) {
|
|
477
|
-
setResult(response.result);
|
|
478
|
-
} else {
|
|
479
|
-
setError("Unexpected response from import tool");
|
|
480
|
-
}
|
|
481
|
-
} catch (err) {
|
|
482
|
-
setError((err as Error).message || "Failed to run dry run");
|
|
483
|
-
} finally {
|
|
484
|
-
setLoading(false);
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
return (
|
|
489
|
-
<div className="mt-4 pt-3 border-top">
|
|
490
|
-
<h6 className="mb-2">Import Old Peers Data</h6>
|
|
491
|
-
<small className="text-muted d-block mb-2">
|
|
492
|
-
Import tasks and log entries from an old peers JSON export file.
|
|
493
|
-
</small>
|
|
494
|
-
|
|
495
|
-
<div className="d-flex align-items-center gap-2 mb-2">
|
|
496
|
-
<input
|
|
497
|
-
ref={fileInputRef}
|
|
498
|
-
type="file"
|
|
499
|
-
accept=".json"
|
|
500
|
-
className="form-control form-control-sm"
|
|
501
|
-
style={{ maxWidth: 350 }}
|
|
502
|
-
onChange={handleFileSelect}
|
|
503
|
-
/>
|
|
504
|
-
<button
|
|
505
|
-
className="btn btn-outline-primary btn-sm"
|
|
506
|
-
onClick={handleDryRun}
|
|
507
|
-
disabled={!filePath || loading}
|
|
508
|
-
>
|
|
509
|
-
{loading ? "Analyzing..." : "Dry Run"}
|
|
510
|
-
</button>
|
|
511
|
-
</div>
|
|
512
|
-
|
|
513
|
-
{error && <div className="alert alert-danger py-1 px-2 small mt-2">{error}</div>}
|
|
514
|
-
|
|
515
|
-
{result && <DryRunResults result={result} />}
|
|
516
|
-
</div>
|
|
517
|
-
);
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
const DryRunResults: React.FC<{ result: IDryRunResult }> = ({ result }) => {
|
|
521
|
-
const sm = result.statusMapping;
|
|
522
|
-
const um = result.userMapping;
|
|
523
|
-
|
|
524
|
-
return (
|
|
525
|
-
<div className="mt-2">
|
|
526
|
-
<div className="alert alert-info py-2 px-3 small">
|
|
527
|
-
<strong>Summary:</strong> {result.totalTasks.toLocaleString()} tasks and{" "}
|
|
528
|
-
{result.totalLogEntries.toLocaleString()} log entries across {result.groups.length} groups (
|
|
529
|
-
{result.totalRecords.toLocaleString()} total records in file)
|
|
530
|
-
</div>
|
|
531
|
-
|
|
532
|
-
{result.warnings.length > 0 && (
|
|
533
|
-
<div className="alert alert-warning py-2 px-3 small">
|
|
534
|
-
<strong>Warnings:</strong>
|
|
535
|
-
<ul className="mb-0 ps-3">
|
|
536
|
-
{result.warnings.map((w, i) => (
|
|
537
|
-
<li key={i}>{w}</li>
|
|
538
|
-
))}
|
|
539
|
-
</ul>
|
|
540
|
-
</div>
|
|
541
|
-
)}
|
|
542
|
-
|
|
543
|
-
<details className="mb-2">
|
|
544
|
-
<summary className="small fw-bold" style={{ cursor: "pointer" }}>
|
|
545
|
-
Status Mapping
|
|
546
|
-
</summary>
|
|
547
|
-
<table className="table table-sm small mt-1">
|
|
548
|
-
<thead>
|
|
549
|
-
<tr>
|
|
550
|
-
<th>Mapping</th>
|
|
551
|
-
<th className="text-end">Count</th>
|
|
552
|
-
</tr>
|
|
553
|
-
</thead>
|
|
554
|
-
<tbody>
|
|
555
|
-
<tr>
|
|
556
|
-
<td>In-Progress → Done</td>
|
|
557
|
-
<td className="text-end">{sm.inProgressToDone.toLocaleString()}</td>
|
|
558
|
-
</tr>
|
|
559
|
-
<tr>
|
|
560
|
-
<td>In-Progress → In-Progress</td>
|
|
561
|
-
<td className="text-end">{sm.inProgressKeep.toLocaleString()}</td>
|
|
562
|
-
</tr>
|
|
563
|
-
<tr>
|
|
564
|
-
<td>Queued → Done</td>
|
|
565
|
-
<td className="text-end">{sm.queuedToDone.toLocaleString()}</td>
|
|
566
|
-
</tr>
|
|
567
|
-
<tr>
|
|
568
|
-
<td>Queued → Queued</td>
|
|
569
|
-
<td className="text-end">{sm.queuedKeep.toLocaleString()}</td>
|
|
570
|
-
</tr>
|
|
571
|
-
<tr>
|
|
572
|
-
<td>Backlog → Done</td>
|
|
573
|
-
<td className="text-end">{sm.backlogToDone.toLocaleString()}</td>
|
|
574
|
-
</tr>
|
|
575
|
-
<tr>
|
|
576
|
-
<td>Backlog → Backlog</td>
|
|
577
|
-
<td className="text-end">{sm.backlogKeep.toLocaleString()}</td>
|
|
578
|
-
</tr>
|
|
579
|
-
<tr>
|
|
580
|
-
<td>No status → Done</td>
|
|
581
|
-
<td className="text-end">{sm.noStatusToDone.toLocaleString()}</td>
|
|
582
|
-
</tr>
|
|
583
|
-
<tr>
|
|
584
|
-
<td>No status → Backlog</td>
|
|
585
|
-
<td className="text-end">{sm.noStatusToBacklog.toLocaleString()}</td>
|
|
586
|
-
</tr>
|
|
587
|
-
</tbody>
|
|
588
|
-
</table>
|
|
589
|
-
</details>
|
|
590
|
-
|
|
591
|
-
<details className="mb-2">
|
|
592
|
-
<summary className="small fw-bold" style={{ cursor: "pointer" }}>
|
|
593
|
-
User Mapping
|
|
594
|
-
</summary>
|
|
595
|
-
<table className="table table-sm small mt-1">
|
|
596
|
-
<thead>
|
|
597
|
-
<tr>
|
|
598
|
-
<th>User</th>
|
|
599
|
-
<th className="text-end">Tasks</th>
|
|
600
|
-
</tr>
|
|
601
|
-
</thead>
|
|
602
|
-
<tbody>
|
|
603
|
-
<tr>
|
|
604
|
-
<td>Mark</td>
|
|
605
|
-
<td className="text-end">{um.mark.toLocaleString()}</td>
|
|
606
|
-
</tr>
|
|
607
|
-
<tr>
|
|
608
|
-
<td>Blair</td>
|
|
609
|
-
<td className="text-end">{um.blair.toLocaleString()}</td>
|
|
610
|
-
</tr>
|
|
611
|
-
<tr>
|
|
612
|
-
<td>Other → Mark</td>
|
|
613
|
-
<td className="text-end">{um.other.toLocaleString()}</td>
|
|
614
|
-
</tr>
|
|
615
|
-
</tbody>
|
|
616
|
-
</table>
|
|
617
|
-
</details>
|
|
618
|
-
|
|
619
|
-
<details open className="mb-2">
|
|
620
|
-
<summary className="small fw-bold" style={{ cursor: "pointer" }}>
|
|
621
|
-
Groups ({result.groups.length})
|
|
622
|
-
</summary>
|
|
623
|
-
<table className="table table-sm small mt-1">
|
|
624
|
-
<thead>
|
|
625
|
-
<tr>
|
|
626
|
-
<th>Group</th>
|
|
627
|
-
<th>Route</th>
|
|
628
|
-
<th className="text-end">Tasks</th>
|
|
629
|
-
<th className="text-end">Active</th>
|
|
630
|
-
<th className="text-end">Done</th>
|
|
631
|
-
<th className="text-end">Logs</th>
|
|
632
|
-
</tr>
|
|
633
|
-
</thead>
|
|
634
|
-
<tbody>
|
|
635
|
-
{result.groups.map((g) => (
|
|
636
|
-
<tr key={g.groupId}>
|
|
637
|
-
<td>{g.groupName}</td>
|
|
638
|
-
<td>
|
|
639
|
-
<span className={`badge bg-${g.context === "home" ? "success" : "secondary"}`}>
|
|
640
|
-
{g.context}
|
|
641
|
-
</span>
|
|
642
|
-
</td>
|
|
643
|
-
<td className="text-end">{g.taskCount.toLocaleString()}</td>
|
|
644
|
-
<td className="text-end">{g.activeCount.toLocaleString()}</td>
|
|
645
|
-
<td className="text-end">{g.doneCount.toLocaleString()}</td>
|
|
646
|
-
<td className="text-end">{g.logEntryCount.toLocaleString()}</td>
|
|
647
|
-
</tr>
|
|
648
|
-
))}
|
|
649
|
-
</tbody>
|
|
650
|
-
</table>
|
|
651
|
-
</details>
|
|
652
|
-
</div>
|
|
653
|
-
);
|
|
654
|
-
};
|
|
655
|
-
|
|
656
402
|
const LogoutSection: React.FC = () => {
|
|
657
403
|
if (!isBrowserPwaHost) return null;
|
|
658
404
|
return (
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const list = jest.fn();
|
|
2
|
+
const get = jest.fn();
|
|
3
|
+
const rows = jest.fn();
|
|
4
|
+
let transport = true;
|
|
5
|
+
jest.mock("@peers-app/peers-sdk", () => ({
|
|
6
|
+
getDefaultDataContext: async () => ({ dataContextId: "group" }),
|
|
7
|
+
getHostContractTransport: () => (transport ? {} : undefined),
|
|
8
|
+
getToolCatalogContractDefinition: () => ({}),
|
|
9
|
+
hostContractConsumer: () => ({ get: async () => ({ tools: { listTools: list, getTool: get } }) }),
|
|
10
|
+
Tools: () => ({ list: rows, get: async (toolId: string) => ({ toolId, name: "historical" }) }),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { toolMentionConfig } from "../../mention-configs";
|
|
14
|
+
import { getToolDetails, listWorkflowMentionTools } from "./tool-catalog-ipc";
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
transport = true;
|
|
19
|
+
});
|
|
20
|
+
test("tool mentions include only workflow-invocable catalog tools", async () => {
|
|
21
|
+
list.mockResolvedValue({
|
|
22
|
+
tools: [
|
|
23
|
+
{ toolId: "run", name: "Run", workflowInvocable: true },
|
|
24
|
+
{ toolId: "approve", name: "Approve", workflowInvocable: false },
|
|
25
|
+
],
|
|
26
|
+
});
|
|
27
|
+
expect(await toolMentionConfig.query("run")).toEqual([{ kind: "tool", id: "run", name: "Run" }]);
|
|
28
|
+
expect(rows).not.toHaveBeenCalled();
|
|
29
|
+
expect(await toolMentionConfig.getById("old")).toEqual({
|
|
30
|
+
kind: "tool",
|
|
31
|
+
id: "old",
|
|
32
|
+
name: "historical",
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
test("browser fallback omits quarantined rows and preserves historical detail", async () => {
|
|
36
|
+
transport = false;
|
|
37
|
+
rows.mockResolvedValue([
|
|
38
|
+
{ toolId: "old", name: "Old" },
|
|
39
|
+
{ toolId: "bad", name: "Bad", catalogState: "quarantined" },
|
|
40
|
+
]);
|
|
41
|
+
expect(await listWorkflowMentionTools("", 10)).toEqual([{ toolId: "old", name: "Old" }]);
|
|
42
|
+
expect(await getToolDetails("old")).toEqual({
|
|
43
|
+
row: { toolId: "old", name: "historical" },
|
|
44
|
+
entry: undefined,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDefaultDataContext,
|
|
3
|
+
getHostContractTransport,
|
|
4
|
+
getToolCatalogContractDefinition,
|
|
5
|
+
hostContractConsumer,
|
|
6
|
+
type IHostContractConsumer,
|
|
7
|
+
type IToolCatalogConsumer,
|
|
8
|
+
type IToolCatalogEntry,
|
|
9
|
+
Tools,
|
|
10
|
+
} from "@peers-app/peers-sdk";
|
|
11
|
+
|
|
12
|
+
const consumers = new Map<string, IHostContractConsumer<IToolCatalogConsumer>>();
|
|
13
|
+
/** Obtain a catalog consumer for the selected host context; PWA callers can fall back to rows. */
|
|
14
|
+
export async function getToolCatalogConsumer(
|
|
15
|
+
dataContextId?: string,
|
|
16
|
+
): Promise<IToolCatalogConsumer> {
|
|
17
|
+
if (!getHostContractTransport()) throw new Error("Tool catalog host transport unavailable");
|
|
18
|
+
dataContextId ??= (await getDefaultDataContext()).dataContextId;
|
|
19
|
+
let handle = consumers.get(dataContextId);
|
|
20
|
+
if (!handle) {
|
|
21
|
+
const id = dataContextId;
|
|
22
|
+
handle = hostContractConsumer<IToolCatalogConsumer>(getToolCatalogContractDefinition(), {
|
|
23
|
+
dataContextId: async () => id,
|
|
24
|
+
});
|
|
25
|
+
consumers.set(id, handle);
|
|
26
|
+
}
|
|
27
|
+
return handle.get();
|
|
28
|
+
}
|
|
29
|
+
/** Get live tool details, retaining historical row metadata when the host catalog is absent. */
|
|
30
|
+
export async function getToolDetails(toolId: string) {
|
|
31
|
+
const row = await Tools().get(toolId);
|
|
32
|
+
if (!row) return undefined;
|
|
33
|
+
let entry: IToolCatalogEntry | undefined;
|
|
34
|
+
try {
|
|
35
|
+
entry = (await (await getToolCatalogConsumer()).tools.getTool({ toolIdOrName: toolId })).tool;
|
|
36
|
+
} catch {
|
|
37
|
+
/* Browser-only hosts retain catalog rows. */
|
|
38
|
+
}
|
|
39
|
+
return { row, entry };
|
|
40
|
+
}
|
|
41
|
+
/** Query workflow mention candidates; unavailable browser hosts retain non-quarantined rows. */
|
|
42
|
+
export async function listWorkflowMentionTools(query: string, limit: number) {
|
|
43
|
+
try {
|
|
44
|
+
const { tools } = await (await getToolCatalogConsumer()).tools.listTools({ query, limit: 500 });
|
|
45
|
+
return tools.filter((entry) => entry.workflowInvocable).slice(0, limit);
|
|
46
|
+
} catch {
|
|
47
|
+
return (await Tools().list({ name: { $matchWords: query } }, { pageSize: 500 }))
|
|
48
|
+
.filter((row) => row.catalogState !== "quarantined")
|
|
49
|
+
.slice(0, limit);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,74 +1,39 @@
|
|
|
1
|
-
import { Tools } from "@peers-app/peers-sdk";
|
|
2
|
-
import { Input } from "../../components/input";
|
|
3
1
|
import { LoadingIndicator } from "../../components/loading-indicator";
|
|
4
|
-
import { SaveButton } from "../../components/save-button";
|
|
5
2
|
import { ScreenTabBody, Tabs } from "../../components/tabs";
|
|
6
3
|
import { usePromise } from "../../hooks";
|
|
7
4
|
import { updateActiveTabTitle } from "../../tabs-layout/tabs-state";
|
|
8
|
-
import {
|
|
5
|
+
import { getToolDetails } from "./tool-catalog-ipc";
|
|
9
6
|
import { ToolInfo } from "./tool-info";
|
|
10
7
|
import { ToolSchema } from "./tool-schema";
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export const ToolDetails = (props: IToolDetailsProps) => {
|
|
17
|
-
const tool = usePromise(
|
|
9
|
+
/** Read-only permanent tool metadata and live provider details. */
|
|
10
|
+
export const ToolDetails = (props: { toolId: string }) => {
|
|
11
|
+
const details = usePromise(
|
|
18
12
|
async () => {
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
return Tools().initDoc(toolData);
|
|
13
|
+
const details = await getToolDetails(props.toolId);
|
|
14
|
+
if (details) updateActiveTabTitle(details.entry?.name || details.row.name || "Tool");
|
|
15
|
+
return details ?? null;
|
|
23
16
|
},
|
|
24
17
|
undefined,
|
|
25
18
|
[props.toolId],
|
|
26
19
|
);
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!tool) {
|
|
33
|
-
return <LoadingIndicator />;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function saveChanges() {
|
|
37
|
-
if (!tool) return;
|
|
38
|
-
tool.save();
|
|
39
|
-
}
|
|
40
|
-
|
|
20
|
+
if (details === null) return <div>Tool not found</div>;
|
|
21
|
+
if (!details) return <LoadingIndicator />;
|
|
22
|
+
const { row, entry } = details;
|
|
41
23
|
return (
|
|
42
|
-
<div className="container-fluid p-3"
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
</h4>
|
|
48
|
-
</div>
|
|
49
|
-
<div className="flex-grow-1">
|
|
50
|
-
<h4>
|
|
51
|
-
<Input
|
|
52
|
-
key={tool.toolId}
|
|
53
|
-
className="border border-0"
|
|
54
|
-
style={{ width: "100%", outline: "none", backgroundColor: "transparent" }}
|
|
55
|
-
value={tool.qs.name}
|
|
56
|
-
/>
|
|
57
|
-
</h4>
|
|
58
|
-
</div>
|
|
59
|
-
<div>
|
|
60
|
-
<SaveButton key={tool.toolId} onClick={saveChanges} doc={tool} />
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
24
|
+
<div className="container-fluid p-3">
|
|
25
|
+
<h4>
|
|
26
|
+
<i className="bi bi-tools me-2" />
|
|
27
|
+
{entry?.name ?? row.name}
|
|
28
|
+
</h4>
|
|
64
29
|
<Tabs
|
|
65
|
-
key={
|
|
30
|
+
key={row.toolId}
|
|
66
31
|
tabs={[
|
|
67
32
|
{
|
|
68
33
|
name: "Info",
|
|
69
34
|
content: (
|
|
70
35
|
<ScreenTabBody>
|
|
71
|
-
<ToolInfo tool={
|
|
36
|
+
<ToolInfo tool={row} entry={entry} />
|
|
72
37
|
</ScreenTabBody>
|
|
73
38
|
),
|
|
74
39
|
},
|
|
@@ -76,24 +41,10 @@ export const ToolDetails = (props: IToolDetailsProps) => {
|
|
|
76
41
|
name: "Schema",
|
|
77
42
|
content: (
|
|
78
43
|
<ScreenTabBody>
|
|
79
|
-
<ToolSchema tool={
|
|
80
|
-
</ScreenTabBody>
|
|
81
|
-
),
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: "Code",
|
|
85
|
-
content: (
|
|
86
|
-
<ScreenTabBody>
|
|
87
|
-
<ToolCode tool={tool} />
|
|
44
|
+
<ToolSchema tool={row} entry={entry} />
|
|
88
45
|
</ScreenTabBody>
|
|
89
46
|
),
|
|
90
47
|
},
|
|
91
|
-
// {
|
|
92
|
-
// name: 'Tests', content:
|
|
93
|
-
// <ScreenTabBody>
|
|
94
|
-
// <ToolTestList toolId={tool.toolId} />
|
|
95
|
-
// </ScreenTabBody>
|
|
96
|
-
// },
|
|
97
48
|
]}
|
|
98
49
|
/>
|
|
99
50
|
</div>
|
|
@@ -1,59 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { AccessLevelField } from "../../components/access-level-field";
|
|
4
|
-
import { Checkbox } from "../../components/checkbox";
|
|
5
|
-
import { Input } from "../../components/input";
|
|
6
|
-
import { MarkdownEditorInline } from "../../components/markdown-editor/editor-inline";
|
|
7
|
-
|
|
8
|
-
/** Info tab for a tool, including its required access level. */
|
|
9
|
-
export const ToolInfo = (props: { tool: IDoc<ITool> }) => {
|
|
10
|
-
const { tool } = props;
|
|
11
|
-
|
|
12
|
-
const [markdownUsageObs] = useState(() => observable(tool.usageDescription));
|
|
13
|
-
const [markdownDetailedObs] = useState(() => observable(tool.detailedDescription || ""));
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
const sub = markdownUsageObs.subscribe(() => {
|
|
17
|
-
tool.usageDescription = markdownUsageObs();
|
|
18
|
-
});
|
|
19
|
-
const sub2 = tool.qs.name.subscribe(() => {
|
|
20
|
-
tool.name = tool.qs
|
|
21
|
-
.name()
|
|
22
|
-
.replaceAll(/\s/g, "_")
|
|
23
|
-
.replaceAll(/[^a-zA-Z0-9_-]/g, "");
|
|
24
|
-
});
|
|
25
|
-
const sub3 = markdownDetailedObs.subscribe(() => {
|
|
26
|
-
tool.detailedDescription = markdownDetailedObs();
|
|
27
|
-
});
|
|
28
|
-
return () => {
|
|
29
|
-
sub.dispose();
|
|
30
|
-
sub2.dispose();
|
|
31
|
-
sub3.dispose();
|
|
32
|
-
};
|
|
33
|
-
}, [tool, markdownDetailedObs, markdownUsageObs]);
|
|
1
|
+
import { getAccessLevelLabel, type ITool, type IToolCatalogEntry } from "@peers-app/peers-sdk";
|
|
2
|
+
import { MarkdownWithMentions } from "../../components/markdown-with-mentions";
|
|
34
3
|
|
|
4
|
+
/** Display permanent identity and the host’s current dispatch and access projection. */
|
|
5
|
+
export const ToolInfo = ({ tool, entry }: { tool: ITool; entry?: IToolCatalogEntry }) => {
|
|
6
|
+
const target = entry?.target;
|
|
7
|
+
const level = entry?.access?.accessLevel;
|
|
35
8
|
return (
|
|
36
9
|
<div>
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
10
|
+
{tool.isAssistantRunner && <span className="badge bg-secondary">Assistant runner</span>}
|
|
11
|
+
<dl>
|
|
12
|
+
<dt>ID</dt>
|
|
13
|
+
<dd>{tool.toolId}</dd>
|
|
14
|
+
<dt>Aliases</dt>
|
|
15
|
+
<dd>{entry?.aliases.join(", ") ?? tool.toolId}</dd>
|
|
16
|
+
<dt>Contract</dt>
|
|
17
|
+
<dd>
|
|
18
|
+
{target
|
|
19
|
+
? `${target.contractId}@${target.version}`
|
|
20
|
+
: tool.contractId
|
|
21
|
+
? `${tool.contractId}@${tool.contractVersion}`
|
|
22
|
+
: "Unresolved"}
|
|
23
|
+
</dd>
|
|
24
|
+
<dt>Member</dt>
|
|
25
|
+
<dd>{target?.toolName ?? tool.toolName ?? tool.name}</dd>
|
|
26
|
+
<dt>Provider</dt>
|
|
27
|
+
<dd>{entry?.provider?.activePackageId ?? "Unavailable"}</dd>
|
|
28
|
+
<dt>Dispatch</dt>
|
|
29
|
+
<dd>
|
|
30
|
+
{entry?.availability.dispatch ?? "Host catalog unavailable"}
|
|
31
|
+
{entry?.availability.message && `: ${entry.availability.message}`}
|
|
32
|
+
</dd>
|
|
33
|
+
<dt>Live access</dt>
|
|
34
|
+
<dd>{level === undefined ? "Unavailable" : `${getAccessLevelLabel(level)} (${level})`}</dd>
|
|
35
|
+
<dt>Workflow</dt>
|
|
36
|
+
<dd>
|
|
37
|
+
{entry ? (entry.workflowInvocable ? "Available" : "Excluded or unavailable") : "Unknown"}
|
|
38
|
+
{entry?.availability.requiresWorkflowRun && "; workflow run required"}
|
|
39
|
+
</dd>
|
|
40
|
+
</dl>
|
|
41
|
+
<MarkdownWithMentions content={entry?.usageDescription ?? tool.usageDescription} />
|
|
42
|
+
{tool.detailedDescription && <MarkdownWithMentions content={tool.detailedDescription} />}
|
|
57
43
|
</div>
|
|
58
44
|
);
|
|
59
45
|
};
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FieldType,
|
|
3
|
-
type ICursorIterable,
|
|
4
|
-
IOSchemaType,
|
|
5
|
-
type ITool,
|
|
6
|
-
newid,
|
|
7
|
-
observable,
|
|
8
|
-
Tools,
|
|
9
|
-
} from "@peers-app/peers-sdk";
|
|
10
|
-
import type React from "react";
|
|
1
|
+
import { type ICursorIterable, type ITool, newid, observable, Tools } from "@peers-app/peers-sdk";
|
|
11
2
|
import { useEffect, useState } from "react";
|
|
12
3
|
import { Input } from "../../components/input";
|
|
13
4
|
import { LazyList } from "../../components/lazy-list";
|
|
14
5
|
import { LoadingIndicator } from "../../components/loading-indicator";
|
|
15
6
|
import { MarkdownWithMentions } from "../../components/markdown-with-mentions";
|
|
16
|
-
import { isDesktop
|
|
7
|
+
import { isDesktop } from "../../globals";
|
|
17
8
|
import { useObservable } from "../../hooks";
|
|
18
9
|
|
|
10
|
+
/** Browse permanent tool catalog rows. */
|
|
19
11
|
export function ToolList() {
|
|
20
12
|
const [searchTextObs] = useState(() => observable(""));
|
|
21
13
|
const [searchText] = useObservable(searchTextObs);
|
|
@@ -48,30 +40,6 @@ export function ToolList() {
|
|
|
48
40
|
return () => sub.dispose();
|
|
49
41
|
}, [cursorId, cursorObs, searchTextObs.subscribe]);
|
|
50
42
|
|
|
51
|
-
async function searchSubmit(evt: React.KeyboardEvent<HTMLInputElement>) {
|
|
52
|
-
if (evt.key !== "Enter") return;
|
|
53
|
-
if (!searchText.trim()) return;
|
|
54
|
-
const toolName = searchText
|
|
55
|
-
.trim()
|
|
56
|
-
.replace(/\s+/g, "_")
|
|
57
|
-
.replaceAll(/[^a-zA-Z0-9_]/g, "");
|
|
58
|
-
const tool = await Tools().initRecord({
|
|
59
|
-
name: toolName,
|
|
60
|
-
usageDescription: searchText.trim(),
|
|
61
|
-
detailedDescription: "",
|
|
62
|
-
inputSchema: {
|
|
63
|
-
type: IOSchemaType.simple,
|
|
64
|
-
fields: [{ name: "input", type: FieldType.string, description: "" }],
|
|
65
|
-
},
|
|
66
|
-
outputSchema: {
|
|
67
|
-
type: IOSchemaType.simple,
|
|
68
|
-
fields: [{ name: "output", type: FieldType.string, description: "" }],
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
await Tools().insert(tool);
|
|
72
|
-
mainContentPath(`tools/${tool.toolId}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
43
|
async function loadMore(existingTools: ITool[]): Promise<ITool[]> {
|
|
76
44
|
const moreMatches: ITool[] = [];
|
|
77
45
|
const cursor = cursorObs() || (await newCursor());
|
|
@@ -93,9 +61,8 @@ export function ToolList() {
|
|
|
93
61
|
value={searchTextObs}
|
|
94
62
|
className="form-control mt-3"
|
|
95
63
|
style={{ marginBottom: "10px" }}
|
|
96
|
-
placeholder="Search
|
|
64
|
+
placeholder="Search tools"
|
|
97
65
|
autoFocus={!!isDesktop()}
|
|
98
|
-
onKeyUp={(evt) => searchSubmit(evt)}
|
|
99
66
|
/>
|
|
100
67
|
|
|
101
68
|
<div className="peers-list-container">
|