ghostykit 0.7.0 → 0.8.0
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/README.md +38 -7
- package/dist/chunks/{chunk-FWBUIGTC.js → chunk-7M2B7VKH.js} +1 -1
- package/dist/chunks/{chunk-TMEHQZWL.js → chunk-KG23PQI2.js} +5 -2
- package/dist/chunks/{chunk-ZIUOXG7B.js → chunk-VFCROLFF.js} +75 -14
- package/dist/core/index.js +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/plugins/index.js +1 -1
- package/dist/react/Bubble.d.ts +5 -1
- package/dist/react/Composer.d.ts +12 -1
- package/dist/react/Piloto.d.ts +8 -0
- package/dist/react/context.d.ts +2 -0
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +79 -25
- package/dist/react/plugins.d.ts +53 -3
- package/dist/styles.css +114 -4
- package/dist/transport/index.d.ts +3 -1
- package/dist/transport/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ const ref = useRef<PilotoHandle>(null);
|
|
|
88
88
|
<Piloto
|
|
89
89
|
ref={ref}
|
|
90
90
|
transport={transport}
|
|
91
|
-
skin="easybits" // "ghosty" (default) | "easybits" | "neutral" | your own
|
|
91
|
+
skin="easybits" // "ghosty" (default) | "easybits" | "neutral" | "denik" | your own
|
|
92
92
|
theme="auto" // "light" | "dark" | "auto"
|
|
93
93
|
locale="en" // Spanish by default
|
|
94
94
|
agentName="Admin assistant"
|
|
@@ -106,7 +106,9 @@ ref.current?.send("Why did this fail?"); // ask on the user'
|
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
Other props: `features` (`attachments`, `retry`, `copy`, `usage`, `replyOptions`, `duration`,
|
|
109
|
-
`steer`
|
|
109
|
+
`steer`, `narration`: `false` keeps the text written before each tool as part of the answer instead of
|
|
110
|
+
checked progress lines), `icons` (`{ send?, stop? }`: your own nodes for the composer's round button;
|
|
111
|
+
default arrow and square), `references` / `onReferencesChange`, `placeholder`, `avatar`, `initialTurns`, `loadOlder`
|
|
110
112
|
(earlier pages of a long thread), `forwardedProps` (extra keys for every run, e.g.
|
|
111
113
|
`{permissions: "ask"}`), and the callbacks `onTurnStart`, `onTitle`, `onState`, `onEvent` (every raw
|
|
112
114
|
AG-UI event), `onSend`. The agent's shared state (`STATE_SNAPSHOT` + `STATE_DELTA`) is
|
|
@@ -152,6 +154,7 @@ const PLUGINS = [crm, modelPicker({ onChange: (model) => api.setModel(model) }),
|
|
|
152
154
|
| `strings` | Texts over the locale's. | `{ stop: "Para" }` |
|
|
153
155
|
| `markdown.transform` | Rewrites the answer's markdown, with the turn at hand (chained). | `(md, turn) => md.replace(/#(\d+)/g, "[#$1](#folio-$1)")` |
|
|
154
156
|
| `markdown.onLinkClick` | A click on a link; `true` if handled (asked in order). | open in-app |
|
|
157
|
+
| `markdown.components` | How a markdown element is drawn, by tag (gets its props and `ctx`): e.g. a link that is a CTA as a button. | `{ a: ({ href, children, ctx }) => … }` |
|
|
155
158
|
| `markdown.blocks` | Fenced blocks by name (```` ```eb-file ````) taken out of the text and drawn as cards under the answer; one still being written comes with `complete: false`. | `{ "eb-file": FileCard }` |
|
|
156
159
|
| `vars`, `className` | Branding on the root: `--piloto-*` tokens and classes. | `{ "--piloto-radius-bubble": "6px" }` |
|
|
157
160
|
| `tools.verbs` | How each tool is named in the steps. | `{ run_cron: { one, many, running } }` |
|
|
@@ -163,17 +166,21 @@ const PLUGINS = [crm, modelPicker({ onChange: (model) => api.setModel(model) }),
|
|
|
163
166
|
| `message.assistantBefore` | Before each answer's text, under its steps. | live page progress, "Creating image…" |
|
|
164
167
|
| `message.user` / `message.assistant` | Extras under each user message / answer. | `({ turn, live }) => …` |
|
|
165
168
|
| `message.formatUserText` | How the user's text is shown (chained). | strip app plumbing |
|
|
169
|
+
| `message.beforeSend` | Rewrites the text that goes out (chained). | prepend what was pointed at |
|
|
170
|
+
| `message.avatar` | The agent's avatar next to each answer. | `() => <img src={logo} />` |
|
|
166
171
|
| `composer.actions` | Buttons in the composer bar, between the image button and the clip. | "My files", "Point" |
|
|
167
172
|
| `composer.actionsEnd` | Buttons after the clip. | model picker |
|
|
168
173
|
| `composer.above` | Above the message box. | `<Subagents pilot={ctx.pilot} />` |
|
|
169
174
|
| `composer.below` | Under the message box. | suggestion chips |
|
|
170
|
-
| `composer.attachments` | `upload`, `transcribe`, `voiceNoteText`. | `{ upload:
|
|
171
|
-
| `
|
|
175
|
+
| `composer.attachments` | `upload`, `transcribe`, `voiceNoteText`, and the limits `accept`, `maxFiles`, `maxBytes`. A voice note keeps its audio attached when there is `upload`. | `{ upload, accept: "image/*", maxFiles: 4, maxBytes: 4_000_000 }` |
|
|
176
|
+
| `composer.replace` | `{ when(ctx), view }`: drawn instead of the message box while `when` is true. | trial over (403 + `x-needs-login`) → "Create your account" |
|
|
177
|
+
| `header` | The top bar, over the thread. A single view goes on the right; `{ start, end }` puts `start` on the left (who the agent is) and `end` on the right. | `{ start: avatar + name, end: new chat · close }` |
|
|
172
178
|
| `sidePanel` | Next to the chat. | live page, subagents |
|
|
173
179
|
| `aboveThread` | Above the messages. | a banner |
|
|
174
180
|
| `empty` | The empty thread (instead of the greeting). | onboarding |
|
|
175
181
|
| `waiting` | While the agent hasn't answered (instead of the waiting phrase). Gets `step`, `since`. | "Creating your image…" |
|
|
176
182
|
| `wrap` | A provider around the whole chat (`{ctx, children}`); the first plugin is outermost. | artifacts panel |
|
|
183
|
+
| `onHttpError` | The server refused a message before answering (`{status, code, message, headers}`; also in `pilot.state.httpError` until the next send). | 429 → upgrade banner |
|
|
177
184
|
| `onTurnStart` / `onTurnDone` | This client started / finished a turn. | open the last delivery |
|
|
178
185
|
|
|
179
186
|
Every view gets `ctx`: `{pilot, agentState, strings, send, addReference, addAttachments, setDraft, focus, plugins}`.
|
|
@@ -186,10 +193,10 @@ in the URL).
|
|
|
186
193
|
|
|
187
194
|
**Order and precedence**, in the order of the `plugins` list:
|
|
188
195
|
|
|
189
|
-
- **Maps** (`strings`, `tools.verbs`, `tools.icons`, `vars`) are merged, and later plugins win. `markdown` and `
|
|
196
|
+
- **Maps** (`strings`, `tools.verbs`, `tools.icons`, `vars`) are merged, and later plugins win. `markdown.transform`, `formatUserText` and `beforeSend` are chained.
|
|
190
197
|
- **`events` and `state`** call every handler, in order.
|
|
191
|
-
- **Keyed or single views** (`tools.render`, `message.custom`, `markdown.blocks`, `empty`, `waiting`, `composer.attachments`): the first plugin that declares one wins. A clash is a development warning.
|
|
192
|
-
- **Lists** (`header`, `sidePanel`, `aboveThread`, `composer.actions`/`actionsEnd`/`above`/`below`, `message.user`/`assistant`/`assistantBefore`, `wrap`, `onTurnStart`/`onTurnDone`) all apply, in order.
|
|
198
|
+
- **Keyed or single views** (`tools.render`, `message.custom`, `markdown.blocks`, `markdown.components`, `message.avatar`, `empty`, `waiting`, `composer.attachments`, `composer.replace`): the first plugin that declares one wins. A clash is a development warning.
|
|
199
|
+
- **Lists** (`header` and its `start`/`end`, `sidePanel`, `aboveThread`, `composer.actions`/`actionsEnd`/`above`/`below`, `message.user`/`assistant`/`assistantBefore`, `wrap`, `onTurnStart`/`onTurnDone`) all apply, in order.
|
|
193
200
|
|
|
194
201
|
In development, ghostykit also warns once about a `CUSTOM` event that nothing handles.
|
|
195
202
|
|
|
@@ -222,6 +229,30 @@ like ghosty.studio; 16 px in the others). Every color, radius, border width and
|
|
|
222
229
|
}
|
|
223
230
|
```
|
|
224
231
|
|
|
232
|
+
The variables a host (or a widget owner, inside an iframe) usually overrides:
|
|
233
|
+
|
|
234
|
+
| Variable | What it paints |
|
|
235
|
+
|---|---|
|
|
236
|
+
| `--piloto-font`, `--piloto-font-prose`, `--piloto-font-mono` | UI text, the answers' prose, code |
|
|
237
|
+
| `--piloto-rem` | The size unit inside the chat (`1rem`) |
|
|
238
|
+
| `--piloto-bg`, `--piloto-surface`, `--piloto-surface-2` | Background, cards and fields, hover/secondary |
|
|
239
|
+
| `--piloto-text`, `--piloto-text-2`, `--piloto-text-3` | Text, secondary text, hints |
|
|
240
|
+
| `--piloto-accent`, `--piloto-accent-strong`, `--piloto-accent-soft`, `--piloto-on-accent` | Brand color (buttons, focus, links), its pressed and soft tones, text on it |
|
|
241
|
+
| `--piloto-user-bubble`, `--piloto-user-text` | The user's bubble |
|
|
242
|
+
| `--piloto-composer-bg`, `--piloto-shadow-input` | The message box |
|
|
243
|
+
| `--piloto-border`, `--piloto-border-width` | Borders |
|
|
244
|
+
| `--piloto-radius-bubble`, `--piloto-radius-input`, `--piloto-radius-card`, `--piloto-radius-button` | Corners |
|
|
245
|
+
| `--piloto-radius-bubble-tail` | The bottom-right corner of the user's bubble (its "tail"); defaults to `--piloto-radius-bubble` |
|
|
246
|
+
| `--piloto-success`, `--piloto-danger`, `--piloto-warning`, `--piloto-warning-ink` | States, and the approval pass |
|
|
247
|
+
|
|
248
|
+
Set them in your CSS on `.piloto`, or from a plugin with `vars` (e.g. the owner's theme of an
|
|
249
|
+
embedded widget): `definePlugin({ name: "theme", vars: { "--piloto-accent": owner.color, "--piloto-radius-input": "8px" } })`.
|
|
250
|
+
|
|
251
|
+
The `denik` skin is Deník's Nik: blue `#5158F6`, ink `#11151A`, Satoshi (the host loads the font), a
|
|
252
|
+
dark user bubble with an 8 px tail, the answer without a bubble (add the avatar with
|
|
253
|
+
`message.avatar`), and a 36 px round send button that turns into an ink circle while stopping. Pair it
|
|
254
|
+
with `icons={{ send: <PaperPlane /> }}` and a plugin `header: { start, end }`.
|
|
255
|
+
|
|
225
256
|
| Ghosty | EasyBits | Neutral |
|
|
226
257
|
|---|---|---|
|
|
227
258
|
|  |  |  |
|
|
@@ -439,7 +439,7 @@ function finish(s0, e, now, failed = false) {
|
|
|
439
439
|
}
|
|
440
440
|
function beginSend(prev, text, attachments = [], now = Date.now()) {
|
|
441
441
|
const turns = text || attachments.length ? [...prev.turns, { role: "user", text, ...attachments.length ? { attachments } : {} }] : prev.turns;
|
|
442
|
-
return { ...prev, turns, busy: true, error: null, notice: null, cutRun: null, turnStartedAt: now, effects: [{ type: "turn-start" }] };
|
|
442
|
+
return { ...prev, turns, busy: true, error: null, notice: null, cutRun: null, httpError: null, turnStartedAt: now, effects: [{ type: "turn-start" }] };
|
|
443
443
|
}
|
|
444
444
|
function beginSteer(prev, text) {
|
|
445
445
|
return { ...prev, turns: [...prev.turns, { role: "user", text }], notice: null, openText: null, effects: [] };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/transport/index.ts
|
|
2
2
|
var PilotHttpError = class extends Error {
|
|
3
|
-
constructor(message, status, code, retryAfter) {
|
|
3
|
+
constructor(message, status, code, retryAfter, headers) {
|
|
4
4
|
super(message);
|
|
5
5
|
this.name = "PilotHttpError";
|
|
6
6
|
this.status = status;
|
|
7
7
|
this.code = code;
|
|
8
8
|
this.retryAfter = retryAfter;
|
|
9
|
+
this.headers = headers;
|
|
9
10
|
}
|
|
10
11
|
};
|
|
11
12
|
async function readSse(body, onMessage) {
|
|
@@ -62,7 +63,9 @@ function createAgUiTransport(opts) {
|
|
|
62
63
|
}
|
|
63
64
|
if (!res.ok) {
|
|
64
65
|
const json = await res.json().catch(() => null);
|
|
65
|
-
|
|
66
|
+
const headers = {};
|
|
67
|
+
res.headers?.forEach?.((v, k) => void (headers[k.toLowerCase()] = v));
|
|
68
|
+
throw new PilotHttpError(json?.message ?? json?.error ?? `Error ${res.status}`, res.status, json?.error, retryAfter, headers);
|
|
66
69
|
}
|
|
67
70
|
if (!res.body) return;
|
|
68
71
|
const serverDate = Date.parse(res.headers.get("date") ?? "");
|
|
@@ -942,8 +942,25 @@ var __iconData37 = {
|
|
|
942
942
|
__iconData37.node;
|
|
943
943
|
var UsersRound = createLucideIcon(__iconData37);
|
|
944
944
|
|
|
945
|
-
// ../../node_modules/lucide-react/dist/esm/icons/
|
|
945
|
+
// ../../node_modules/lucide-react/dist/esm/icons/wrench.mjs
|
|
946
946
|
var __iconData38 = {
|
|
947
|
+
name: "wrench",
|
|
948
|
+
size: 24,
|
|
949
|
+
node: [
|
|
950
|
+
[
|
|
951
|
+
"path",
|
|
952
|
+
{
|
|
953
|
+
d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",
|
|
954
|
+
key: "1ngwbx"
|
|
955
|
+
}
|
|
956
|
+
]
|
|
957
|
+
]
|
|
958
|
+
};
|
|
959
|
+
__iconData38.node;
|
|
960
|
+
var Wrench = createLucideIcon(__iconData38);
|
|
961
|
+
|
|
962
|
+
// ../../node_modules/lucide-react/dist/esm/icons/x.mjs
|
|
963
|
+
var __iconData39 = {
|
|
947
964
|
name: "x",
|
|
948
965
|
size: 24,
|
|
949
966
|
node: [
|
|
@@ -951,8 +968,8 @@ var __iconData38 = {
|
|
|
951
968
|
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
952
969
|
]
|
|
953
970
|
};
|
|
954
|
-
|
|
955
|
-
var X = createLucideIcon(
|
|
971
|
+
__iconData39.node;
|
|
972
|
+
var X = createLucideIcon(__iconData39);
|
|
956
973
|
|
|
957
974
|
// src/react/Chips.tsx
|
|
958
975
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1033,6 +1050,9 @@ function mimeOf(file) {
|
|
|
1033
1050
|
const ext = (file.name.match(/\.([A-Za-z0-9]+)$/)?.[1] || "").toLowerCase();
|
|
1034
1051
|
return BY_EXTENSION[ext] ?? "application/octet-stream";
|
|
1035
1052
|
}
|
|
1053
|
+
function accepts(accept, name, mime) {
|
|
1054
|
+
return accept.split(",").map((x) => x.trim().toLowerCase()).filter(Boolean).some((rule) => rule.startsWith(".") ? name.toLowerCase().endsWith(rule) : rule.endsWith("/*") ? mime.startsWith(rule.slice(0, -1)) : mime === rule);
|
|
1055
|
+
}
|
|
1036
1056
|
var isText = (mime) => mime.startsWith("text/") || mime === "application/json" || mime.endsWith("+json") || mime.endsWith("+xml");
|
|
1037
1057
|
function shortSize(bytes) {
|
|
1038
1058
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -1073,9 +1093,13 @@ function Composer({
|
|
|
1073
1093
|
extras,
|
|
1074
1094
|
extrasEnd,
|
|
1075
1095
|
handleRef,
|
|
1096
|
+
accept,
|
|
1076
1097
|
maxFiles = 8,
|
|
1077
|
-
maxFileMb = 25
|
|
1098
|
+
maxFileMb = 25,
|
|
1099
|
+
icons
|
|
1078
1100
|
}) {
|
|
1101
|
+
const sendIcon = icons?.send ?? /* @__PURE__ */ jsx2(ArrowUp, { className: "h-4 w-4" });
|
|
1102
|
+
const stopIcon = icons?.stop ?? /* @__PURE__ */ jsx2(Square, { className: "h-3 w-3 fill-current" });
|
|
1079
1103
|
const { strings: t } = usePiloto();
|
|
1080
1104
|
const [value, setValue] = useState(initialValue);
|
|
1081
1105
|
const [files, setFiles] = useState([]);
|
|
@@ -1129,6 +1153,10 @@ function Composer({
|
|
|
1129
1153
|
rejected.push(t.tooBig(file.name, maxFileMb));
|
|
1130
1154
|
continue;
|
|
1131
1155
|
}
|
|
1156
|
+
if (accept && !accepts(accept, file.name, mime)) {
|
|
1157
|
+
rejected.push(t.cantUpload(file.name));
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1132
1160
|
if (next.length >= maxFiles) {
|
|
1133
1161
|
rejected.push(t.tooMany(maxFiles));
|
|
1134
1162
|
break;
|
|
@@ -1150,7 +1178,7 @@ function Composer({
|
|
|
1150
1178
|
setNotice(rejected.length ? rejected.join(" \xB7 ") : null);
|
|
1151
1179
|
ref.current?.focus({ preventScroll: true });
|
|
1152
1180
|
},
|
|
1153
|
-
[busy, canSeeImages, upload, maxFiles, maxFileMb, t]
|
|
1181
|
+
[busy, canSeeImages, upload, maxFiles, maxFileMb, accept, t]
|
|
1154
1182
|
);
|
|
1155
1183
|
useEffect(() => {
|
|
1156
1184
|
if (!withFiles) return;
|
|
@@ -1406,8 +1434,8 @@ function Composer({
|
|
|
1406
1434
|
onClick: toggleRecording,
|
|
1407
1435
|
"aria-label": t.stopAndSendVoice,
|
|
1408
1436
|
title: t.send,
|
|
1409
|
-
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-brand-500 text-white hover:bg-brand-600",
|
|
1410
|
-
children:
|
|
1437
|
+
className: "gc-send-voice flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-brand-500 text-white hover:bg-brand-600",
|
|
1438
|
+
children: sendIcon
|
|
1411
1439
|
}
|
|
1412
1440
|
)
|
|
1413
1441
|
] })
|
|
@@ -1434,7 +1462,7 @@ function Composer({
|
|
|
1434
1462
|
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1435
1463
|
/* @__PURE__ */ jsxs2("div", { className: "@container flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden", children: [
|
|
1436
1464
|
withFiles && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1437
|
-
/* @__PURE__ */ jsx2(
|
|
1465
|
+
(!accept || accepts(accept, "x.png", "image/png")) && /* @__PURE__ */ jsx2(
|
|
1438
1466
|
"button",
|
|
1439
1467
|
{
|
|
1440
1468
|
type: "button",
|
|
@@ -1451,13 +1479,13 @@ function Composer({
|
|
|
1451
1479
|
}
|
|
1452
1480
|
),
|
|
1453
1481
|
extras,
|
|
1454
|
-
/* @__PURE__ */ jsx2(
|
|
1482
|
+
(!accept || !/^\s*image\/\*\s*$/.test(accept)) && /* @__PURE__ */ jsx2(
|
|
1455
1483
|
"button",
|
|
1456
1484
|
{
|
|
1457
1485
|
type: "button",
|
|
1458
1486
|
onClick: () => {
|
|
1459
1487
|
if (!fileRef.current) return;
|
|
1460
|
-
fileRef.current.accept = "";
|
|
1488
|
+
fileRef.current.accept = accept ?? "";
|
|
1461
1489
|
fileRef.current.click();
|
|
1462
1490
|
},
|
|
1463
1491
|
disabled: busy || files.length >= maxFiles,
|
|
@@ -1492,8 +1520,9 @@ function Composer({
|
|
|
1492
1520
|
onClick: busy ? onStop : () => void submit(),
|
|
1493
1521
|
disabled: !busy && !canSend || uploading,
|
|
1494
1522
|
"aria-label": busy ? t.stop : uploading ? t.uploading : t.send,
|
|
1523
|
+
"data-state": busy ? "stop" : "send",
|
|
1495
1524
|
className: `gc-send flex h-8 w-8 items-center justify-center rounded-full transition-colors ${busy || canSend || uploading ? "bg-brand-500 text-white hover:bg-brand-600" : "bg-background-disabled text-text-disabled"}`,
|
|
1496
|
-
children: busy ?
|
|
1525
|
+
children: busy ? stopIcon : uploading ? /* @__PURE__ */ jsx2(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : sendIcon
|
|
1497
1526
|
}
|
|
1498
1527
|
)
|
|
1499
1528
|
] })
|
|
@@ -1555,6 +1584,9 @@ function ModelPicker({ models, current, onChange }) {
|
|
|
1555
1584
|
}
|
|
1556
1585
|
|
|
1557
1586
|
// src/react/plugins.ts
|
|
1587
|
+
function isView(x) {
|
|
1588
|
+
return typeof x === "function" || typeof x === "object" && x !== null && "$$typeof" in x;
|
|
1589
|
+
}
|
|
1558
1590
|
function definePlugin(plugin) {
|
|
1559
1591
|
return plugin;
|
|
1560
1592
|
}
|
|
@@ -1583,12 +1615,23 @@ function pluginPoints(p) {
|
|
|
1583
1615
|
add(p.message?.assistant, "message.assistant");
|
|
1584
1616
|
for (const k of Object.keys(p.message?.custom ?? {})) out.push(`message.custom[${k}]`);
|
|
1585
1617
|
add(p.message?.formatUserText, "message.formatUserText");
|
|
1618
|
+
add(p.message?.beforeSend, "message.beforeSend");
|
|
1619
|
+
add(p.message?.avatar, "message.avatar");
|
|
1620
|
+
for (const k of Object.keys(p.markdown?.components ?? {})) out.push(`markdown.components[${k}]`);
|
|
1621
|
+
add(p.composer?.replace, "composer.replace");
|
|
1622
|
+
add(p.onHttpError, "onHttpError");
|
|
1586
1623
|
add(p.composer?.actions, "composer.actions");
|
|
1587
1624
|
add(p.composer?.actionsEnd, "composer.actionsEnd");
|
|
1588
1625
|
add(p.composer?.above, "composer.above");
|
|
1589
1626
|
add(p.composer?.below, "composer.below");
|
|
1590
1627
|
add(p.composer?.attachments, "composer.attachments");
|
|
1591
|
-
|
|
1628
|
+
if (p.header) {
|
|
1629
|
+
if (isView(p.header)) out.push("header");
|
|
1630
|
+
else {
|
|
1631
|
+
add(p.header.start, "header.start");
|
|
1632
|
+
add(p.header.end, "header.end");
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1592
1635
|
add(p.sidePanel, "sidePanel");
|
|
1593
1636
|
add(p.aboveThread, "aboveThread");
|
|
1594
1637
|
add(p.empty, "empty");
|
|
@@ -1611,8 +1654,12 @@ function resolvePlugins(list = []) {
|
|
|
1611
1654
|
toolRenders: {},
|
|
1612
1655
|
customRenders: {},
|
|
1613
1656
|
blockRenders: {},
|
|
1657
|
+
components: {},
|
|
1658
|
+
replacers: [],
|
|
1659
|
+
httpError: [],
|
|
1614
1660
|
attachments: {},
|
|
1615
1661
|
header: [],
|
|
1662
|
+
headerStart: [],
|
|
1616
1663
|
sidePanel: [],
|
|
1617
1664
|
aboveThread: [],
|
|
1618
1665
|
composerActions: [],
|
|
@@ -1633,6 +1680,7 @@ function resolvePlugins(list = []) {
|
|
|
1633
1680
|
const transforms = [];
|
|
1634
1681
|
const linkHandlers = [];
|
|
1635
1682
|
const formatters = [];
|
|
1683
|
+
const beforeSends = [];
|
|
1636
1684
|
const names = /* @__PURE__ */ new Set();
|
|
1637
1685
|
const first = (slot, key, plugin, value, point) => {
|
|
1638
1686
|
if (slot[key]) r.warnings.push(`\xAB${plugin}\xBB y \xAB${slot[key].plugin}\xBB dibujan ${point}[${key}]: gana \xAB${slot[key].plugin}\xBB (va antes).`);
|
|
@@ -1654,13 +1702,18 @@ function resolvePlugins(list = []) {
|
|
|
1654
1702
|
for (const [k, v] of Object.entries(p.message?.custom ?? {})) first(r.customRenders, k, p.name, v, "message.custom");
|
|
1655
1703
|
for (const [k, v] of Object.entries(p.markdown?.blocks ?? {})) first(r.blockRenders, k, p.name, v, "markdown.blocks");
|
|
1656
1704
|
if (p.styles === "ghostykit") r.ghostykitStyled.add(p.name);
|
|
1705
|
+
for (const [k, v] of Object.entries(p.markdown?.components ?? {})) first(r.components, k, p.name, v, "markdown.components");
|
|
1706
|
+
if (p.message?.avatar) r.avatar ? r.warnings.push(`\xAB${p.name}\xBB tambi\xE9n da message.avatar: gana \xAB${r.avatar.plugin}\xBB.`) : r.avatar = own(p.message.avatar);
|
|
1707
|
+
if (p.message?.beforeSend) beforeSends.push(p.message.beforeSend);
|
|
1708
|
+
if (p.composer?.replace) r.replacers.push(own(p.composer.replace));
|
|
1709
|
+
if (p.onHttpError) r.httpError.push(own(p.onHttpError));
|
|
1657
1710
|
if (p.composer?.actionsEnd) r.composerActionsEnd.push(own(p.composer.actionsEnd));
|
|
1658
1711
|
if (p.composer?.below) r.composerBelow.push(own(p.composer.below));
|
|
1659
1712
|
if (p.message?.assistantBefore) r.messageBefore.push(own(p.message.assistantBefore));
|
|
1660
1713
|
if (p.wrap) r.wraps.push(own(p.wrap));
|
|
1661
1714
|
if (p.onTurnStart) r.turnStart.push(own(p.onTurnStart));
|
|
1662
1715
|
if (p.onTurnDone) r.turnDone.push(own(p.onTurnDone));
|
|
1663
|
-
for (const key of ["upload", "transcribe", "voiceNoteText"]) {
|
|
1716
|
+
for (const key of ["upload", "transcribe", "voiceNoteText", "accept", "maxFiles", "maxBytes"]) {
|
|
1664
1717
|
const v = p.composer?.attachments?.[key];
|
|
1665
1718
|
if (!v) continue;
|
|
1666
1719
|
if (r.attachments[key]) r.warnings.push(`\xAB${p.name}\xBB tambi\xE9n da composer.attachments.${key}: gana el primero.`);
|
|
@@ -1668,7 +1721,13 @@ function resolvePlugins(list = []) {
|
|
|
1668
1721
|
}
|
|
1669
1722
|
if (p.empty) r.empty ? r.warnings.push(`\xAB${p.name}\xBB tambi\xE9n dibuja empty: gana \xAB${r.empty.plugin}\xBB.`) : r.empty = own(p.empty);
|
|
1670
1723
|
if (p.waiting) r.waiting ? r.warnings.push(`\xAB${p.name}\xBB tambi\xE9n dibuja waiting: gana \xAB${r.waiting.plugin}\xBB.`) : r.waiting = own(p.waiting);
|
|
1671
|
-
if (p.header)
|
|
1724
|
+
if (p.header) {
|
|
1725
|
+
if (isView(p.header)) r.header.push(own(p.header));
|
|
1726
|
+
else {
|
|
1727
|
+
if (p.header.start) r.headerStart.push(own(p.header.start));
|
|
1728
|
+
if (p.header.end) r.header.push(own(p.header.end));
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1672
1731
|
if (p.sidePanel) r.sidePanel.push(own(p.sidePanel));
|
|
1673
1732
|
if (p.aboveThread) r.aboveThread.push(own(p.aboveThread));
|
|
1674
1733
|
if (p.composer?.actions) r.composerActions.push(own(p.composer.actions));
|
|
@@ -1685,6 +1744,7 @@ function resolvePlugins(list = []) {
|
|
|
1685
1744
|
}
|
|
1686
1745
|
if (transforms.length) r.markdown.transform = (md, turn) => transforms.reduce((acc, f) => f(acc, turn), md);
|
|
1687
1746
|
if (linkHandlers.length) r.markdown.onLinkClick = (href, ev) => linkHandlers.some((f) => f(href, ev));
|
|
1747
|
+
if (beforeSends.length) r.beforeSend = (t, ctx) => beforeSends.reduce((acc, f) => f(acc, ctx), t);
|
|
1688
1748
|
if (formatters.length) r.formatUserText = (t) => formatters.reduce((acc, f) => f(acc), t);
|
|
1689
1749
|
return r;
|
|
1690
1750
|
}
|
|
@@ -1736,6 +1796,7 @@ export {
|
|
|
1736
1796
|
Send,
|
|
1737
1797
|
ShieldAlert,
|
|
1738
1798
|
UsersRound,
|
|
1799
|
+
Wrench,
|
|
1739
1800
|
X,
|
|
1740
1801
|
STRINGS_ES,
|
|
1741
1802
|
STRINGS_EN,
|
package/dist/core/index.js
CHANGED
package/dist/core/types.d.ts
CHANGED
|
@@ -159,6 +159,13 @@ export interface PilotState {
|
|
|
159
159
|
/** Cuándo empezó el turno que ESTE cliente encargó (0 = ninguno). */
|
|
160
160
|
turnStartedAt: number;
|
|
161
161
|
runId: string | null;
|
|
162
|
+
/** El último rechazo HTTP del servidor (antes del stream): para avisos propios (p. ej. 403 + `x-needs-login`). Se limpia al mandar. */
|
|
163
|
+
httpError?: {
|
|
164
|
+
status: number;
|
|
165
|
+
code?: string;
|
|
166
|
+
message: string;
|
|
167
|
+
headers?: Record<string, string>;
|
|
168
|
+
} | null;
|
|
162
169
|
/** El estado compartido del agente (`STATE_SNAPSHOT`, con `STATE_DELTA` aplicado encima). */
|
|
163
170
|
agentState: Record<string, unknown>;
|
|
164
171
|
/** El paso con nombre en curso (`STEP_STARTED`), p. ej. `waking`. */
|
package/dist/plugins/index.js
CHANGED
package/dist/react/Bubble.d.ts
CHANGED
|
@@ -40,10 +40,14 @@ export interface AgentBubbleProps {
|
|
|
40
40
|
renderTools?: (turn: Turn, live: boolean) => ReactNode;
|
|
41
41
|
/** Antes del texto de la respuesta, bajo sus pasos. */
|
|
42
42
|
renderBefore?: (turn: Turn, live: boolean) => ReactNode;
|
|
43
|
+
/** `false`: el texto antes de cada herramienta NO se vuelve paso con palomita (todo es respuesta). */
|
|
44
|
+
narration?: boolean;
|
|
45
|
+
/** El avatar del agente a la izquierda de la respuesta. */
|
|
46
|
+
avatar?: ReactNode;
|
|
43
47
|
/** Bloques con nombre (```eb-file```) que salen del texto y se pintan como tarjetas bajo la respuesta. */
|
|
44
48
|
blocks?: {
|
|
45
49
|
langs: string[];
|
|
46
50
|
render: (blocks: TextBlock[], turn: Turn, live: boolean) => ReactNode;
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
|
-
export declare function AgentBubble(
|
|
53
|
+
export declare function AgentBubble(props: AgentBubbleProps): import("react").JSX.Element;
|
package/dist/react/Composer.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface ComposerHandle {
|
|
|
16
16
|
}
|
|
17
17
|
/** El tipo del archivo, deducido del nombre cuando el navegador no lo dice (pasa más de lo que parece). */
|
|
18
18
|
export declare function mimeOf(file: File): string;
|
|
19
|
+
/** ¿Cabe en un `accept` («image/*,.pdf,application/json»)? */
|
|
20
|
+
export declare function accepts(accept: string, name: string, mime: string): boolean;
|
|
19
21
|
export interface ComposerProps {
|
|
20
22
|
onSubmit: (text: string, attachments: Attachment[]) => void;
|
|
21
23
|
onStop?: () => void;
|
|
@@ -46,10 +48,19 @@ export interface ComposerProps {
|
|
|
46
48
|
extrasEnd?: ReactNode;
|
|
47
49
|
/** Para agregar adjuntos desde afuera. */
|
|
48
50
|
handleRef?: Ref<ComposerHandle>;
|
|
51
|
+
/** Qué se puede adjuntar (como el `accept` de un input). Default: todo. */
|
|
52
|
+
accept?: string;
|
|
49
53
|
maxFiles?: number;
|
|
50
54
|
maxFileMb?: number;
|
|
55
|
+
/** Íconos del botón de mandar y de detener. Default: flecha hacia arriba y cuadrito. */
|
|
56
|
+
icons?: ComposerIcons;
|
|
51
57
|
}
|
|
52
|
-
|
|
58
|
+
/** Los íconos propios del botón redondo del composer (`<Piloto icons={…} />`). */
|
|
59
|
+
export interface ComposerIcons {
|
|
60
|
+
send?: ReactNode;
|
|
61
|
+
stop?: ReactNode;
|
|
62
|
+
}
|
|
63
|
+
export declare function Composer({ onSubmit, onStop, busy, canSteer, placeholder, initialValue, autoFocus, canSeeImages, attachments: withFiles, upload, transcribe, voiceNoteText, models, currentModel, onModelChange, references, onRemoveReference, onActivateReference, extras, extrasEnd, handleRef, accept, maxFiles, maxFileMb, icons, }: ComposerProps): import("react").JSX.Element;
|
|
53
64
|
/**
|
|
54
65
|
* El selector de modelo. PORTADO de Ghosty (`ModelPicker.tsx`): dropdown discreto hacia arriba, sin
|
|
55
66
|
* Radix; cierra con clic fuera y con Escape.
|
package/dist/react/Piloto.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CSSProperties, type ReactNode } from "react";
|
|
2
2
|
import type { Reference } from "../core/types";
|
|
3
3
|
import type { PilotTransport } from "../transport";
|
|
4
|
+
import { type ComposerIcons } from "./Composer";
|
|
4
5
|
import { type PilotTheme } from "./context";
|
|
5
6
|
import { type PilotoPlugin, type PluginCtx } from "./plugins";
|
|
6
7
|
import { type Pilot, type UsePilotOptions } from "./usePilot";
|
|
@@ -19,6 +20,11 @@ export interface PilotoFeatures {
|
|
|
19
20
|
duration?: boolean;
|
|
20
21
|
/** Mandar con el run vivo (steer). Default: lo que diga el agente en `state.caps.steer`. */
|
|
21
22
|
steer?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Lo que el agente dice ANTES de cada herramienta como pasos con palomita («Reviso tus crons.»).
|
|
25
|
+
* Default: sí. `false`: todo el texto es respuesta (los pasos siguen en su caja).
|
|
26
|
+
*/
|
|
27
|
+
narration?: boolean;
|
|
22
28
|
}
|
|
23
29
|
export interface PilotoProps extends UsePilotOptions {
|
|
24
30
|
transport: PilotTransport | null;
|
|
@@ -35,6 +41,8 @@ export interface PilotoProps extends UsePilotOptions {
|
|
|
35
41
|
welcome?: ReactNode;
|
|
36
42
|
avatar?: ReactNode;
|
|
37
43
|
placeholder?: string;
|
|
44
|
+
/** Íconos del botón redondo del composer: `send` (mandar) y `stop` (detener). Default: flecha y cuadrito. */
|
|
45
|
+
icons?: ComposerIcons;
|
|
38
46
|
suggestions?: string[];
|
|
39
47
|
features?: PilotoFeatures;
|
|
40
48
|
/**
|
package/dist/react/context.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type PilotTheme = "light" | "dark" | "auto";
|
|
|
11
11
|
export interface MarkdownOptions {
|
|
12
12
|
/** Reescribe el markdown antes de pintarlo (p. ej. `#12` → `[#12](#folio-12)`). */
|
|
13
13
|
transform?: (md: string, turn?: Turn) => string;
|
|
14
|
+
/** Cómo se pinta un elemento del markdown, por etiqueta (`a`, `code`…). */
|
|
15
|
+
components?: Record<string, ComponentType<any>>;
|
|
14
16
|
/** Un clic en un enlace. Devuelve `true` si la app lo manejó (no se abre el aviso de salida). */
|
|
15
17
|
onLinkClick?: (href: string, event: MouseEvent) => boolean;
|
|
16
18
|
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export { Piloto, Piloto as default, type PilotoFeatures, type PilotoHandle, type PilotoProps } from "./Piloto";
|
|
2
|
-
export { definePlugin, pluginPoints, pluginRequires, resolvePlugins, type PilotoPlugin, type PluginCtx, type PluginEvent, type PluginView, type ResolvedPlugins, } from "./plugins";
|
|
2
|
+
export { definePlugin, pluginPoints, pluginRequires, resolvePlugins, type PilotoPlugin, type PluginCtx, type PluginEvent, type PluginHeader, type PluginView, type ResolvedPlugins, } from "./plugins";
|
|
3
3
|
export { toAgUiContext, usePilot, type Pilot, type SendOptions, type TurnDoneInfo, type TurnEvent, type UsePilotOptions } from "./usePilot";
|
|
4
4
|
export { AgentBubble, CopyUserMessage, ReplyOptionButtons, TurnFooter, UsageStats, UserBubble, type AgentBubbleProps } from "./Bubble";
|
|
5
5
|
export { AgentSteps, ArcadeIcon, ClawIcon, DEFAULT_TOOL_ICONS, Narration, RobotArmIcon, ToolRow } from "./Steps";
|
|
6
6
|
export { LinkSafetyModal, Markdown } from "./Markdown";
|
|
7
7
|
export { InlineMarkdown, renderInline } from "./InlineMarkdown";
|
|
8
8
|
export { useTextAtPace } from "./TextAtPace";
|
|
9
|
-
export { Composer, ModelPicker, mimeOf, type ComposerHandle, type ComposerProps, type ModelOption, type UploadResult } from "./Composer";
|
|
9
|
+
export { Composer, ModelPicker, mimeOf, type ComposerHandle, type ComposerIcons, type ComposerProps, type ModelOption, type UploadResult } from "./Composer";
|
|
10
10
|
export { ConfirmCard, DecisionLines } from "./ConfirmCard";
|
|
11
11
|
export { ReferenceChips, Suggestions } from "./Chips";
|
|
12
12
|
export { TypingDots, WaitLabel } from "./Status";
|
package/dist/react/index.js
CHANGED
|
@@ -24,12 +24,12 @@ import {
|
|
|
24
24
|
subagentsOf,
|
|
25
25
|
takeQueued,
|
|
26
26
|
toPermission
|
|
27
|
-
} from "../chunks/chunk-
|
|
27
|
+
} from "../chunks/chunk-7M2B7VKH.js";
|
|
28
28
|
import {
|
|
29
29
|
PilotHttpError,
|
|
30
30
|
createAgUiTransport,
|
|
31
31
|
readSse
|
|
32
|
-
} from "../chunks/chunk-
|
|
32
|
+
} from "../chunks/chunk-KG23PQI2.js";
|
|
33
33
|
import {
|
|
34
34
|
Activity,
|
|
35
35
|
AppWindow,
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
Suggestions,
|
|
71
71
|
TurnContext,
|
|
72
72
|
UsersRound,
|
|
73
|
+
Wrench,
|
|
73
74
|
X,
|
|
74
75
|
definePlugin,
|
|
75
76
|
isDev,
|
|
@@ -79,7 +80,7 @@ import {
|
|
|
79
80
|
resolvePlugins,
|
|
80
81
|
rootProps,
|
|
81
82
|
usePiloto
|
|
82
|
-
} from "../chunks/chunk-
|
|
83
|
+
} from "../chunks/chunk-VFCROLFF.js";
|
|
83
84
|
import {
|
|
84
85
|
TOOL_VERBS_EN,
|
|
85
86
|
TOOL_VERBS_ES,
|
|
@@ -441,7 +442,7 @@ function Markdown({ children, animate = false, className }) {
|
|
|
441
442
|
{
|
|
442
443
|
onClickCapture: onClick,
|
|
443
444
|
className: `ghosty-md min-w-0 max-w-full break-words ${className ?? "text-text-primary"} [&_pre]:max-w-full [&_pre]:overflow-x-auto [&_table]:block [&_table]:max-w-full [&_table]:overflow-x-auto`,
|
|
444
|
-
children: animate ? /* @__PURE__ */ jsx3(ChunksContext.Provider, { value: { registry, part: 0 }, children: /* @__PURE__ */ jsx3(Streamdown, { BlockComponent: ChunkedBlock, linkSafety, children: text }) }) : /* @__PURE__ */ jsx3(Streamdown, { linkSafety, children: text })
|
|
445
|
+
children: animate ? /* @__PURE__ */ jsx3(ChunksContext.Provider, { value: { registry, part: 0 }, children: /* @__PURE__ */ jsx3(Streamdown, { BlockComponent: ChunkedBlock, linkSafety, components: markdown.components, children: text }) }) : /* @__PURE__ */ jsx3(Streamdown, { linkSafety, components: markdown.components, children: text })
|
|
445
446
|
}
|
|
446
447
|
)
|
|
447
448
|
);
|
|
@@ -544,7 +545,7 @@ var DEFAULT_TOOL_ICONS = {
|
|
|
544
545
|
};
|
|
545
546
|
function iconFor(t, icons) {
|
|
546
547
|
for (const name of toolNames(t)) if (icons[name]) return icons[name];
|
|
547
|
-
return t.kind && (icons[t.kind] ?? DEFAULT_TOOL_ICONS[t.kind]) ||
|
|
548
|
+
return t.kind && (icons[t.kind] ?? DEFAULT_TOOL_ICONS[t.kind]) || icons["*"] || Wrench;
|
|
548
549
|
}
|
|
549
550
|
var VISIBLE = 5;
|
|
550
551
|
function AgentSteps({ tools, live }) {
|
|
@@ -678,7 +679,7 @@ function UserBubble({ turn }) {
|
|
|
678
679
|
},
|
|
679
680
|
i
|
|
680
681
|
)) }),
|
|
681
|
-
(inBubble.length > 0 || visible.trim()) && /* @__PURE__ */ jsxs4("div", { className: "gc-msg-user max-w-[75%] whitespace-pre-wrap break-words rounded-(--piloto-radius-bubble) bg-(--piloto-user-bubble) px-4 py-3 font-sans text-base leading-[22px] text-(--piloto-user-text)", children: [
|
|
682
|
+
(inBubble.length > 0 || visible.trim()) && /* @__PURE__ */ jsxs4("div", { className: "gc-msg-user max-w-[75%] whitespace-pre-wrap break-words rounded-(--piloto-radius-bubble) rounded-br-[var(--piloto-radius-bubble-tail,var(--piloto-radius-bubble))] bg-(--piloto-user-bubble) px-4 py-3 font-sans text-base leading-[22px] text-(--piloto-user-text)", children: [
|
|
682
683
|
inBubble.length > 0 && /* @__PURE__ */ jsx6("div", { className: `flex flex-wrap justify-end gap-1.5 ${visible.trim() ? "mb-2" : ""}`, children: inBubble.map(
|
|
683
684
|
(a, i) => a.mimeType.startsWith("image/") ? /* @__PURE__ */ jsx6("img", { src: a.data ? `data:${a.mimeType};base64,${a.data}` : a.url, alt: "", className: "h-20 w-20 rounded-lg object-cover" }, i) : /* @__PURE__ */ jsx6("audio", { src: a.url, controls: true, preload: "metadata", className: "h-9 max-w-[16rem]" }, i)
|
|
684
685
|
) }),
|
|
@@ -777,14 +778,22 @@ function TurnFooter({ text, seconds, onRetry, showCopy = true, showDuration = tr
|
|
|
777
778
|
] })
|
|
778
779
|
] });
|
|
779
780
|
}
|
|
780
|
-
function AgentBubble(
|
|
781
|
+
function AgentBubble(props) {
|
|
782
|
+
const { turn, avatar } = props;
|
|
783
|
+
const body = /* @__PURE__ */ jsx6(AgentBody, { ...props, showUsage: props.showUsage ?? true, showCopy: props.showCopy ?? true, showDuration: props.showDuration ?? true });
|
|
784
|
+
return /* @__PURE__ */ jsx6(TurnContext.Provider, { value: turn, children: avatar ? /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-3", children: [
|
|
785
|
+
/* @__PURE__ */ jsx6("div", { className: "mt-0.5 shrink-0", children: avatar }),
|
|
786
|
+
body
|
|
787
|
+
] }) : body });
|
|
788
|
+
}
|
|
789
|
+
function AgentBody({ turn, live, onRetry, onOption, showUsage, showCopy, showDuration, renderDeliveries, renderTools, renderBefore, blocks, narration = true }) {
|
|
781
790
|
const { strings: t } = usePiloto();
|
|
782
791
|
const extracted = blocks ? extractBlocks(turn.text ?? "", blocks.langs, turn.cuts) : { text: turn.text ?? "", cuts: turn.cuts, blocks: [] };
|
|
783
792
|
const closed = !live && !turn.tools?.some((x) => x.status === "pending");
|
|
784
|
-
const narrated = splitNarration(extracted.text, extracted.cuts, closed);
|
|
793
|
+
const narrated = narration ? splitNarration(extracted.text, extracted.cuts, closed) : { steps: [], rest: extracted.text };
|
|
785
794
|
const visible = narrated.rest;
|
|
786
795
|
const options = onOption && !live && visible ? extractReplyOptions(visible) : null;
|
|
787
|
-
return /* @__PURE__ */
|
|
796
|
+
return /* @__PURE__ */ jsxs4("div", { className: "gc-msg-agent min-w-0 max-w-[90%]", children: [
|
|
788
797
|
turn.thought && /* @__PURE__ */ jsxs4("details", { className: "mb-3 text-xs text-text-secondary", children: [
|
|
789
798
|
/* @__PURE__ */ jsx6("summary", { className: "cursor-pointer select-none", children: t.thinking }),
|
|
790
799
|
/* @__PURE__ */ jsx6("p", { className: "mt-2 whitespace-pre-wrap border-l-2 border-border-secondary pl-3", children: turn.thought })
|
|
@@ -805,7 +814,7 @@ function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy
|
|
|
805
814
|
turn.decisions?.length ? /* @__PURE__ */ jsx6(DecisionLines, { decisions: turn.decisions }) : null,
|
|
806
815
|
showUsage && turn.usage && /* @__PURE__ */ jsx6(UsageStats, { ...turn.usage }),
|
|
807
816
|
turn.text && !live && /* @__PURE__ */ jsx6(TurnFooter, { text: turn.text, seconds: turn.seconds, onRetry, showCopy, showDuration })
|
|
808
|
-
] })
|
|
817
|
+
] });
|
|
809
818
|
}
|
|
810
819
|
|
|
811
820
|
// src/react/Status.tsx
|
|
@@ -1002,7 +1011,8 @@ function usePilot(transport, opts = {}) {
|
|
|
1002
1011
|
commit({ ...ref.current, notice: err instanceof Error && err.message ? err.message : null, effects: [] });
|
|
1003
1012
|
return runNow({ messages: [] }, true);
|
|
1004
1013
|
}
|
|
1005
|
-
|
|
1014
|
+
const failed = sendFailed(ref.current, err instanceof Error && err.message ? err.message : "No se pudo hablar con el agente.");
|
|
1015
|
+
commit(err instanceof PilotHttpError ? { ...failed, httpError: { status: err.status, ...err.code ? { code: err.code } : {}, message: err.message, ...err.headers ? { headers: err.headers } : {} } } : failed);
|
|
1006
1016
|
return;
|
|
1007
1017
|
}
|
|
1008
1018
|
if (c.signal.aborted) return;
|
|
@@ -1189,6 +1199,18 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1189
1199
|
useEffect5(() => {
|
|
1190
1200
|
if (isDev()) for (const w of plugins.warnings) warnOnce(w);
|
|
1191
1201
|
}, [plugins]);
|
|
1202
|
+
const ctxForComponents = useRef4(null);
|
|
1203
|
+
const boundComponents = useMemo3(() => {
|
|
1204
|
+
const entries = Object.entries(plugins.components);
|
|
1205
|
+
if (!entries.length) return void 0;
|
|
1206
|
+
return Object.fromEntries(
|
|
1207
|
+
entries.map(([tag, { plugin, value: C }]) => [
|
|
1208
|
+
tag,
|
|
1209
|
+
// Como toda vista de plugin: marcada con su nombre y, si es de la app, aislada de las utilidades.
|
|
1210
|
+
(p) => /* @__PURE__ */ jsx8("span", { ...pluginAttrs(plugin, plugins.ghostykitStyled), style: { display: "contents" }, children: /* @__PURE__ */ jsx8(C, { ...p, ctx: ctxForComponents.current }) })
|
|
1211
|
+
])
|
|
1212
|
+
);
|
|
1213
|
+
}, [plugins]);
|
|
1192
1214
|
const strings = useMemo3(() => ({ ...locale === "en" ? STRINGS_EN : STRINGS_ES, ...plugins.strings }), [locale, plugins]);
|
|
1193
1215
|
const config = useMemo3(
|
|
1194
1216
|
() => ({
|
|
@@ -1198,9 +1220,9 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1198
1220
|
formatUserText: plugins.formatUserText ? (x) => plugins.formatUserText(cleanUserText(x)) : cleanUserText,
|
|
1199
1221
|
theme,
|
|
1200
1222
|
skin: props.skin ?? "ghosty",
|
|
1201
|
-
markdown: plugins.markdown
|
|
1223
|
+
markdown: { ...plugins.markdown, ...boundComponents ? { components: boundComponents } : {} }
|
|
1202
1224
|
}),
|
|
1203
|
-
[strings, locale, plugins, theme, props.skin]
|
|
1225
|
+
[strings, locale, plugins, theme, props.skin, boundComponents]
|
|
1204
1226
|
);
|
|
1205
1227
|
const t = strings;
|
|
1206
1228
|
const [ownRefs, setOwnRefs] = useState8([]);
|
|
@@ -1235,12 +1257,16 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1235
1257
|
const send = useCallback2(
|
|
1236
1258
|
(text, attachments = []) => {
|
|
1237
1259
|
const current = refsRef.current;
|
|
1238
|
-
const
|
|
1260
|
+
const before = pluginsRef.current.beforeSend;
|
|
1261
|
+
const full = applyReferences(before && ctxRef.current ? before(text, ctxRef.current) : text, current);
|
|
1239
1262
|
if (current.length) setRefs([]);
|
|
1240
1263
|
void pilot.send(full, attachments, current.length ? { references: current } : {});
|
|
1241
1264
|
},
|
|
1242
1265
|
[pilot, setRefs]
|
|
1243
1266
|
);
|
|
1267
|
+
const pluginsRef = useRef4(plugins);
|
|
1268
|
+
pluginsRef.current = plugins;
|
|
1269
|
+
const ctxRef = useRef4(null);
|
|
1244
1270
|
const composerHandle = useRef4(null);
|
|
1245
1271
|
const focus = useCallback2(() => composerBox.current?.querySelector("textarea")?.focus({ preventScroll: true }), []);
|
|
1246
1272
|
const addReference = useCallback2(
|
|
@@ -1256,10 +1282,9 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1256
1282
|
const actions = { pilot, send, addReference, addAttachments, setDraft, focus };
|
|
1257
1283
|
useImperativeHandle(handle, () => actions, [pilot, send, addReference, addAttachments, setDraft, focus]);
|
|
1258
1284
|
const ctx = { ...actions, agentState: state.agentState, strings, plugins: plugins.list };
|
|
1259
|
-
|
|
1285
|
+
ctxForComponents.current = ctx;
|
|
1286
|
+
const mark = (plugin) => pluginAttrs(plugin, plugins.ghostykitStyled);
|
|
1260
1287
|
ctxRef.current = ctx;
|
|
1261
|
-
const pluginsRef = useRef4(plugins);
|
|
1262
|
-
pluginsRef.current = plugins;
|
|
1263
1288
|
const unattended = useRef4(/* @__PURE__ */ new Set());
|
|
1264
1289
|
useEffect5(
|
|
1265
1290
|
() => pilot.subscribe?.((event) => {
|
|
@@ -1315,6 +1340,17 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1315
1340
|
}),
|
|
1316
1341
|
[pilot.onTurn]
|
|
1317
1342
|
);
|
|
1343
|
+
useEffect5(() => {
|
|
1344
|
+
const err = state.httpError;
|
|
1345
|
+
if (!err) return;
|
|
1346
|
+
for (const h of pluginsRef.current.httpError) {
|
|
1347
|
+
try {
|
|
1348
|
+
h.value(err, ctxRef.current);
|
|
1349
|
+
} catch (e) {
|
|
1350
|
+
console.error(`[ghostykit] el plugin \xAB${h.plugin}\xBB fall\xF3 con onHttpError`, e);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}, [state.httpError]);
|
|
1318
1354
|
const [held, setHeld] = useState8({});
|
|
1319
1355
|
const heldRef = useRef4(held);
|
|
1320
1356
|
heldRef.current = held;
|
|
@@ -1428,12 +1464,20 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1428
1464
|
const toolRunning = busy && last?.role === "assistant" && (last.tools ?? []).some(isRunning);
|
|
1429
1465
|
const empty = turns.length === 0 && !busy;
|
|
1430
1466
|
const placeholder = props.placeholder ?? (props.agentName ? t.placeholder(props.agentName) : t.placeholderDefault);
|
|
1431
|
-
const { upload, transcribe, voiceNoteText } = plugins.attachments;
|
|
1467
|
+
const { upload, transcribe, voiceNoteText, accept, maxFiles, maxBytes } = plugins.attachments;
|
|
1432
1468
|
const showFiles = features.attachments ?? Boolean(upload);
|
|
1433
1469
|
const pointedRefs = refs.filter((r) => r.prompt);
|
|
1434
1470
|
const plainRefs = refs.filter((r) => !r.prompt);
|
|
1435
1471
|
const removeRef = (id) => setRefs(refsRef.current.filter((r) => r.id !== id));
|
|
1436
|
-
const
|
|
1472
|
+
const replacer = plugins.replacers.find((r) => {
|
|
1473
|
+
try {
|
|
1474
|
+
return r.value.when(ctx);
|
|
1475
|
+
} catch {
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
const Replacement = replacer?.value.view;
|
|
1480
|
+
const composer = Replacement ? /* @__PURE__ */ jsx8("div", { ...mark(replacer.plugin), children: /* @__PURE__ */ jsx8(Replacement, { ctx }) }) : /* @__PURE__ */ jsxs6("div", { ref: composerBox, children: [
|
|
1437
1481
|
/* @__PURE__ */ jsx8(ReferenceChips, { references: pointedRefs, onRemove: removeRef, onActivate: props.onReferenceClick }),
|
|
1438
1482
|
/* @__PURE__ */ jsx8(
|
|
1439
1483
|
Composer,
|
|
@@ -1453,7 +1497,11 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1453
1497
|
onActivateReference: props.onReferenceClick,
|
|
1454
1498
|
extras: plugins.composerActions.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.composerActions, ctx, styled: plugins.ghostykitStyled, inline: true }) : void 0,
|
|
1455
1499
|
extrasEnd: plugins.composerActionsEnd.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.composerActionsEnd, ctx, styled: plugins.ghostykitStyled, inline: true }) : void 0,
|
|
1456
|
-
handleRef: composerHandle
|
|
1500
|
+
handleRef: composerHandle,
|
|
1501
|
+
icons: props.icons,
|
|
1502
|
+
accept,
|
|
1503
|
+
...maxFiles ? { maxFiles } : {},
|
|
1504
|
+
...maxBytes ? { maxFileMb: maxBytes / (1024 * 1024) } : {}
|
|
1457
1505
|
},
|
|
1458
1506
|
draft.n
|
|
1459
1507
|
)
|
|
@@ -1464,8 +1512,12 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1464
1512
|
] }) : chat;
|
|
1465
1513
|
const aboveComposer = plugins.composerAbove.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.composerAbove, ctx, styled: plugins.ghostykitStyled }) : null;
|
|
1466
1514
|
const belowComposer = plugins.composerBelow.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.composerBelow, ctx, styled: plugins.ghostykitStyled }) : null;
|
|
1467
|
-
const
|
|
1468
|
-
const
|
|
1515
|
+
const headerStart = plugins.headerStart.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.headerStart, ctx, styled: plugins.ghostykitStyled, inline: true }) : null;
|
|
1516
|
+
const headerEnd = plugins.header.length ? /* @__PURE__ */ jsx8(Views, { items: plugins.header, ctx, styled: plugins.ghostykitStyled, inline: true }) : null;
|
|
1517
|
+
const headerSides = (headerStart || headerEnd) && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
1518
|
+
headerStart && /* @__PURE__ */ jsx8("div", { className: "piloto-header-start flex min-w-0 items-center gap-2", children: headerStart }),
|
|
1519
|
+
headerEnd && /* @__PURE__ */ jsx8("div", { className: "piloto-header-end ml-auto flex items-center gap-2", children: headerEnd })
|
|
1520
|
+
] });
|
|
1469
1521
|
const Empty = plugins.empty?.value;
|
|
1470
1522
|
const Waiting = plugins.waiting?.value;
|
|
1471
1523
|
const renderDeliveries = (turn, live) => {
|
|
@@ -1516,7 +1568,7 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1516
1568
|
// Un hilo vacío enseña lo mismo que la portada: la cara, el saludo, la caja y por dónde
|
|
1517
1569
|
// empezar. Si no cabe (panel bajo), se desplaza: `safe center` nunca empuja nada fuera.
|
|
1518
1570
|
/* @__PURE__ */ jsxs6("div", { className: "gs-scroll flex min-h-0 flex-1 flex-col items-center justify-center overflow-y-auto px-4 pb-[10vh] [justify-content:safe_center]", children: [
|
|
1519
|
-
|
|
1571
|
+
headerSides && /* @__PURE__ */ jsx8("div", { className: "piloto-header absolute inset-x-0 top-0 z-10 flex items-center gap-2 px-4 pt-3", children: headerSides }),
|
|
1520
1572
|
Empty ? /* @__PURE__ */ jsx8("div", { ...mark(plugins.empty.plugin), children: /* @__PURE__ */ jsx8(Empty, { ctx }) }) : /* @__PURE__ */ jsxs6("div", { className: "mb-6 flex flex-col items-center", children: [
|
|
1521
1573
|
props.avatar && /* @__PURE__ */ jsx8("div", { className: "mb-3", children: props.avatar }),
|
|
1522
1574
|
props.welcome && // Un saludo largo (una explicación del piloto) baja a letra de lectura.
|
|
@@ -1536,7 +1588,7 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1536
1588
|
] })
|
|
1537
1589
|
] })
|
|
1538
1590
|
) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
1539
|
-
|
|
1591
|
+
headerSides && /* @__PURE__ */ jsx8("div", { className: "piloto-header flex shrink-0 items-center gap-2 px-4 pt-3 sm:px-6", children: headerSides }),
|
|
1540
1592
|
/* @__PURE__ */ jsxs6(
|
|
1541
1593
|
"div",
|
|
1542
1594
|
{
|
|
@@ -1585,7 +1637,9 @@ var Piloto = forwardRef(function Piloto2(props, handle) {
|
|
|
1585
1637
|
renderDeliveries,
|
|
1586
1638
|
renderTools,
|
|
1587
1639
|
renderBefore,
|
|
1588
|
-
blocks
|
|
1640
|
+
blocks,
|
|
1641
|
+
narration: features.narration !== false,
|
|
1642
|
+
avatar: plugins.avatar ? /* @__PURE__ */ jsx8("span", { ...mark(plugins.avatar.plugin), children: /* @__PURE__ */ jsx8(plugins.avatar.value, { ctx, turn }) }) : void 0
|
|
1589
1643
|
}
|
|
1590
1644
|
),
|
|
1591
1645
|
turn.role === "assistant" && /* @__PURE__ */ jsx8(TurnNotes, { turn })
|
package/dist/react/plugins.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ComponentType, ReactNode } from "react";
|
|
|
2
2
|
import type { BaseEvent } from "@ag-ui/core";
|
|
3
3
|
import type { TextBlock } from "../core/narration";
|
|
4
4
|
import type { ToolVerbs } from "../core/steps";
|
|
5
|
-
import type { Attachment, CustomEntry, Reference, ToolEntry, Turn } from "../core/types";
|
|
5
|
+
import type { Attachment, CustomEntry, PilotState, Reference, ToolEntry, Turn } from "../core/types";
|
|
6
6
|
import type { UploadResult } from "./Composer";
|
|
7
7
|
import type { MarkdownOptions, PilotIcon } from "./context";
|
|
8
8
|
import type { PilotStrings } from "./strings";
|
|
@@ -52,7 +52,17 @@ export interface PluginMarkdown {
|
|
|
52
52
|
turn: Turn;
|
|
53
53
|
live: boolean;
|
|
54
54
|
}>>;
|
|
55
|
+
/**
|
|
56
|
+
* Cómo se pinta un elemento del markdown, por etiqueta (`a`, `code`, `img`…): p. ej. un enlace que
|
|
57
|
+
* es un CTA como botón. Recibe las props del elemento y `ctx`. Por etiqueta, gana el primer plugin.
|
|
58
|
+
*/
|
|
59
|
+
components?: Record<string, ComponentType<Record<string, unknown> & {
|
|
60
|
+
ctx: PluginCtx;
|
|
61
|
+
children?: ReactNode;
|
|
62
|
+
}>>;
|
|
55
63
|
}
|
|
64
|
+
/** Un rechazo HTTP del servidor antes del stream (403 con `x-needs-login`, 429…). */
|
|
65
|
+
export type PluginHttpError = NonNullable<PilotState["httpError"]>;
|
|
56
66
|
export interface PilotoPlugin {
|
|
57
67
|
/** Único: aparece en `data-plugin`, en los avisos y en `pilotDevtools()`. */
|
|
58
68
|
name: string;
|
|
@@ -115,6 +125,12 @@ export interface PilotoPlugin {
|
|
|
115
125
|
}>>;
|
|
116
126
|
/** El texto del usuario como se pinta (se encadena en orden). */
|
|
117
127
|
formatUserText?: (text: string) => string;
|
|
128
|
+
/** El texto que sale, antes de mandarlo (p. ej. anteponer lo señalado). Se encadena en orden. */
|
|
129
|
+
beforeSend?: (text: string, ctx: PluginCtx) => string;
|
|
130
|
+
/** El avatar del agente junto a cada respuesta. Gana el primer plugin. */
|
|
131
|
+
avatar?: PluginView<{
|
|
132
|
+
turn: Turn;
|
|
133
|
+
}>;
|
|
118
134
|
};
|
|
119
135
|
composer?: {
|
|
120
136
|
/** Botones en la barra del composer, entre la imagen y el clip («Mis archivos», «Señalar»). */
|
|
@@ -130,10 +146,27 @@ export interface PilotoPlugin {
|
|
|
130
146
|
upload?: (file: File) => Promise<UploadResult>;
|
|
131
147
|
transcribe?: (audio: Blob) => Promise<string>;
|
|
132
148
|
voiceNoteText?: (transcript: string) => string;
|
|
149
|
+
/** Qué se puede adjuntar (como el `accept` de un input): `"image/*"` = sólo fotos. */
|
|
150
|
+
accept?: string;
|
|
151
|
+
/** Cuántos archivos a la vez (default 8). */
|
|
152
|
+
maxFiles?: number;
|
|
153
|
+
/** Tamaño máximo por archivo, en bytes (default 25 MB). */
|
|
154
|
+
maxBytes?: number;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* En lugar de la caja, cuando `when(ctx)` es verdad (p. ej. la prueba se acabó: 403 con
|
|
158
|
+
* `x-needs-login` → «Crea tu cuenta»). Gana el primer plugin cuyo `when` es verdad.
|
|
159
|
+
*/
|
|
160
|
+
replace?: {
|
|
161
|
+
when: (ctx: PluginCtx) => boolean;
|
|
162
|
+
view: PluginView;
|
|
133
163
|
};
|
|
134
164
|
};
|
|
135
|
-
/**
|
|
136
|
-
|
|
165
|
+
/**
|
|
166
|
+
* La barra de arriba, sobre el hilo. Una vista sola va a la DERECHA (acciones); con
|
|
167
|
+
* `{ start, end }`, `start` va a la izquierda (avatar y nombre del agente) y `end` a la derecha.
|
|
168
|
+
*/
|
|
169
|
+
header?: PluginView | PluginHeader;
|
|
137
170
|
/** Junto al chat. */
|
|
138
171
|
sidePanel?: PluginView;
|
|
139
172
|
/** Arriba de los mensajes, dentro del hilo. */
|
|
@@ -150,11 +183,20 @@ export interface PilotoPlugin {
|
|
|
150
183
|
ctx: PluginCtx;
|
|
151
184
|
children: ReactNode;
|
|
152
185
|
}>;
|
|
186
|
+
/** El servidor rechazó un mensaje antes de contestar (`ctx.pilot.state.httpError` también lo guarda). */
|
|
187
|
+
onHttpError?: (error: PluginHttpError, ctx: PluginCtx) => void;
|
|
153
188
|
/** Este cliente mandó un turno. */
|
|
154
189
|
onTurnStart?: (ctx: PluginCtx) => void;
|
|
155
190
|
/** Terminó un turno de este cliente (abrir la última entrega, re-leer datos). */
|
|
156
191
|
onTurnDone?: (info: TurnDoneInfo, ctx: PluginCtx) => void;
|
|
157
192
|
}
|
|
193
|
+
/** Los dos lados de la barra de arriba. */
|
|
194
|
+
export interface PluginHeader {
|
|
195
|
+
/** A la izquierda: quién es (avatar, nombre). */
|
|
196
|
+
start?: PluginView;
|
|
197
|
+
/** A la derecha: acciones (nueva conversación, cerrar). */
|
|
198
|
+
end?: PluginView;
|
|
199
|
+
}
|
|
158
200
|
/** Ayuda de tipos: `definePlugin({ name: "crm", … })`. */
|
|
159
201
|
export declare function definePlugin<T extends PilotoPlugin>(plugin: T): T;
|
|
160
202
|
type Owned<T> = {
|
|
@@ -174,10 +216,18 @@ export interface ResolvedPlugins {
|
|
|
174
216
|
toolRenders: Record<string, Owned<NonNullable<NonNullable<PilotoPlugin["tools"]>["render"]>[string]>>;
|
|
175
217
|
customRenders: Record<string, Owned<NonNullable<NonNullable<PilotoPlugin["message"]>["custom"]>[string]>>;
|
|
176
218
|
blockRenders: Record<string, Owned<NonNullable<PluginMarkdown["blocks"]>[string]>>;
|
|
219
|
+
components: Record<string, Owned<NonNullable<PluginMarkdown["components"]>[string]>>;
|
|
220
|
+
avatar?: Owned<NonNullable<NonNullable<PilotoPlugin["message"]>["avatar"]>>;
|
|
221
|
+
beforeSend?: (text: string, ctx: PluginCtx) => string;
|
|
222
|
+
replacers: Owned<NonNullable<NonNullable<PilotoPlugin["composer"]>["replace"]>>[];
|
|
223
|
+
httpError: Owned<NonNullable<PilotoPlugin["onHttpError"]>>[];
|
|
177
224
|
attachments: NonNullable<NonNullable<PilotoPlugin["composer"]>["attachments"]>;
|
|
178
225
|
empty?: Owned<PluginView>;
|
|
179
226
|
waiting?: Owned<NonNullable<PilotoPlugin["waiting"]>>;
|
|
227
|
+
/** La barra de arriba, a la derecha (`header` o `header.end`). */
|
|
180
228
|
header: Owned<PluginView>[];
|
|
229
|
+
/** La barra de arriba, a la izquierda (`header.start`). */
|
|
230
|
+
headerStart: Owned<PluginView>[];
|
|
181
231
|
sidePanel: Owned<PluginView>[];
|
|
182
232
|
aboveThread: Owned<PluginView>[];
|
|
183
233
|
composerActions: Owned<PluginView>[];
|
package/dist/styles.css
CHANGED
|
@@ -119,18 +119,21 @@
|
|
|
119
119
|
.piloto .inset-0:not([data-piloto-isolate] *) {
|
|
120
120
|
inset: 0px;
|
|
121
121
|
}
|
|
122
|
+
.piloto .inset-x-0:not([data-piloto-isolate] *) {
|
|
123
|
+
inset-inline: 0px;
|
|
124
|
+
}
|
|
122
125
|
.piloto .-top-12:not([data-piloto-isolate] *) {
|
|
123
126
|
top: calc(var(--spacing) * -12);
|
|
124
127
|
}
|
|
128
|
+
.piloto .top-0:not([data-piloto-isolate] *) {
|
|
129
|
+
top: 0px;
|
|
130
|
+
}
|
|
125
131
|
.piloto .top-0\.5:not([data-piloto-isolate] *) {
|
|
126
132
|
top: calc(var(--spacing) * 0.5);
|
|
127
133
|
}
|
|
128
134
|
.piloto .top-2:not([data-piloto-isolate] *) {
|
|
129
135
|
top: calc(var(--spacing) * 2);
|
|
130
136
|
}
|
|
131
|
-
.piloto .top-3:not([data-piloto-isolate] *) {
|
|
132
|
-
top: calc(var(--spacing) * 3);
|
|
133
|
-
}
|
|
134
137
|
.piloto .top-4:not([data-piloto-isolate] *) {
|
|
135
138
|
top: calc(var(--spacing) * 4);
|
|
136
139
|
}
|
|
@@ -254,6 +257,9 @@
|
|
|
254
257
|
.piloto .ml-1:not([data-piloto-isolate] *) {
|
|
255
258
|
margin-left: var(--spacing);
|
|
256
259
|
}
|
|
260
|
+
.piloto .ml-auto:not([data-piloto-isolate] *) {
|
|
261
|
+
margin-left: auto;
|
|
262
|
+
}
|
|
257
263
|
.piloto .block:not([data-piloto-isolate] *) {
|
|
258
264
|
display: block;
|
|
259
265
|
}
|
|
@@ -653,6 +659,9 @@
|
|
|
653
659
|
.piloto .rounded-xl:not([data-piloto-isolate] *) {
|
|
654
660
|
border-radius: var(--radius-xl);
|
|
655
661
|
}
|
|
662
|
+
.piloto .rounded-br-\[var\(--piloto-radius-bubble-tail\,var\(--piloto-radius-bubble\)\)\]:not([data-piloto-isolate] *) {
|
|
663
|
+
border-bottom-right-radius: var(--piloto-radius-bubble-tail,var(--piloto-radius-bubble));
|
|
664
|
+
}
|
|
656
665
|
.piloto .border:not([data-piloto-isolate] *) {
|
|
657
666
|
border-style: var(--tw-border-style);
|
|
658
667
|
border-width: 1px;
|
|
@@ -1853,7 +1862,7 @@
|
|
|
1853
1862
|
`.gs-prosa`, la garra, los puntitos), (3) el blindaje contra el CSS de la app
|
|
1854
1863
|
anfitriona y (4) la pieza nueva: el pase de permiso.
|
|
1855
1864
|
|
|
1856
|
-
Pieles por `data-skin`: ghosty (default), easybits y
|
|
1865
|
+
Pieles por `data-skin`: ghosty (default), easybits, neutral y denik; cada una con su
|
|
1857
1866
|
oscuro (`data-theme="dark"`, o `auto` siguiendo al sistema).
|
|
1858
1867
|
───────────────────────────────────────────────────────────────────────────── */
|
|
1859
1868
|
|
|
@@ -1897,6 +1906,9 @@
|
|
|
1897
1906
|
|
|
1898
1907
|
/* Forma (los valores de gs). */
|
|
1899
1908
|
--piloto-radius-bubble: 12px;
|
|
1909
|
+
/* La esquina inferior derecha de TU burbuja (la «cola»). Si no se define, vale lo mismo que
|
|
1910
|
+
`--piloto-radius-bubble` (el componente usa ese respaldo). */
|
|
1911
|
+
/* --piloto-radius-bubble-tail: 12px; */
|
|
1900
1912
|
--piloto-radius-input: 24px;
|
|
1901
1913
|
--piloto-radius-card: 16px;
|
|
1902
1914
|
--piloto-radius-button: 12px;
|
|
@@ -2055,6 +2067,80 @@
|
|
|
2055
2067
|
--piloto-shadow-hero: 4px 4px 0 #000000;
|
|
2056
2068
|
}
|
|
2057
2069
|
|
|
2070
|
+
/* ── Deník: azul #5158F6, tinta #11151A, Satoshi; tu burbuja oscura con cola, la respuesta sin
|
|
2071
|
+
burbuja (el avatar a la izquierda va por `message.avatar`). La app anfitriona carga Satoshi. ── */
|
|
2072
|
+
.piloto[data-skin="denik"] {
|
|
2073
|
+
--piloto-rem: 16px;
|
|
2074
|
+
--piloto-font: "Satoshi", "Satoshi ", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
2075
|
+
/* La respuesta va en la misma Satoshi, no en serif. */
|
|
2076
|
+
--piloto-font-prose: "Satoshi", "Satoshi ", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
2077
|
+
--piloto-bg: #ffffff;
|
|
2078
|
+
--piloto-surface: #f8f8fa;
|
|
2079
|
+
--piloto-surface-2: #f2f2f2;
|
|
2080
|
+
--piloto-surface-hover: #fafafa;
|
|
2081
|
+
--piloto-text: #11151a;
|
|
2082
|
+
--piloto-text-2: #4b5563;
|
|
2083
|
+
--piloto-text-3: #8391a1;
|
|
2084
|
+
--piloto-text-disabled: #c4cad2;
|
|
2085
|
+
--piloto-text-inverse: #ffffff;
|
|
2086
|
+
--piloto-inverse: #11151a;
|
|
2087
|
+
--piloto-border: #f2f2f2;
|
|
2088
|
+
--piloto-border-2: #f2f2f2;
|
|
2089
|
+
--piloto-accent-100: #eeeffe;
|
|
2090
|
+
--piloto-accent-soft: #a8abfa;
|
|
2091
|
+
--piloto-accent: #5158f6;
|
|
2092
|
+
--piloto-accent-strong: #3f46e0;
|
|
2093
|
+
--piloto-on-accent: #ffffff;
|
|
2094
|
+
--piloto-success: #10b981;
|
|
2095
|
+
--piloto-danger: #ca5757;
|
|
2096
|
+
--piloto-focus: #5158f6;
|
|
2097
|
+
--piloto-user-bubble: #11151a;
|
|
2098
|
+
--piloto-user-text: #ffffff;
|
|
2099
|
+
--piloto-composer-bg: #ffffff;
|
|
2100
|
+
--piloto-radius-bubble: 24px;
|
|
2101
|
+
--piloto-radius-bubble-tail: 8px;
|
|
2102
|
+
--piloto-radius-input: 24px;
|
|
2103
|
+
--piloto-radius-card: 16px;
|
|
2104
|
+
--piloto-radius-button: 12px;
|
|
2105
|
+
--piloto-border-width: 1px;
|
|
2106
|
+
--piloto-shadow-input: 0 4px 24px rgb(0 0 0 / 0.06);
|
|
2107
|
+
--piloto-shadow-color: #11151a;
|
|
2108
|
+
--piloto-shadow-hero: 5px 5px 0 #11151a;
|
|
2109
|
+
}
|
|
2110
|
+
.piloto[data-skin="denik"][data-theme="dark"] {
|
|
2111
|
+
--piloto-bg: #11151a;
|
|
2112
|
+
--piloto-surface: #1a1f26;
|
|
2113
|
+
--piloto-surface-2: #242a33;
|
|
2114
|
+
--piloto-surface-hover: #161b21;
|
|
2115
|
+
--piloto-text: #f2f4f7;
|
|
2116
|
+
--piloto-text-2: #c2c8d0;
|
|
2117
|
+
--piloto-text-3: #8391a1;
|
|
2118
|
+
--piloto-text-disabled: #4b5563;
|
|
2119
|
+
--piloto-text-inverse: #11151a;
|
|
2120
|
+
--piloto-inverse: #f2f4f7;
|
|
2121
|
+
--piloto-border: #242a33;
|
|
2122
|
+
--piloto-border-2: #1a1f26;
|
|
2123
|
+
--piloto-accent-100: #1c2140;
|
|
2124
|
+
--piloto-accent-soft: #3d43b8;
|
|
2125
|
+
--piloto-accent: #6b71f8;
|
|
2126
|
+
--piloto-accent-strong: #8a8ffa;
|
|
2127
|
+
--piloto-user-bubble: #2a303a;
|
|
2128
|
+
--piloto-user-text: #f2f4f7;
|
|
2129
|
+
--piloto-composer-bg: #161b21;
|
|
2130
|
+
--piloto-shadow-input: 0 4px 24px rgb(0 0 0 / 0.4);
|
|
2131
|
+
--piloto-shadow-color: #5158f6;
|
|
2132
|
+
--piloto-shadow-hero: 5px 5px 0 #5158f6;
|
|
2133
|
+
}
|
|
2134
|
+
/* Las piezas de Deník que no son sólo color. */
|
|
2135
|
+
.piloto[data-skin="denik"] .piloto-header:not([data-piloto-isolate] *) { padding: 12px 16px; border-bottom: 1px solid var(--piloto-border); background: var(--piloto-bg); }
|
|
2136
|
+
.piloto[data-skin="denik"] .gc-msg-user:not([data-piloto-isolate] *) { max-width: 85%; padding: 10px 16px; font-size: 14px; line-height: 20px; }
|
|
2137
|
+
.piloto[data-skin="denik"] .gc-input:focus-within:not([data-piloto-isolate] *) { border-color: var(--piloto-accent); }
|
|
2138
|
+
/* El botón redondo: 36 px; apagado sigue azul (más tenue); al responder, círculo de tinta. */
|
|
2139
|
+
.piloto[data-skin="denik"] .gc-send:not([data-piloto-isolate] *),
|
|
2140
|
+
.piloto[data-skin="denik"] .gc-send-voice:not([data-piloto-isolate] *) { width: 36px; height: 36px; }
|
|
2141
|
+
.piloto[data-skin="denik"] .gc-send[data-state="send"]:disabled:not([data-piloto-isolate] *) { background: var(--piloto-accent); color: var(--piloto-on-accent); opacity: 0.4; }
|
|
2142
|
+
.piloto[data-skin="denik"] .gc-send[data-state="stop"]:not([data-piloto-isolate] *) { background: var(--piloto-inverse); color: var(--piloto-text-inverse); }
|
|
2143
|
+
|
|
2058
2144
|
@media (prefers-color-scheme: dark) {
|
|
2059
2145
|
.piloto[data-theme="auto"] {
|
|
2060
2146
|
color-scheme: dark;
|
|
@@ -2123,6 +2209,30 @@
|
|
|
2123
2209
|
--piloto-shadow-color: #000000;
|
|
2124
2210
|
--piloto-shadow-hero: 4px 4px 0 #000000;
|
|
2125
2211
|
}
|
|
2212
|
+
.piloto[data-skin="denik"][data-theme="auto"] {
|
|
2213
|
+
--piloto-bg: #11151a;
|
|
2214
|
+
--piloto-surface: #1a1f26;
|
|
2215
|
+
--piloto-surface-2: #242a33;
|
|
2216
|
+
--piloto-surface-hover: #161b21;
|
|
2217
|
+
--piloto-text: #f2f4f7;
|
|
2218
|
+
--piloto-text-2: #c2c8d0;
|
|
2219
|
+
--piloto-text-3: #8391a1;
|
|
2220
|
+
--piloto-text-disabled: #4b5563;
|
|
2221
|
+
--piloto-text-inverse: #11151a;
|
|
2222
|
+
--piloto-inverse: #f2f4f7;
|
|
2223
|
+
--piloto-border: #242a33;
|
|
2224
|
+
--piloto-border-2: #1a1f26;
|
|
2225
|
+
--piloto-accent-100: #1c2140;
|
|
2226
|
+
--piloto-accent-soft: #3d43b8;
|
|
2227
|
+
--piloto-accent: #6b71f8;
|
|
2228
|
+
--piloto-accent-strong: #8a8ffa;
|
|
2229
|
+
--piloto-user-bubble: #2a303a;
|
|
2230
|
+
--piloto-user-text: #f2f4f7;
|
|
2231
|
+
--piloto-composer-bg: #161b21;
|
|
2232
|
+
--piloto-shadow-input: 0 4px 24px rgb(0 0 0 / 0.4);
|
|
2233
|
+
--piloto-shadow-color: #5158f6;
|
|
2234
|
+
--piloto-shadow-hero: 5px 5px 0 #5158f6;
|
|
2235
|
+
}
|
|
2126
2236
|
}
|
|
2127
2237
|
|
|
2128
2238
|
/* ── Reset con alcance y BLINDAJE contra el CSS de la app anfitriona ──────────
|
|
@@ -13,7 +13,9 @@ export declare class PilotHttpError extends Error {
|
|
|
13
13
|
code?: string;
|
|
14
14
|
/** Segundos de `Retry-After` (429/503). */
|
|
15
15
|
retryAfter?: number;
|
|
16
|
-
|
|
16
|
+
/** Las cabeceras de la respuesta (minúsculas), p. ej. `x-needs-login`. */
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
constructor(message: string, status: number, code?: string, retryAfter?: number, headers?: Record<string, string>);
|
|
17
19
|
}
|
|
18
20
|
export interface AgUiTransportOptions {
|
|
19
21
|
/** El endpoint del agente: `POST <url>`. */
|
package/dist/transport/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghostykit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Ghosty Autopilot
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Ghosty Autopilot — the autopilot chat for your React app, on AG-UI: works with LangGraph, Mastra, CrewAI or any AG-UI agent. Streaming text, agent steps, human approval cards (interrupt/resume), attachments and voice.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|
|
7
7
|
"types": "./dist/core/index.d.ts",
|