@hachej/boring-ui-kit 0.4.51 → 0.4.53
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/chat-react-session.d.ts +6 -0
- package/dist/chat-react-session.js +33 -6
- package/dist/chat-view.js +2 -1
- package/dist/file-tree-view.js +37 -23
- package/dist/file-tree.d.ts +12 -0
- package/dist/file-tree.js +56 -4
- package/package.json +2 -2
- package/r/agent-target.json +4 -4
- package/r/app-shell.json +11 -11
- package/r/board-viewer.json +6 -6
- package/r/button.json +1 -1
- package/r/dock.json +1 -1
- package/r/documents.json +12 -12
- package/r/drawing-viewer.json +1 -1
- package/r/feedback.json +6 -6
- package/r/file-tree.json +4 -4
- package/r/highlights.json +3 -3
- package/r/html-viewer.json +3 -3
- package/r/looks-viewer.json +2 -2
- package/r/maquette-viewer.json +1 -1
- package/r/markdown-editor.json +3 -3
- package/r/mockup-viewer.json +1 -1
- package/r/pi-ambient.json +8 -8
- package/r/pi-app-kinds.json +10 -10
- package/r/pi-app.json +10 -10
- package/r/pi-artifacts.json +2 -2
- package/r/pi-chat-approval.json +2 -2
- package/r/pi-chat-attachments.json +1 -1
- package/r/pi-chat-markdown.json +2 -2
- package/r/pi-chat-mentions.json +1 -1
- package/r/pi-chat-pickers.json +1 -1
- package/r/pi-chat-plugins.json +11 -11
- package/r/pi-chat-question.json +2 -2
- package/r/pi-chat-slash.json +2 -2
- package/r/pi-chat-subagent.json +1 -1
- package/r/pi-chat-voice.json +3 -3
- package/r/pi-chat.json +7 -7
- package/r/pi-workspace.json +3 -3
- package/r/placed-agent.json +4 -4
- package/r/provider-setup.json +1 -1
- package/r/registry.json +162 -162
- package/r/shared-chat.json +6 -6
- package/r/spaces.json +1 -1
- package/r/task-list-viewer.json +2 -2
- package/r/version-history.json +2 -2
- package/r/viewer-code.json +2 -2
- package/r/viewer-frame.json +4 -4
- package/r/viewer-html.json +4 -4
- package/r/viewer-image.json +2 -2
- package/r/viewer-markdown.json +5 -5
- package/r/viewer-pdf.json +3 -3
- package/r/viewers.json +6 -6
- package/r/windows.json +1 -1
package/r/board-viewer.json
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
"title": "Board viewer",
|
|
5
5
|
"description": "A board@1 artifact kind for *.board.json. Lazy native tldraw projection of maquette screens, sandboxed HTML mock-ups, goto arrows and notes. Conditional board-file autosave; phone viewing without accidental frame edits. Hosts supply their resource client, asset URLs and license key.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
|
+
"@hachej/boring-files@^0.4.53",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"lucide-react@1.51.0",
|
|
11
11
|
"tldraw@5.5.2"
|
|
12
12
|
],
|
|
13
13
|
"registryDependencies": [
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/maquette-viewer.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/mockup-viewer.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-artifacts.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/utils.json"
|
|
18
18
|
],
|
|
19
19
|
"files": [
|
|
20
20
|
{
|
package/r/button.json
CHANGED
package/r/dock.json
CHANGED
package/r/documents.json
CHANGED
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
"title": "Documents: the Library, a file and its revisions",
|
|
5
5
|
"description": "The workspace's Library as the list, the chosen file in its viewer and its retained revisions beside it (an older one opens read-only). DocumentSheet opens one document over the app for a host's « Voir la fiche », with an optional « Toutes les fiches ». Every read goes through one revisionProvider binding.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
|
+
"@hachej/boring-files@^0.4.53",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"lucide-react@^1.51.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
18
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
19
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
20
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
21
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
22
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/utils.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/button.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-artifacts.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-app.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-app-kinds.json",
|
|
18
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/app-shell.json",
|
|
19
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/file-tree.json",
|
|
20
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-frame.json",
|
|
21
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-image.json",
|
|
22
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-pdf.json"
|
|
23
23
|
],
|
|
24
24
|
"files": [
|
|
25
25
|
{
|
package/r/drawing-viewer.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Drawing viewer",
|
|
5
5
|
"description": "DrawingViewer, drawingKind and describeDrawing: an Excalidraw (.excalidraw) or draw.io (.drawio) file, read-only, drawn by the tool's own renderer on a white page fitted to the shapes: Excalidraw's exportToSvg (its fonts served by the app, never its CDN) and the official diagrams.net viewer, vendored in the kit (@hachej/boring-ui-kit/drawio-viewer), in a sandboxed frame without network. Shown as an image, so nothing in the file runs; an unreadable file shows an inline error. onRead hands the shapes and arrows the renderer read (kind, place, size, label, colours, from and to). Each renderer loads the first time a drawing of its format opens; the kind is not in the pi-app-kinds default list, so apps without drawings install nothing.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0",
|
|
10
10
|
"@excalidraw/excalidraw@0.18.1"
|
package/r/feedback.json
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
"title": "Feedback",
|
|
5
5
|
"description": "Feedback from the chat composer: one Feedback button starts feedback mode on the application page (hover, click to pin a numbered note, hold ⌥ to use the app, optional voice), and one chip carries the notes to the next message. FeedbackBar, NoteBubble, FeedbackChip with its review, useComposerFeedback for the pi-chat composer, and PickerOverlay styles. Optional, not mounted by default: PointButton, AnnotateSheet, FeedbackList and FeedbackReport. Picking, masking, anchoring and placement stay in @hachej/boring-feedback/page and /ui, so restyling cannot weaken them.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-feedback@^0.4.
|
|
8
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
9
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-feedback@^0.4.53",
|
|
8
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
9
|
+
"@hachej/boring-files@^0.4.53",
|
|
10
10
|
"react@^19.0.0",
|
|
11
11
|
"react-dom@^19.0.0",
|
|
12
12
|
"lucide-react@^1.51.0"
|
|
13
13
|
],
|
|
14
14
|
"registryDependencies": [
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-voice.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/utils.json"
|
|
18
18
|
],
|
|
19
19
|
"files": [
|
|
20
20
|
{
|
package/r/file-tree.json
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"title": "File tree",
|
|
5
5
|
"description": "Browse, search, upload, rename, move, delete and inspect retained versions through one revisionProvider binding. Uses the consumer's shadcn Button, dropdown-menu and dialog with shared version-history rows; Base UI style only.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
|
+
"@hachej/boring-files@^0.4.53",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"lucide-react@^1.51.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/utils.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/version-history.json"
|
|
15
15
|
],
|
|
16
16
|
"files": [
|
|
17
17
|
{
|
package/r/highlights.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"title": "Anchored highlights",
|
|
5
5
|
"description": "Exact Markdown highlights and comments in a conditional workspace sidecar.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
|
+
"@hachej/boring-files@^0.4.53",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"react-dom@^19.0.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/markdown-editor.json"
|
|
14
14
|
],
|
|
15
15
|
"files": [
|
|
16
16
|
{
|
package/r/html-viewer.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"title": "HTML source and passive preview",
|
|
5
5
|
"description": "Exact HTML source editing with a reconstructed passive preview and borrowed controller. Browser security qualification remains separate.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
|
+
"@hachej/boring-files@^0.4.53",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"react-dom@^19.0.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/button.json"
|
|
14
14
|
],
|
|
15
15
|
"files": [
|
|
16
16
|
{
|
package/r/looks-viewer.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"lucide-react@^1.51.0"
|
|
9
9
|
],
|
|
10
10
|
"registryDependencies": [
|
|
11
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/mockup-viewer.json",
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/utils.json"
|
|
13
13
|
],
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
package/r/maquette-viewer.json
CHANGED
package/r/markdown-editor.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"title": "Markdown editor",
|
|
5
5
|
"description": "A host-owned styled wrapper over the concrete borrowed Markdown controller.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
|
+
"@hachej/boring-files@^0.4.53",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"react-dom@^19.0.0",
|
|
11
11
|
"marked@^17.0.6",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"lucide-react@^1.51.0"
|
|
25
25
|
],
|
|
26
26
|
"registryDependencies": [
|
|
27
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
27
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/button.json"
|
|
28
28
|
],
|
|
29
29
|
"files": [
|
|
30
30
|
{
|
package/r/mockup-viewer.json
CHANGED
package/r/pi-ambient.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Pi ambient agent",
|
|
5
5
|
"description": "AmbientChat, a compact floating agent bar that grows into a chat window over an existing page, and AgentNotifications, toasts over the kit's shared chat-react run notifications (with the one guarded browser Notification helper). A block over the pi-chat item; its in-window artifact panel uses the pi-workspace layout.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"react-dom@^19.0.0",
|
|
10
10
|
"@earendil-works/pi-durable@~1.4.0",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"lucide-react@^1.51.0"
|
|
13
13
|
],
|
|
14
14
|
"registryDependencies": [
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
18
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
19
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
20
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
21
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-plugins.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-question.json",
|
|
18
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-artifacts.json",
|
|
19
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-workspace.json",
|
|
20
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/utils.json",
|
|
21
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/button.json"
|
|
22
22
|
],
|
|
23
23
|
"files": [
|
|
24
24
|
{
|
package/r/pi-app-kinds.json
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
"title": "Pi agent app default kinds",
|
|
5
5
|
"description": "defaultKinds({ locale, comments }): the artifact kinds the pi-app item draws with by default, in order: maquette, diagram, mock-up and records, then Markdown, HTML, SVG, CSV, image and PDF (documentKinds). Pass it as AgentWorkspace's artifactKinds (or ViewerOptions.kinds), with an app's own kinds before it. Each kind loads its viewer on first open.",
|
|
6
6
|
"registryDependencies": [
|
|
7
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
8
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
9
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
10
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
11
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
7
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-app.json",
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-markdown.json",
|
|
9
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-html.json",
|
|
10
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-image.json",
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-pdf.json",
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-code.json",
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/maquette-viewer.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/diagram-viewer.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/mockup-viewer.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/record-viewer.json"
|
|
17
17
|
],
|
|
18
18
|
"files": [
|
|
19
19
|
{
|
package/r/pi-app.json
CHANGED
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
"title": "Pi agent app",
|
|
5
5
|
"description": "AgentWorkspace: a whole agent page in one component. Left: the sessions pane (search, New, rename, archive, delete; collapsible, a drawer on a narrow screen). Center: PiChat. Right: the artifact panel (versions, file viewer, auto-open of new artifacts) in the ArtifactWorkspace panel, a full-screen sheet on a phone; it draws files with the kinds the app passes in artifactKinds (defaultKinds of the pi-app-kinds item is the default list). layout=\"embedded\" drops the left column for one compact top bar (title, History, Library, New) that joins an app-shell screen header. Props are data and callbacks; hooks for the conversations handler (useConversations), the chat transport (useRemoteChat) and saved resources (useSaved).",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
|
+
"@hachej/boring-files@^0.4.53",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"react-dom@^19.0.0",
|
|
11
11
|
"lucide-react@^1.51.0"
|
|
12
12
|
],
|
|
13
13
|
"registryDependencies": [
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
18
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
19
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
20
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
21
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-plugins.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-artifacts.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-workspace.json",
|
|
18
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/viewer-frame.json",
|
|
19
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/utils.json",
|
|
20
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/button.json",
|
|
21
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/file-tree.json"
|
|
22
22
|
],
|
|
23
23
|
"files": [
|
|
24
24
|
{
|
package/r/pi-artifacts.json
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"title": "Pi artifacts",
|
|
5
5
|
"description": "Artifact descriptors, kinds and the artifact card that opens generated content in the host's panel. Plug it in with `artifacts={{ ...artifactCards, open, isOpen, kinds }}`; without it pi-chat offers no artifact. pi-app and the viewers import their artifact kinds and types from it.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0",
|
|
10
10
|
"@earendil-works/pi-ai@~1.4.0",
|
|
11
11
|
"@earendil-works/pi-durable@~1.4.0"
|
|
12
12
|
],
|
|
13
13
|
"registryDependencies": [
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json"
|
|
15
15
|
],
|
|
16
16
|
"files": [
|
|
17
17
|
{
|
package/r/pi-chat-approval.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"title": "Pi chat approval card",
|
|
5
5
|
"description": "The Approve / Deny card for host tools wrapped with `requireApproval`. Plug it in with `cards={{ approval: ApprovalCard }}`; without it pi-chat shows a \"waiting for your answer\" notice with no answer UI.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0",
|
|
10
10
|
"@earendil-works/pi-ai@~1.4.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json"
|
|
14
14
|
],
|
|
15
15
|
"files": [
|
|
16
16
|
{
|
package/r/pi-chat-markdown.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"title": "Pi chat Markdown",
|
|
5
5
|
"description": "Markdown for the chat's assistant replies: safe token-by-token rendering (raw HTML shown as text, no images fetched, http(s) links only), code blocks, tables, task lists and /command buttons. Plug it in with `renderText={markdownText}`; without it pi-chat shows replies as plain text.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"marked@^17.0.6",
|
|
10
10
|
"entities@^8.1.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json"
|
|
14
14
|
],
|
|
15
15
|
"files": [
|
|
16
16
|
{
|
package/r/pi-chat-mentions.json
CHANGED
package/r/pi-chat-pickers.json
CHANGED
package/r/pi-chat-plugins.json
CHANGED
|
@@ -7,17 +7,17 @@
|
|
|
7
7
|
"react@^19.0.0"
|
|
8
8
|
],
|
|
9
9
|
"registryDependencies": [
|
|
10
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
11
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
18
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
19
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
20
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
10
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json",
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-markdown.json",
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-slash.json",
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-mentions.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-pickers.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-voice.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-attachments.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-question.json",
|
|
18
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-approval.json",
|
|
19
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat-subagent.json",
|
|
20
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-artifacts.json"
|
|
21
21
|
],
|
|
22
22
|
"files": [
|
|
23
23
|
{
|
package/r/pi-chat-question.json
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"@earendil-works/pi-ai@~1.4.0"
|
|
10
10
|
],
|
|
11
11
|
"registryDependencies": [
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json"
|
|
13
13
|
],
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
|
16
16
|
"path": "registry/pi-chat-question/question-card.tsx",
|
|
17
|
-
"content": "'use client';\n\nimport { useEffect, useId, useRef, useState } from 'react';\nimport type { FormEvent } from 'react';\nimport type { ToolCall, ToolResultMessage } from '@earendil-works/pi-ai';\nimport { CheckIcon, CircleSlashIcon, SendIcon } from 'lucide-react';\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport { Alert, AlertDescription } from '@/components/ui/alert';\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';\nimport { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from '@/components/ui/item';\nimport { Spinner } from '@/components/ui/spinner';\nimport { useChatText } from '../pi-chat/labels';\nimport { ASK_USER_TOOL } from '../pi-chat/config';\nimport type { AnswerOutcome, QuestionCardProps } from '../pi-chat/config';\n\n// The core owns the tool name and the props: it places the question row and shows its waiting notice without this item.\nexport { ASK_USER_TOOL };\nexport type { AnswerOutcome, QuestionCardProps };\n\n/** One question of a form: its text and its options (chips); every question of a form also takes free text. */\nexport interface FormQuestion { readonly question: string; readonly options: readonly string[] }\ninterface Question {\n readonly question: string; readonly options: readonly string[]; readonly allowFreeText: boolean;\n /** Several questions in one form (`questions`), answered together as one text. Empty for a single question. */\n readonly items: readonly FormQuestion[];\n}\n\nconst strings = (value: unknown, max: number) => Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string' && item.length > 0).slice(0, max) : [];\n\n/**\n * Read `{ question, options?, allowFreeText?, questions? }` defensively: model arguments are untrusted input. Free text is allowed\n * unless `allowFreeText` is `false` (and always when there are no options). `questions` (up to 8) makes a form.\n */\nexport function parseQuestion(call: ToolCall): Question {\n const args = call.arguments as Record<string, unknown>;\n const options = strings(args['options'], 6);\n const items = Array.isArray(args['questions']) ? args['questions'].flatMap((item: unknown) => {\n const value = item !== null && typeof item === 'object' ? item as Record<string, unknown> : {};\n return typeof value['question'] === 'string' && value['question'] ? [{ question: value['question'], options: strings(value['options'], 6) }] : [];\n }).slice(0, 8) : [];\n return { question: typeof args['question'] === 'string' ? args['question'] : '', options, allowFreeText: args['allowFreeText'] !== false || options.length === 0, items };\n}\n\n/** The answers of a form as the one text the agent reads: each question, then its answer, or `skipped` when left blank. */\nexport function formAnswer(items: readonly FormQuestion[], values: readonly string[], skipped: string): string {\n return items.map((item, i) => `${i + 1}. ${item.question}\\n→ ${values[i]?.trim() || skipped}`).join('\\n\\n');\n}\n\n/** The answers of a `formAnswer` text, one per question; undefined when the text does not have that shape (then it is shown whole). */\nfunction splitFormAnswer(items: readonly FormQuestion[], text: string): readonly string[] | undefined {\n const values: string[] = [];\n let at = 0;\n for (let i = 0; i < items.length; i++) {\n const head = `${i + 1}. ${items[i]!.question}\\n→ `;\n if (!text.startsWith(head, at)) return undefined;\n at += head.length;\n const end = i + 1 < items.length ? text.indexOf(`\\n\\n${i + 2}. ${items[i + 1]!.question}\\n→ `, at) : text.length;\n if (end < 0) return undefined;\n values.push(text.slice(at, end));\n at = end + 2;\n }\n return values;\n}\n\n/** The answered text from a result such as `{\"kind\":\"answered\",\"answer\":\"...\"}`; undefined when the call did not produce an answer. */\nexport function parseAnswer(result: ToolResultMessage | undefined): { readonly answer: string } | { readonly failure: string } | undefined {\n if (!result) return undefined;\n const text = result.content.map(part => part.type === 'text' ? part.text : '').join('');\n if (!result.isError) {\n try {\n const value: unknown = JSON.parse(text);\n if (value !== null && typeof value === 'object' && (value as { kind?: unknown }).kind === 'answered' && typeof (value as { answer?: unknown }).answer === 'string') return { answer: (value as { answer: string }).answer };\n } catch { /* fall through to the failure text */ }\n }\n return { failure: text || 'The question was not answered.' };\n}\n\n\n/**\n * Human-in-the-loop question: writing (still generated, not answerable), pending (options and free text), then answered with the chosen\n * answer. With `questions` it is one form: each question has its option chips and a free-text field, and Answer sends one text\n * (`formAnswer`). It needs only a tool call and an `answer` callback, so a host can show it outside the chat too (for example in a\n * popover answering a request of its own); wrap it in `ChatTextProvider` for the chat's words there.\n */\nexport function QuestionCard({ call, result, answer, questionId, live = false }: QuestionCardProps) {\n const { labels } = useChatText();\n const question = parseQuestion(call);\n const settled = parseAnswer(result);\n const [sent, setSent] = useState<string | undefined>();\n const [sending, setSending] = useState<string | undefined>();\n const [refusal, setRefusal] = useState<string | undefined>();\n const [text, setText] = useState('');\n const [values, setValues] = useState<readonly string[]>([]);\n const formId = useId();\n const setValue = (i: number, value: string) => setValues(previous => question.items.map((_, j) => j === i ? value : previous[j] ?? ''));\n const mounted = useRef(true);\n useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);\n const current = settled && 'answer' in settled ? settled.answer : sent;\n // `pending` means the question waits on the person; `writing` means the model is still generating it (options may be partial).\n const state = settled ? ('failure' in settled ? 'failed' : 'answered') : sent !== undefined ? 'sent' : live ? 'writing' : 'pending';\n const open = state === 'pending' || state === 'writing';\n\n async function submit(value: string) {\n const trimmed = value.trim();\n if (!answer || !questionId || live || !trimmed || sending !== undefined) return;\n setRefusal(undefined); setSending(trimmed);\n try {\n const outcome = await answer(questionId, trimmed);\n if (outcome.kind === 'answered') setSent(trimmed);\n else setRefusal(outcome.reason || labels.answerRefused(outcome.kind));\n } catch (cause) { setRefusal(cause instanceof Error ? cause.message : labels.answerNotSent); }\n finally { if (mounted.current) setSending(undefined); }\n }\n const onSubmit = (event: FormEvent) => { event.preventDefault(); void submit(text); };\n const onSubmitForm = (event: FormEvent) => { event.preventDefault(); if (values.some(value => value.trim())) void submit(formAnswer(question.items, values, labels.skipped)); };\n const busy = sending !== undefined || live;\n\n const unavailable = !answer || !questionId || busy;\n // `m-0`: a host without a global preflight keeps the paragraph margin of ItemDescription.\n const wrap = 'm-0 line-clamp-none [overflow-wrap:anywhere]';\n\n if (!open) {\n const values = question.items.length > 0 && current !== undefined ? splitFormAnswer(question.items, current) : undefined;\n return <Item role=\"group\" data-testid=\"question-card\" data-state={state} data-call-id={call.id} aria-label={labels.question}>\n <ItemMedia variant=\"icon\">\n {state === 'answered' ? <CheckIcon role=\"img\" aria-label={labels.answered} /> : state === 'sent' ? <Spinner aria-label={labels.waitingForAgent} /> : <CircleSlashIcon role=\"img\" aria-label={labels.notAnswered} />}\n </ItemMedia>\n <ItemContent className=\"min-w-0\">\n {state === 'failed' && <ItemDescription data-testid=\"question-text\" className={wrap}>{question.question || labels.askedQuestion}</ItemDescription>}\n {values ? <div className=\"flex min-w-0 flex-col gap-2\">{question.items.map((item, i) => <div key={i} data-testid=\"question-answer-item\" className=\"flex min-w-0 flex-col gap-1\">\n <ItemDescription className={wrap}>{item.question}</ItemDescription>\n {values[i] === labels.skipped\n ? <ItemDescription data-testid=\"question-answer\" data-skipped=\"true\" className={wrap}>{values[i]}</ItemDescription>\n : <ItemTitle data-testid=\"question-answer\" className={wrap}>{values[i]}</ItemTitle>}\n </div>)}</div>\n : current !== undefined && <ItemTitle className={wrap}><span className=\"sr-only\">{labels.yourAnswer} </span><span data-testid=\"question-answer\" className=\"whitespace-pre-line\">{current}</span></ItemTitle>}\n {state === 'failed' && settled && 'failure' in settled && <ItemDescription role=\"alert\" className={wrap}>{settled.failure}</ItemDescription>}\n </ItemContent>\n </Item>;\n }\n\n const option = (value: string, selected: boolean, onClick: () => void) => <Button key={value} variant={selected ? 'secondary' : 'outline'} data-testid=\"question-option\" data-option={value}\n aria-pressed={question.items.length > 0 ? selected : undefined} disabled={unavailable} onClick={onClick} className=\"h-auto min-h-8 w-full justify-start whitespace-normal text-start\">\n {selected && <CheckIcon data-icon=\"inline-start\" aria-hidden=\"true\" />}{value}\n </Button>;\n\n return <Card role=\"group\" data-testid=\"question-card\" data-state={state} data-call-id={call.id} aria-label={labels.question}>\n <CardHeader>\n <CardDescription>{labels.questionForYou}</CardDescription>\n <CardTitle data-testid=\"question-text\" className=\"[overflow-wrap:anywhere]\">{question.question || labels.askedQuestion}</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-col gap-4\">\n {question.items.length > 0 ? <form data-testid=\"question-form\" onSubmit={onSubmitForm} className=\"flex flex-col gap-4\">\n {question.items.map((item, i) => <div key={i} role=\"group\" aria-labelledby={`${formId}-${i}`} data-testid=\"question-item\" className=\"flex min-w-0 flex-col gap-2\">\n <p id={`${formId}-${i}`} className=\"m-0 [overflow-wrap:anywhere]\">{i + 1}. {item.question}</p>\n {item.options.length > 0 && <div role=\"group\" aria-label={labels.answerOptions} className=\"flex flex-col gap-2\">\n {item.options.map(value => option(value, values[i] === value, () => setValue(i, values[i] === value ? '' : value)))}\n </div>}\n <Input data-testid=\"question-input\" value={item.options.includes(values[i] ?? '') ? '' : values[i] ?? ''} maxLength={2000} disabled={unavailable}\n placeholder={item.options.length ? labels.ownAnswer : labels.typeAnswer} aria-label={`${labels.yourAnswerInput} ${i + 1}`} onChange={event => setValue(i, event.currentTarget.value)} />\n </div>)}\n <div><Button type=\"submit\" data-testid=\"question-submit\" disabled={unavailable || !values.some(value => value.trim())}><SendIcon data-icon=\"inline-start\" aria-hidden=\"true\" />{labels.answer}</Button></div>\n </form> : <>\n {question.options.length > 0 && <div role=\"group\" aria-label={labels.answerOptions} className=\"flex flex-col gap-2\">\n {question.options.map(value => option(value, false, () => { void submit(value); }))}\n </div>}\n {question.allowFreeText && <form onSubmit={onSubmit} className=\"flex gap-2\">\n <Input data-testid=\"question-input\" value={text} maxLength={2000} disabled={unavailable} placeholder={question.options.length ? labels.ownAnswer : labels.typeAnswer}\n aria-label={labels.yourAnswerInput} onChange={event => setText(event.currentTarget.value)} />\n <Button type=\"submit\" data-testid=\"question-submit\" disabled={unavailable || !text.trim()}><SendIcon data-icon=\"inline-start\" aria-hidden=\"true\" />{labels.answer}</Button>\n </form>}\n </>}\n {live && <CardDescription>{labels.stillWritingQuestion}</CardDescription>}\n {!answer && !live && <CardDescription>{labels.answeringUnavailable}</CardDescription>}\n {refusal && <Alert variant=\"destructive\" data-testid=\"question-refusal\"><AlertDescription>{refusal}</AlertDescription></Alert>}\n </CardContent>\n </Card>;\n}\n",
|
|
17
|
+
"content": "'use client';\n\nimport { useEffect, useId, useRef, useState } from 'react';\nimport type { FormEvent } from 'react';\nimport type { ToolCall, ToolResultMessage } from '@earendil-works/pi-ai';\nimport { CheckIcon, CircleSlashIcon, SendIcon } from 'lucide-react';\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport { Alert, AlertDescription } from '@/components/ui/alert';\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';\nimport { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from '@/components/ui/item';\nimport { Spinner } from '@/components/ui/spinner';\nimport { useChatText } from '../pi-chat/labels';\nimport { ASK_USER_TOOL } from '../pi-chat/config';\nimport type { AnswerOutcome, QuestionCardProps } from '../pi-chat/config';\n\n// The core owns the tool name and the props: it places the question row and shows its waiting notice without this item.\nexport { ASK_USER_TOOL };\nexport type { AnswerOutcome, QuestionCardProps };\n\n/** One question of a form: its text and its options (chips); every question of a form also takes free text. */\nexport interface FormQuestion { readonly question: string; readonly options: readonly string[] }\ninterface Question {\n readonly question: string; readonly options: readonly string[]; readonly allowFreeText: boolean;\n /** Several questions in one form (`questions`), answered together as one text. Empty for a single question. */\n readonly items: readonly FormQuestion[];\n}\n\nconst strings = (value: unknown, max: number) => Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string' && item.length > 0).slice(0, max) : [];\n\n/**\n * Read `{ question, options?, allowFreeText?, questions? }` defensively: model arguments are untrusted input. Free text is allowed\n * unless `allowFreeText` is `false` (and always when there are no options). `questions` (up to 8) makes a form.\n */\nexport function parseQuestion(call: ToolCall): Question {\n const args = call.arguments as Record<string, unknown>;\n const options = strings(args['options'], 6);\n const items = Array.isArray(args['questions']) ? args['questions'].flatMap((item: unknown) => {\n const value = item !== null && typeof item === 'object' ? item as Record<string, unknown> : {};\n return typeof value['question'] === 'string' && value['question'] ? [{ question: value['question'], options: strings(value['options'], 6) }] : [];\n }).slice(0, 8) : [];\n return { question: typeof args['question'] === 'string' ? args['question'] : '', options, allowFreeText: args['allowFreeText'] !== false || options.length === 0, items };\n}\n\n/** The answers of a form as the one text the agent reads: each question, then its answer, or `skipped` when left blank. */\nexport function formAnswer(items: readonly FormQuestion[], values: readonly string[], skipped: string): string {\n return items.map((item, i) => `${i + 1}. ${item.question}\\n→ ${values[i]?.trim() || skipped}`).join('\\n\\n');\n}\n\n/** The answers of a `formAnswer` text, one per question; undefined when the text does not have that shape (then it is shown whole). */\nfunction splitFormAnswer(items: readonly FormQuestion[], text: string): readonly string[] | undefined {\n const values: string[] = [];\n let at = 0;\n for (let i = 0; i < items.length; i++) {\n const head = `${i + 1}. ${items[i]!.question}\\n→ `;\n if (!text.startsWith(head, at)) return undefined;\n at += head.length;\n const end = i + 1 < items.length ? text.indexOf(`\\n\\n${i + 2}. ${items[i + 1]!.question}\\n→ `, at) : text.length;\n if (end < 0) return undefined;\n values.push(text.slice(at, end));\n at = end + 2;\n }\n return values;\n}\n\n/** The answered text from a result such as `{\"kind\":\"answered\",\"answer\":\"...\"}`; undefined when the call did not produce an answer. */\nexport function parseAnswer(result: ToolResultMessage | undefined): { readonly answer: string } | { readonly failure: string } | undefined {\n if (!result) return undefined;\n // Pi appends its diagnostics to a result as a `<harness>` block of their own, for the model: never for the person.\n const text = result.content.map(part => part.type === 'text' && !/^<harness>[\\s\\S]*<\\/harness>$/.test(part.text) ? part.text : '').join('');\n if (!result.isError) {\n try {\n const value: unknown = JSON.parse(text);\n if (value !== null && typeof value === 'object' && (value as { kind?: unknown }).kind === 'answered' && typeof (value as { answer?: unknown }).answer === 'string') return { answer: (value as { answer: string }).answer };\n } catch { /* fall through to the failure text */ }\n }\n return { failure: text || 'The question was not answered.' };\n}\n\n\n/**\n * Human-in-the-loop question: writing (still generated, not answerable), pending (options and free text), then answered with the chosen\n * answer. With `questions` it is one form: each question has its option chips and a free-text field, and Answer sends one text\n * (`formAnswer`). It needs only a tool call and an `answer` callback, so a host can show it outside the chat too (for example in a\n * popover answering a request of its own); wrap it in `ChatTextProvider` for the chat's words there.\n */\nexport function QuestionCard({ call, result, answer, questionId, live = false }: QuestionCardProps) {\n const { labels } = useChatText();\n const question = parseQuestion(call);\n const settled = parseAnswer(result);\n const [sent, setSent] = useState<string | undefined>();\n const [sending, setSending] = useState<string | undefined>();\n const [refusal, setRefusal] = useState<string | undefined>();\n const [text, setText] = useState('');\n const [values, setValues] = useState<readonly string[]>([]);\n const formId = useId();\n const setValue = (i: number, value: string) => setValues(previous => question.items.map((_, j) => j === i ? value : previous[j] ?? ''));\n const mounted = useRef(true);\n useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);\n const current = settled && 'answer' in settled ? settled.answer : sent;\n // `pending` means the question waits on the person; `writing` means the model is still generating it (options may be partial).\n const state = settled ? ('failure' in settled ? 'failed' : 'answered') : sent !== undefined ? 'sent' : live ? 'writing' : 'pending';\n const open = state === 'pending' || state === 'writing';\n\n async function submit(value: string) {\n const trimmed = value.trim();\n if (!answer || !questionId || live || !trimmed || sending !== undefined) return;\n setRefusal(undefined); setSending(trimmed);\n try {\n const outcome = await answer(questionId, trimmed);\n if (outcome.kind === 'answered') setSent(trimmed);\n else setRefusal(outcome.reason || labels.answerRefused(outcome.kind));\n } catch (cause) { setRefusal(cause instanceof Error ? cause.message : labels.answerNotSent); }\n finally { if (mounted.current) setSending(undefined); }\n }\n const onSubmit = (event: FormEvent) => { event.preventDefault(); void submit(text); };\n const onSubmitForm = (event: FormEvent) => { event.preventDefault(); if (values.some(value => value.trim())) void submit(formAnswer(question.items, values, labels.skipped)); };\n const busy = sending !== undefined || live;\n\n const unavailable = !answer || !questionId || busy;\n // `m-0`: a host without a global preflight keeps the paragraph margin of ItemDescription.\n const wrap = 'm-0 line-clamp-none [overflow-wrap:anywhere]';\n\n if (!open) {\n const values = question.items.length > 0 && current !== undefined ? splitFormAnswer(question.items, current) : undefined;\n return <Item role=\"group\" data-testid=\"question-card\" data-state={state} data-call-id={call.id} aria-label={labels.question}>\n <ItemMedia variant=\"icon\">\n {state === 'answered' ? <CheckIcon role=\"img\" aria-label={labels.answered} /> : state === 'sent' ? <Spinner aria-label={labels.waitingForAgent} /> : <CircleSlashIcon role=\"img\" aria-label={labels.notAnswered} />}\n </ItemMedia>\n <ItemContent className=\"min-w-0\">\n {state === 'failed' && <ItemDescription data-testid=\"question-text\" className={wrap}>{question.question || labels.askedQuestion}</ItemDescription>}\n {values ? <div className=\"flex min-w-0 flex-col gap-2\">{question.items.map((item, i) => <div key={i} data-testid=\"question-answer-item\" className=\"flex min-w-0 flex-col gap-1\">\n <ItemDescription className={wrap}>{item.question}</ItemDescription>\n {values[i] === labels.skipped\n ? <ItemDescription data-testid=\"question-answer\" data-skipped=\"true\" className={wrap}>{values[i]}</ItemDescription>\n : <ItemTitle data-testid=\"question-answer\" className={wrap}>{values[i]}</ItemTitle>}\n </div>)}</div>\n : current !== undefined && <ItemTitle className={wrap}><span className=\"sr-only\">{labels.yourAnswer} </span><span data-testid=\"question-answer\" className=\"whitespace-pre-line\">{current}</span></ItemTitle>}\n {state === 'failed' && settled && 'failure' in settled && <ItemDescription role=\"alert\" className={wrap}>{settled.failure}</ItemDescription>}\n </ItemContent>\n </Item>;\n }\n\n const option = (value: string, selected: boolean, onClick: () => void) => <Button key={value} variant={selected ? 'secondary' : 'outline'} data-testid=\"question-option\" data-option={value}\n aria-pressed={question.items.length > 0 ? selected : undefined} disabled={unavailable} onClick={onClick} className=\"h-auto min-h-8 w-full justify-start whitespace-normal text-start\">\n {selected && <CheckIcon data-icon=\"inline-start\" aria-hidden=\"true\" />}{value}\n </Button>;\n\n return <Card role=\"group\" data-testid=\"question-card\" data-state={state} data-call-id={call.id} aria-label={labels.question}>\n <CardHeader>\n <CardDescription>{labels.questionForYou}</CardDescription>\n <CardTitle data-testid=\"question-text\" className=\"[overflow-wrap:anywhere]\">{question.question || labels.askedQuestion}</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-col gap-4\">\n {question.items.length > 0 ? <form data-testid=\"question-form\" onSubmit={onSubmitForm} className=\"flex flex-col gap-4\">\n {question.items.map((item, i) => <div key={i} role=\"group\" aria-labelledby={`${formId}-${i}`} data-testid=\"question-item\" className=\"flex min-w-0 flex-col gap-2\">\n <p id={`${formId}-${i}`} className=\"m-0 [overflow-wrap:anywhere]\">{i + 1}. {item.question}</p>\n {item.options.length > 0 && <div role=\"group\" aria-label={labels.answerOptions} className=\"flex flex-col gap-2\">\n {item.options.map(value => option(value, values[i] === value, () => setValue(i, values[i] === value ? '' : value)))}\n </div>}\n <Input data-testid=\"question-input\" value={item.options.includes(values[i] ?? '') ? '' : values[i] ?? ''} maxLength={2000} disabled={unavailable}\n placeholder={item.options.length ? labels.ownAnswer : labels.typeAnswer} aria-label={`${labels.yourAnswerInput} ${i + 1}`} onChange={event => setValue(i, event.currentTarget.value)} />\n </div>)}\n <div><Button type=\"submit\" data-testid=\"question-submit\" disabled={unavailable || !values.some(value => value.trim())}><SendIcon data-icon=\"inline-start\" aria-hidden=\"true\" />{labels.answer}</Button></div>\n </form> : <>\n {question.options.length > 0 && <div role=\"group\" aria-label={labels.answerOptions} className=\"flex flex-col gap-2\">\n {question.options.map(value => option(value, false, () => { void submit(value); }))}\n </div>}\n {question.allowFreeText && <form onSubmit={onSubmit} className=\"flex gap-2\">\n <Input data-testid=\"question-input\" value={text} maxLength={2000} disabled={unavailable} placeholder={question.options.length ? labels.ownAnswer : labels.typeAnswer}\n aria-label={labels.yourAnswerInput} onChange={event => setText(event.currentTarget.value)} />\n <Button type=\"submit\" data-testid=\"question-submit\" disabled={unavailable || !text.trim()}><SendIcon data-icon=\"inline-start\" aria-hidden=\"true\" />{labels.answer}</Button>\n </form>}\n </>}\n {live && <CardDescription>{labels.stillWritingQuestion}</CardDescription>}\n {!answer && !live && <CardDescription>{labels.answeringUnavailable}</CardDescription>}\n {refusal && <Alert variant=\"destructive\" data-testid=\"question-refusal\"><AlertDescription>{refusal}</AlertDescription></Alert>}\n </CardContent>\n </Card>;\n}\n",
|
|
18
18
|
"type": "registry:component",
|
|
19
19
|
"target": "components/pi-chat-question/question-card.tsx"
|
|
20
20
|
}
|
package/r/pi-chat-slash.json
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
"title": "Pi chat slash menu",
|
|
5
5
|
"description": "The composer's `/` menu of commands and skills (and the Commands entry of the + menu). Plug it in with `slash={{ ...config, Picker: SlashPicker }}`; without it pi-chat has no `/` menu.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0"
|
|
10
10
|
],
|
|
11
11
|
"registryDependencies": [
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json"
|
|
13
13
|
],
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
package/r/pi-chat-subagent.json
CHANGED
package/r/pi-chat-voice.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"title": "Pi chat voice messages",
|
|
5
5
|
"description": "Tap-to-record voice messages: a compatibility export of the kit's chat-react/voice recorder hook, the composer's record button and the pending voice bubble, plus useHoldToRecord. Plug it in with `voiceMessage={{ ...config, ...voiceMessagePlugin }}`; without it pi-chat has no voice button.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.53",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0"
|
|
10
10
|
],
|
|
11
11
|
"registryDependencies": [
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/utils.json",
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.53/r/pi-chat.json"
|
|
14
14
|
],
|
|
15
15
|
"files": [
|
|
16
16
|
{
|