ghostykit 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +173 -2
- package/dist/chunks/chunk-6U545WJG.js +36 -0
- package/dist/chunks/chunk-D6JXJWYM.js +373 -0
- package/dist/chunks/chunk-O6FCE4CS.js +670 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +74 -0
- package/dist/core/narration.d.ts +17 -0
- package/dist/core/options.d.ts +14 -0
- package/dist/core/reconcile.d.ts +7 -0
- package/dist/core/reducer.d.ts +54 -0
- package/dist/core/steps.d.ts +30 -0
- package/dist/core/text.d.ts +15 -0
- package/dist/core/types.d.ts +236 -0
- package/dist/fonts.css +3 -0
- package/dist/pointer/index.d.ts +9 -0
- package/dist/pointer/index.js +27 -0
- package/dist/react/Bubble.d.ts +41 -0
- package/dist/react/Chips.d.ts +13 -0
- package/dist/react/Composer.d.ts +45 -0
- package/dist/react/ConfirmCard.d.ts +17 -0
- package/dist/react/InlineMarkdown.d.ts +5 -0
- package/dist/react/Markdown.d.ts +12 -0
- package/dist/react/Piloto.d.ts +92 -0
- package/dist/react/Status.d.ts +19 -0
- package/dist/react/Steps.d.ts +22 -0
- package/dist/react/TextAtPace.d.ts +29 -0
- package/dist/react/context.d.ts +34 -0
- package/dist/react/index.d.ts +15 -0
- package/dist/react/index.js +2799 -0
- package/dist/react/strings.d.ts +89 -0
- package/dist/react/usePilot.d.ts +67 -0
- package/dist/styles.css +2364 -0
- package/dist/transport/index.d.ts +32 -0
- package/dist/transport/index.js +20 -0
- package/dist/transport/postStream.d.ts +44 -0
- package/dist/transport/types.d.ts +42 -0
- package/package.json +91 -4
|
@@ -0,0 +1,2799 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RESTART_NOTICE,
|
|
3
|
+
RESUME_TEXT,
|
|
4
|
+
TOOL_VERBS_EN,
|
|
5
|
+
TOOL_VERBS_ES,
|
|
6
|
+
appendAgentMessages,
|
|
7
|
+
applyHistory,
|
|
8
|
+
beginSend,
|
|
9
|
+
cleanReplyOption,
|
|
10
|
+
createPilotState,
|
|
11
|
+
dropLastExchange,
|
|
12
|
+
expiryMs,
|
|
13
|
+
extractReplyOptions,
|
|
14
|
+
groupSteps,
|
|
15
|
+
isNarration,
|
|
16
|
+
isRunning,
|
|
17
|
+
lastUserTurn,
|
|
18
|
+
narrationToMarkdown,
|
|
19
|
+
reconcileTurns,
|
|
20
|
+
reconcileWith,
|
|
21
|
+
reducePilotEvent,
|
|
22
|
+
resolvePermission,
|
|
23
|
+
resumeMode,
|
|
24
|
+
sendAccepted,
|
|
25
|
+
sendFailed,
|
|
26
|
+
sendRejectedBusy,
|
|
27
|
+
splitNarration,
|
|
28
|
+
stepLabel,
|
|
29
|
+
stopTurn,
|
|
30
|
+
summarizeTools,
|
|
31
|
+
toolName
|
|
32
|
+
} from "../chunks/chunk-O6FCE4CS.js";
|
|
33
|
+
import {
|
|
34
|
+
PilotHttpError,
|
|
35
|
+
createPostStreamTransport,
|
|
36
|
+
createSseTransport,
|
|
37
|
+
readSse
|
|
38
|
+
} from "../chunks/chunk-D6JXJWYM.js";
|
|
39
|
+
import {
|
|
40
|
+
applyReferences,
|
|
41
|
+
cleanUserText,
|
|
42
|
+
formatDuration,
|
|
43
|
+
systemNoteOf
|
|
44
|
+
} from "../chunks/chunk-6U545WJG.js";
|
|
45
|
+
|
|
46
|
+
// ../../node_modules/lucide-react/dist/esm/createLucideIcon.mjs
|
|
47
|
+
import { forwardRef as forwardRef2, createElement as createElement3 } from "react";
|
|
48
|
+
|
|
49
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs
|
|
50
|
+
var toKebabCase = (string) => string?.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
51
|
+
|
|
52
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/utils/toLucideIconData.mjs
|
|
53
|
+
function toLucideIconData(iconName, iconNode, aliases = []) {
|
|
54
|
+
if (iconNode == null) {
|
|
55
|
+
throw new Error("[lucide]: iconNode is required when icon name is used");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
name: toKebabCase(iconName),
|
|
59
|
+
size: 24,
|
|
60
|
+
node: iconNode,
|
|
61
|
+
...aliases.length > 0 ? { aliases } : {}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs
|
|
66
|
+
var toCamelCase = (string) => {
|
|
67
|
+
let out = "";
|
|
68
|
+
let upperNext = false;
|
|
69
|
+
for (const ch of string) {
|
|
70
|
+
if (ch === "-" || ch === "_" || ch <= " ") {
|
|
71
|
+
upperNext = out.length > 0;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (out.length === 0) {
|
|
75
|
+
out += ch.toLowerCase();
|
|
76
|
+
} else {
|
|
77
|
+
out += upperNext ? ch.toUpperCase() : ch;
|
|
78
|
+
}
|
|
79
|
+
upperNext = false;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs
|
|
85
|
+
var toPascalCase = (string) => {
|
|
86
|
+
const camelCase = toCamelCase(string);
|
|
87
|
+
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ../../node_modules/lucide-react/dist/esm/Icon.mjs
|
|
91
|
+
import { forwardRef, createElement as createElement2 } from "react";
|
|
92
|
+
|
|
93
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs
|
|
94
|
+
var mergeClasses = (...classes) => classes.filter((className, index, array) => {
|
|
95
|
+
return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
|
|
96
|
+
}).join(" ").trim();
|
|
97
|
+
|
|
98
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/build/defaultAttributes.mjs
|
|
99
|
+
var defaultAttributes = {
|
|
100
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
101
|
+
width: 24,
|
|
102
|
+
height: 24,
|
|
103
|
+
viewBox: "0 0 24 24",
|
|
104
|
+
fill: "none",
|
|
105
|
+
stroke: "currentColor",
|
|
106
|
+
"stroke-width": 2,
|
|
107
|
+
"stroke-linecap": "round",
|
|
108
|
+
"stroke-linejoin": "round"
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/build/buildLucideIconNode.mjs
|
|
112
|
+
function isDefined(value) {
|
|
113
|
+
return value !== null && value !== void 0;
|
|
114
|
+
}
|
|
115
|
+
function buildLucideIconNode(icon, params = {}) {
|
|
116
|
+
const attributeNames = params.attributeNames ?? {};
|
|
117
|
+
const getAttributeName = (attributeName) => attributeNames[attributeName] ?? attributeName;
|
|
118
|
+
const viewBoxWidth = icon.size ?? icon.width ?? defaultAttributes["width"];
|
|
119
|
+
const viewBoxHeight = icon.size ?? icon.height ?? defaultAttributes["height"];
|
|
120
|
+
const aliasClassNames = icon.aliases?.filter((alias) => typeof alias === "string" && alias.trim() !== "").map((alias) => `lucide-${alias}`) ?? [];
|
|
121
|
+
const iconClassNames = [...icon.name ? [`lucide-${icon.name}`] : [], ...aliasClassNames];
|
|
122
|
+
const classNamesFromClassName = params.className?.split(" ").filter(Boolean) ?? [];
|
|
123
|
+
const className = params.includeDefaultClasses === false ? mergeClasses(...classNamesFromClassName) : mergeClasses("lucide", ...iconClassNames, ...classNamesFromClassName);
|
|
124
|
+
const calculatedStrokeWidth = params.absoluteStrokeWidth ? Number(params.strokeWidth ?? defaultAttributes["stroke-width"]) * Number(icon.size ?? icon.width ?? defaultAttributes["width"]) / Number(params.size ?? params.width ?? defaultAttributes["width"]) : params.strokeWidth ?? defaultAttributes["stroke-width"];
|
|
125
|
+
const attributes = {
|
|
126
|
+
...Object.entries(defaultAttributes).reduce((attrs, [attrName, value]) => {
|
|
127
|
+
attrs[getAttributeName(attrName)] = value;
|
|
128
|
+
return attrs;
|
|
129
|
+
}, {}),
|
|
130
|
+
..."color" in params && params.color && {
|
|
131
|
+
[getAttributeName("stroke")]: params.color
|
|
132
|
+
},
|
|
133
|
+
..."size" in params && isDefined(params.size) && {
|
|
134
|
+
[getAttributeName("width")]: params.size,
|
|
135
|
+
[getAttributeName("height")]: params.size
|
|
136
|
+
},
|
|
137
|
+
..."width" in params && isDefined(params.width) && {
|
|
138
|
+
[getAttributeName("width")]: params.width
|
|
139
|
+
},
|
|
140
|
+
..."height" in params && isDefined(params.height) && {
|
|
141
|
+
[getAttributeName("height")]: params.height
|
|
142
|
+
},
|
|
143
|
+
[getAttributeName("stroke-width")]: calculatedStrokeWidth,
|
|
144
|
+
...className && {
|
|
145
|
+
[getAttributeName("class")]: className
|
|
146
|
+
},
|
|
147
|
+
[getAttributeName("viewBox")]: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
|
|
148
|
+
...params.hasA11yProp === false ? {
|
|
149
|
+
[getAttributeName("aria-hidden")]: "true"
|
|
150
|
+
} : {},
|
|
151
|
+
..."attributes" in params && params.attributes
|
|
152
|
+
};
|
|
153
|
+
return [
|
|
154
|
+
"svg",
|
|
155
|
+
attributes,
|
|
156
|
+
icon.node.map((child) => {
|
|
157
|
+
const [name, attrs, children] = child;
|
|
158
|
+
const nextAttrs = params.nonScalingStroke ? { [getAttributeName("vector-effect")]: "non-scaling-stroke", ...attrs } : attrs;
|
|
159
|
+
return children ? [name, nextAttrs, children] : [name, nextAttrs];
|
|
160
|
+
})
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/build/buildLucideIconForReact.mjs
|
|
165
|
+
function buildLucideIconForReact(icon, params = {}) {
|
|
166
|
+
return buildLucideIconNode(icon, {
|
|
167
|
+
...params,
|
|
168
|
+
attributeNames: {
|
|
169
|
+
...params.attributeNames,
|
|
170
|
+
class: "className",
|
|
171
|
+
"stroke-width": "strokeWidth",
|
|
172
|
+
"stroke-linecap": "strokeLinecap",
|
|
173
|
+
"stroke-linejoin": "strokeLinejoin",
|
|
174
|
+
"vector-effect": "vectorEffect"
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ../../node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs
|
|
180
|
+
var hasA11yProp = (props) => {
|
|
181
|
+
for (const prop in props) {
|
|
182
|
+
if (prop.startsWith("aria-") || prop === "role" || prop === "title") {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// ../../node_modules/lucide-react/dist/esm/context.mjs
|
|
190
|
+
import { createContext, useContext, useMemo, createElement } from "react";
|
|
191
|
+
var LucideContext = createContext({});
|
|
192
|
+
var useLucideContext = () => useContext(LucideContext);
|
|
193
|
+
|
|
194
|
+
// ../../node_modules/lucide-react/dist/esm/Icon.mjs
|
|
195
|
+
var Icon = forwardRef(
|
|
196
|
+
({
|
|
197
|
+
color,
|
|
198
|
+
size,
|
|
199
|
+
width,
|
|
200
|
+
height,
|
|
201
|
+
strokeWidth,
|
|
202
|
+
absoluteStrokeWidth,
|
|
203
|
+
nonScalingStroke,
|
|
204
|
+
className = "",
|
|
205
|
+
children,
|
|
206
|
+
iconNode = [],
|
|
207
|
+
icon = {
|
|
208
|
+
node: iconNode,
|
|
209
|
+
aliases: [],
|
|
210
|
+
size: 24
|
|
211
|
+
},
|
|
212
|
+
...rest
|
|
213
|
+
}, ref) => {
|
|
214
|
+
const {
|
|
215
|
+
size: contextSize = 24,
|
|
216
|
+
strokeWidth: contextStrokeWidth = 2,
|
|
217
|
+
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
|
|
218
|
+
nonScalingStroke: contextNonScalingStroke = false,
|
|
219
|
+
color: contextColor = "currentColor",
|
|
220
|
+
className: contextClass = ""
|
|
221
|
+
} = useLucideContext() ?? {};
|
|
222
|
+
const hasAccessibleProp = Boolean(children) || hasA11yProp(rest);
|
|
223
|
+
const [name, svgAttributes, builtIconNode = []] = buildLucideIconForReact(icon, {
|
|
224
|
+
color: color ?? contextColor,
|
|
225
|
+
width: width ?? size ?? contextSize,
|
|
226
|
+
height: height ?? size ?? contextSize,
|
|
227
|
+
strokeWidth: strokeWidth ?? contextStrokeWidth,
|
|
228
|
+
absoluteStrokeWidth: absoluteStrokeWidth ?? contextAbsoluteStrokeWidth,
|
|
229
|
+
nonScalingStroke: nonScalingStroke ?? contextNonScalingStroke,
|
|
230
|
+
className: mergeClasses(contextClass, className),
|
|
231
|
+
hasA11yProp: hasAccessibleProp,
|
|
232
|
+
attributes: rest
|
|
233
|
+
});
|
|
234
|
+
return createElement2(
|
|
235
|
+
name,
|
|
236
|
+
{
|
|
237
|
+
ref,
|
|
238
|
+
...svgAttributes
|
|
239
|
+
},
|
|
240
|
+
[
|
|
241
|
+
...builtIconNode.map(([tag, attrs]) => createElement2(tag, attrs)),
|
|
242
|
+
...Array.isArray(children) ? children : [children]
|
|
243
|
+
]
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// ../../node_modules/lucide-react/dist/esm/createLucideIcon.mjs
|
|
249
|
+
function createLucideIcon(iconDataOrName, iconNode = [], aliases = []) {
|
|
250
|
+
const iconData = typeof iconDataOrName === "string" ? toLucideIconData(iconDataOrName, iconNode, aliases) : iconDataOrName;
|
|
251
|
+
const Component = forwardRef2(
|
|
252
|
+
({ className, ...props }, ref) => createElement3(Icon, {
|
|
253
|
+
ref,
|
|
254
|
+
icon: iconData,
|
|
255
|
+
className,
|
|
256
|
+
...props
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
if (iconData.name) {
|
|
260
|
+
Component.displayName = toPascalCase(iconData.name);
|
|
261
|
+
}
|
|
262
|
+
return Component;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ../../node_modules/lucide-react/dist/esm/icons/app-window.mjs
|
|
266
|
+
var __iconData = {
|
|
267
|
+
name: "app-window",
|
|
268
|
+
size: 24,
|
|
269
|
+
node: [
|
|
270
|
+
["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2", key: "izxlao" }],
|
|
271
|
+
["path", { d: "M10 4v4", key: "pp8u80" }],
|
|
272
|
+
["path", { d: "M2 8h20", key: "d11cs7" }],
|
|
273
|
+
["path", { d: "M6 4v4", key: "1svtjw" }]
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
__iconData.node;
|
|
277
|
+
var AppWindow = createLucideIcon(__iconData);
|
|
278
|
+
|
|
279
|
+
// ../../node_modules/lucide-react/dist/esm/icons/arrow-down.mjs
|
|
280
|
+
var __iconData2 = {
|
|
281
|
+
name: "arrow-down",
|
|
282
|
+
size: 24,
|
|
283
|
+
node: [
|
|
284
|
+
["path", { d: "M12 5v14", key: "s699le" }],
|
|
285
|
+
["path", { d: "m19 12-7 7-7-7", key: "1idqje" }]
|
|
286
|
+
]
|
|
287
|
+
};
|
|
288
|
+
__iconData2.node;
|
|
289
|
+
var ArrowDown = createLucideIcon(__iconData2);
|
|
290
|
+
|
|
291
|
+
// ../../node_modules/lucide-react/dist/esm/icons/arrow-up-right.mjs
|
|
292
|
+
var __iconData3 = {
|
|
293
|
+
name: "arrow-up-right",
|
|
294
|
+
size: 24,
|
|
295
|
+
node: [
|
|
296
|
+
["path", { d: "M7 7h10v10", key: "1tivn9" }],
|
|
297
|
+
["path", { d: "M7 17 17 7", key: "1vkiza" }]
|
|
298
|
+
]
|
|
299
|
+
};
|
|
300
|
+
__iconData3.node;
|
|
301
|
+
var ArrowUpRight = createLucideIcon(__iconData3);
|
|
302
|
+
|
|
303
|
+
// ../../node_modules/lucide-react/dist/esm/icons/arrow-up.mjs
|
|
304
|
+
var __iconData4 = {
|
|
305
|
+
name: "arrow-up",
|
|
306
|
+
size: 24,
|
|
307
|
+
node: [
|
|
308
|
+
["path", { d: "m5 12 7-7 7 7", key: "hav0vg" }],
|
|
309
|
+
["path", { d: "M12 19V5", key: "x0mq9r" }]
|
|
310
|
+
]
|
|
311
|
+
};
|
|
312
|
+
__iconData4.node;
|
|
313
|
+
var ArrowUp = createLucideIcon(__iconData4);
|
|
314
|
+
|
|
315
|
+
// ../../node_modules/lucide-react/dist/esm/icons/at-sign.mjs
|
|
316
|
+
var __iconData5 = {
|
|
317
|
+
name: "at-sign",
|
|
318
|
+
size: 24,
|
|
319
|
+
node: [
|
|
320
|
+
["circle", { cx: "12", cy: "12", r: "4", key: "4exip2" }],
|
|
321
|
+
["path", { d: "M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8", key: "7n84p3" }]
|
|
322
|
+
]
|
|
323
|
+
};
|
|
324
|
+
__iconData5.node;
|
|
325
|
+
var AtSign = createLucideIcon(__iconData5);
|
|
326
|
+
|
|
327
|
+
// ../../node_modules/lucide-react/dist/esm/icons/book-open.mjs
|
|
328
|
+
var __iconData6 = {
|
|
329
|
+
name: "book-open",
|
|
330
|
+
size: 24,
|
|
331
|
+
node: [
|
|
332
|
+
["path", { d: "M12 5v16", key: "1f6ucr" }],
|
|
333
|
+
[
|
|
334
|
+
"path",
|
|
335
|
+
{
|
|
336
|
+
d: "M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z",
|
|
337
|
+
key: "1fyvmf"
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
]
|
|
341
|
+
};
|
|
342
|
+
__iconData6.node;
|
|
343
|
+
var BookOpen = createLucideIcon(__iconData6);
|
|
344
|
+
|
|
345
|
+
// ../../node_modules/lucide-react/dist/esm/icons/check.mjs
|
|
346
|
+
var __iconData7 = {
|
|
347
|
+
name: "check",
|
|
348
|
+
size: 24,
|
|
349
|
+
node: [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]]
|
|
350
|
+
};
|
|
351
|
+
__iconData7.node;
|
|
352
|
+
var Check = createLucideIcon(__iconData7);
|
|
353
|
+
|
|
354
|
+
// ../../node_modules/lucide-react/dist/esm/icons/chevron-down.mjs
|
|
355
|
+
var __iconData8 = {
|
|
356
|
+
name: "chevron-down",
|
|
357
|
+
size: 24,
|
|
358
|
+
node: [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]]
|
|
359
|
+
};
|
|
360
|
+
__iconData8.node;
|
|
361
|
+
var ChevronDown = createLucideIcon(__iconData8);
|
|
362
|
+
|
|
363
|
+
// ../../node_modules/lucide-react/dist/esm/icons/chevron-right.mjs
|
|
364
|
+
var __iconData9 = {
|
|
365
|
+
name: "chevron-right",
|
|
366
|
+
size: 24,
|
|
367
|
+
node: [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]]
|
|
368
|
+
};
|
|
369
|
+
__iconData9.node;
|
|
370
|
+
var ChevronRight = createLucideIcon(__iconData9);
|
|
371
|
+
|
|
372
|
+
// ../../node_modules/lucide-react/dist/esm/icons/circle-check.mjs
|
|
373
|
+
var __iconData10 = {
|
|
374
|
+
name: "circle-check",
|
|
375
|
+
size: 24,
|
|
376
|
+
node: [
|
|
377
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
378
|
+
["path", { d: "m16 9-5.5 5.5L8 12", key: "xofnsj" }]
|
|
379
|
+
],
|
|
380
|
+
aliases: ["check-circle-2"]
|
|
381
|
+
};
|
|
382
|
+
__iconData10.node;
|
|
383
|
+
var CircleCheck = createLucideIcon(__iconData10);
|
|
384
|
+
|
|
385
|
+
// ../../node_modules/lucide-react/dist/esm/icons/circle-slash.mjs
|
|
386
|
+
var __iconData11 = {
|
|
387
|
+
name: "circle-slash",
|
|
388
|
+
size: 24,
|
|
389
|
+
node: [
|
|
390
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
391
|
+
["line", { x1: "9", x2: "15", y1: "15", y2: "9", key: "1dfufj" }]
|
|
392
|
+
]
|
|
393
|
+
};
|
|
394
|
+
__iconData11.node;
|
|
395
|
+
var CircleSlash = createLucideIcon(__iconData11);
|
|
396
|
+
|
|
397
|
+
// ../../node_modules/lucide-react/dist/esm/icons/circle-x.mjs
|
|
398
|
+
var __iconData12 = {
|
|
399
|
+
name: "circle-x",
|
|
400
|
+
size: 24,
|
|
401
|
+
node: [
|
|
402
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
403
|
+
["path", { d: "m15 9-6 6", key: "1uzhvr" }],
|
|
404
|
+
["path", { d: "m9 9 6 6", key: "z0biqf" }]
|
|
405
|
+
],
|
|
406
|
+
aliases: ["x-circle"]
|
|
407
|
+
};
|
|
408
|
+
__iconData12.node;
|
|
409
|
+
var CircleX = createLucideIcon(__iconData12);
|
|
410
|
+
|
|
411
|
+
// ../../node_modules/lucide-react/dist/esm/icons/clock.mjs
|
|
412
|
+
var __iconData13 = {
|
|
413
|
+
name: "clock",
|
|
414
|
+
size: 24,
|
|
415
|
+
node: [
|
|
416
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
417
|
+
["path", { d: "M12 6v6l4 2", key: "mmk7yg" }]
|
|
418
|
+
]
|
|
419
|
+
};
|
|
420
|
+
__iconData13.node;
|
|
421
|
+
var Clock = createLucideIcon(__iconData13);
|
|
422
|
+
|
|
423
|
+
// ../../node_modules/lucide-react/dist/esm/icons/coins.mjs
|
|
424
|
+
var __iconData14 = {
|
|
425
|
+
name: "coins",
|
|
426
|
+
size: 24,
|
|
427
|
+
node: [
|
|
428
|
+
["path", { d: "M13.744 17.736a6 6 0 1 1-7.48-7.48", key: "bq4yh3" }],
|
|
429
|
+
["path", { d: "M15 6h1v4", key: "11y1tn" }],
|
|
430
|
+
["path", { d: "m6.134 14.768.866-.5 2 3.464", key: "17snzx" }],
|
|
431
|
+
["circle", { cx: "16", cy: "8", r: "6", key: "14bfc9" }]
|
|
432
|
+
]
|
|
433
|
+
};
|
|
434
|
+
__iconData14.node;
|
|
435
|
+
var Coins = createLucideIcon(__iconData14);
|
|
436
|
+
|
|
437
|
+
// ../../node_modules/lucide-react/dist/esm/icons/copy.mjs
|
|
438
|
+
var __iconData15 = {
|
|
439
|
+
name: "copy",
|
|
440
|
+
size: 24,
|
|
441
|
+
node: [
|
|
442
|
+
["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
|
|
443
|
+
["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
|
|
444
|
+
]
|
|
445
|
+
};
|
|
446
|
+
__iconData15.node;
|
|
447
|
+
var Copy = createLucideIcon(__iconData15);
|
|
448
|
+
|
|
449
|
+
// ../../node_modules/lucide-react/dist/esm/icons/cpu.mjs
|
|
450
|
+
var __iconData16 = {
|
|
451
|
+
name: "cpu",
|
|
452
|
+
size: 24,
|
|
453
|
+
node: [
|
|
454
|
+
["path", { d: "M12 20v2", key: "1lh1kg" }],
|
|
455
|
+
["path", { d: "M12 2v2", key: "tus03m" }],
|
|
456
|
+
["path", { d: "M17 20v2", key: "1rnc9c" }],
|
|
457
|
+
["path", { d: "M17 2v2", key: "11trls" }],
|
|
458
|
+
["path", { d: "M2 12h2", key: "1t8f8n" }],
|
|
459
|
+
["path", { d: "M2 17h2", key: "7oei6x" }],
|
|
460
|
+
["path", { d: "M2 7h2", key: "asdhe0" }],
|
|
461
|
+
["path", { d: "M20 12h2", key: "1q8mjw" }],
|
|
462
|
+
["path", { d: "M20 17h2", key: "1fpfkl" }],
|
|
463
|
+
["path", { d: "M20 7h2", key: "1o8tra" }],
|
|
464
|
+
["path", { d: "M7 20v2", key: "4gnj0m" }],
|
|
465
|
+
["path", { d: "M7 2v2", key: "1i4yhu" }],
|
|
466
|
+
["rect", { x: "4", y: "4", width: "16", height: "16", rx: "2", key: "1vbyd7" }],
|
|
467
|
+
["rect", { x: "8", y: "8", width: "8", height: "8", rx: "1", key: "z9xiuo" }]
|
|
468
|
+
]
|
|
469
|
+
};
|
|
470
|
+
__iconData16.node;
|
|
471
|
+
var Cpu = createLucideIcon(__iconData16);
|
|
472
|
+
|
|
473
|
+
// ../../node_modules/lucide-react/dist/esm/icons/external-link.mjs
|
|
474
|
+
var __iconData17 = {
|
|
475
|
+
name: "external-link",
|
|
476
|
+
size: 24,
|
|
477
|
+
node: [
|
|
478
|
+
["path", { d: "M15 3h6v6", key: "1q9fwt" }],
|
|
479
|
+
["path", { d: "M10 14 21 3", key: "gplh6r" }],
|
|
480
|
+
["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }]
|
|
481
|
+
]
|
|
482
|
+
};
|
|
483
|
+
__iconData17.node;
|
|
484
|
+
var ExternalLink = createLucideIcon(__iconData17);
|
|
485
|
+
|
|
486
|
+
// ../../node_modules/lucide-react/dist/esm/icons/file-pen-line.mjs
|
|
487
|
+
var __iconData18 = {
|
|
488
|
+
name: "file-pen-line",
|
|
489
|
+
size: 24,
|
|
490
|
+
node: [
|
|
491
|
+
[
|
|
492
|
+
"path",
|
|
493
|
+
{
|
|
494
|
+
d: "M14.364 13.634a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506l4.013-4.009a1 1 0 0 0-3.004-3.004z",
|
|
495
|
+
key: "ukzhwg"
|
|
496
|
+
}
|
|
497
|
+
],
|
|
498
|
+
["path", { d: "M14.487 7.858A1 1 0 0 1 14 7V2", key: "1klhew" }],
|
|
499
|
+
[
|
|
500
|
+
"path",
|
|
501
|
+
{
|
|
502
|
+
d: "M20 19.645V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l2.516 2.516",
|
|
503
|
+
key: "rxaxab"
|
|
504
|
+
}
|
|
505
|
+
],
|
|
506
|
+
["path", { d: "M8 18h1", key: "13wk12" }]
|
|
507
|
+
],
|
|
508
|
+
aliases: ["file-signature"]
|
|
509
|
+
};
|
|
510
|
+
__iconData18.node;
|
|
511
|
+
var FilePenLine = createLucideIcon(__iconData18);
|
|
512
|
+
|
|
513
|
+
// ../../node_modules/lucide-react/dist/esm/icons/file-spreadsheet.mjs
|
|
514
|
+
var __iconData19 = {
|
|
515
|
+
name: "file-spreadsheet",
|
|
516
|
+
size: 24,
|
|
517
|
+
node: [
|
|
518
|
+
[
|
|
519
|
+
"path",
|
|
520
|
+
{
|
|
521
|
+
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
522
|
+
key: "1oefj6"
|
|
523
|
+
}
|
|
524
|
+
],
|
|
525
|
+
["path", { d: "M14 2v5a1 1 0 0 0 1 1h5", key: "wfsgrz" }],
|
|
526
|
+
["path", { d: "M8 13h2", key: "yr2amv" }],
|
|
527
|
+
["path", { d: "M14 13h2", key: "un5t4a" }],
|
|
528
|
+
["path", { d: "M8 17h2", key: "2yhykz" }],
|
|
529
|
+
["path", { d: "M14 17h2", key: "10kma7" }]
|
|
530
|
+
]
|
|
531
|
+
};
|
|
532
|
+
__iconData19.node;
|
|
533
|
+
var FileSpreadsheet = createLucideIcon(__iconData19);
|
|
534
|
+
|
|
535
|
+
// ../../node_modules/lucide-react/dist/esm/icons/file-text.mjs
|
|
536
|
+
var __iconData20 = {
|
|
537
|
+
name: "file-text",
|
|
538
|
+
size: 24,
|
|
539
|
+
node: [
|
|
540
|
+
[
|
|
541
|
+
"path",
|
|
542
|
+
{
|
|
543
|
+
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
544
|
+
key: "1oefj6"
|
|
545
|
+
}
|
|
546
|
+
],
|
|
547
|
+
["path", { d: "M14 2v5a1 1 0 0 0 1 1h5", key: "wfsgrz" }],
|
|
548
|
+
["path", { d: "M10 9H8", key: "b1mrlr" }],
|
|
549
|
+
["path", { d: "M16 13H8", key: "t4e002" }],
|
|
550
|
+
["path", { d: "M16 17H8", key: "z1uh3a" }]
|
|
551
|
+
]
|
|
552
|
+
};
|
|
553
|
+
__iconData20.node;
|
|
554
|
+
var FileText = createLucideIcon(__iconData20);
|
|
555
|
+
|
|
556
|
+
// ../../node_modules/lucide-react/dist/esm/icons/folder.mjs
|
|
557
|
+
var __iconData21 = {
|
|
558
|
+
name: "folder",
|
|
559
|
+
size: 24,
|
|
560
|
+
node: [
|
|
561
|
+
[
|
|
562
|
+
"path",
|
|
563
|
+
{
|
|
564
|
+
d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",
|
|
565
|
+
key: "1kt360"
|
|
566
|
+
}
|
|
567
|
+
]
|
|
568
|
+
]
|
|
569
|
+
};
|
|
570
|
+
__iconData21.node;
|
|
571
|
+
var Folder = createLucideIcon(__iconData21);
|
|
572
|
+
|
|
573
|
+
// ../../node_modules/lucide-react/dist/esm/icons/gauge.mjs
|
|
574
|
+
var __iconData22 = {
|
|
575
|
+
name: "gauge",
|
|
576
|
+
size: 24,
|
|
577
|
+
node: [
|
|
578
|
+
["path", { d: "m12 14 4-4", key: "9kzdfg" }],
|
|
579
|
+
["path", { d: "M3.34 19a10 10 0 1 1 17.32 0", key: "19p75a" }]
|
|
580
|
+
]
|
|
581
|
+
};
|
|
582
|
+
__iconData22.node;
|
|
583
|
+
var Gauge = createLucideIcon(__iconData22);
|
|
584
|
+
|
|
585
|
+
// ../../node_modules/lucide-react/dist/esm/icons/globe.mjs
|
|
586
|
+
var __iconData23 = {
|
|
587
|
+
name: "globe",
|
|
588
|
+
size: 24,
|
|
589
|
+
node: [
|
|
590
|
+
["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
|
|
591
|
+
["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20", key: "13o1zl" }],
|
|
592
|
+
["path", { d: "M2 12h20", key: "9i4pu4" }]
|
|
593
|
+
]
|
|
594
|
+
};
|
|
595
|
+
__iconData23.node;
|
|
596
|
+
var Globe = createLucideIcon(__iconData23);
|
|
597
|
+
|
|
598
|
+
// ../../node_modules/lucide-react/dist/esm/icons/image.mjs
|
|
599
|
+
var __iconData24 = {
|
|
600
|
+
name: "image",
|
|
601
|
+
size: 24,
|
|
602
|
+
node: [
|
|
603
|
+
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
|
|
604
|
+
["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
|
|
605
|
+
["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
|
|
606
|
+
]
|
|
607
|
+
};
|
|
608
|
+
__iconData24.node;
|
|
609
|
+
var Image = createLucideIcon(__iconData24);
|
|
610
|
+
|
|
611
|
+
// ../../node_modules/lucide-react/dist/esm/icons/list-checks.mjs
|
|
612
|
+
var __iconData25 = {
|
|
613
|
+
name: "list-checks",
|
|
614
|
+
size: 24,
|
|
615
|
+
node: [
|
|
616
|
+
["path", { d: "M13 5h8", key: "a7qcls" }],
|
|
617
|
+
["path", { d: "M13 12h8", key: "h98zly" }],
|
|
618
|
+
["path", { d: "M13 19h8", key: "c3s6r1" }],
|
|
619
|
+
["path", { d: "m3 17 2 2 4-4", key: "1jhpwq" }],
|
|
620
|
+
["path", { d: "m3 7 2 2 4-4", key: "1obspn" }]
|
|
621
|
+
]
|
|
622
|
+
};
|
|
623
|
+
__iconData25.node;
|
|
624
|
+
var ListChecks = createLucideIcon(__iconData25);
|
|
625
|
+
|
|
626
|
+
// ../../node_modules/lucide-react/dist/esm/icons/loader-circle.mjs
|
|
627
|
+
var __iconData26 = {
|
|
628
|
+
name: "loader-circle",
|
|
629
|
+
size: 24,
|
|
630
|
+
node: [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]],
|
|
631
|
+
aliases: ["loader-2"]
|
|
632
|
+
};
|
|
633
|
+
__iconData26.node;
|
|
634
|
+
var LoaderCircle = createLucideIcon(__iconData26);
|
|
635
|
+
|
|
636
|
+
// ../../node_modules/lucide-react/dist/esm/icons/mic.mjs
|
|
637
|
+
var __iconData27 = {
|
|
638
|
+
name: "mic",
|
|
639
|
+
size: 24,
|
|
640
|
+
node: [
|
|
641
|
+
["path", { d: "M12 19v3", key: "npa21l" }],
|
|
642
|
+
["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2", key: "1vc78b" }],
|
|
643
|
+
["rect", { x: "9", y: "2", width: "6", height: "13", rx: "3", key: "s6n7sd" }]
|
|
644
|
+
]
|
|
645
|
+
};
|
|
646
|
+
__iconData27.node;
|
|
647
|
+
var Mic = createLucideIcon(__iconData27);
|
|
648
|
+
|
|
649
|
+
// ../../node_modules/lucide-react/dist/esm/icons/paperclip.mjs
|
|
650
|
+
var __iconData28 = {
|
|
651
|
+
name: "paperclip",
|
|
652
|
+
size: 24,
|
|
653
|
+
node: [
|
|
654
|
+
[
|
|
655
|
+
"path",
|
|
656
|
+
{
|
|
657
|
+
d: "m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551",
|
|
658
|
+
key: "1miecu"
|
|
659
|
+
}
|
|
660
|
+
]
|
|
661
|
+
]
|
|
662
|
+
};
|
|
663
|
+
__iconData28.node;
|
|
664
|
+
var Paperclip = createLucideIcon(__iconData28);
|
|
665
|
+
|
|
666
|
+
// ../../node_modules/lucide-react/dist/esm/icons/rotate-ccw.mjs
|
|
667
|
+
var __iconData29 = {
|
|
668
|
+
name: "rotate-ccw",
|
|
669
|
+
size: 24,
|
|
670
|
+
node: [
|
|
671
|
+
["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", key: "1357e3" }],
|
|
672
|
+
["path", { d: "M3 3v5h5", key: "1xhq8a" }]
|
|
673
|
+
]
|
|
674
|
+
};
|
|
675
|
+
__iconData29.node;
|
|
676
|
+
var RotateCcw = createLucideIcon(__iconData29);
|
|
677
|
+
|
|
678
|
+
// ../../node_modules/lucide-react/dist/esm/icons/search.mjs
|
|
679
|
+
var __iconData30 = {
|
|
680
|
+
name: "search",
|
|
681
|
+
size: 24,
|
|
682
|
+
node: [
|
|
683
|
+
["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
|
|
684
|
+
["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
|
|
685
|
+
]
|
|
686
|
+
};
|
|
687
|
+
__iconData30.node;
|
|
688
|
+
var Search = createLucideIcon(__iconData30);
|
|
689
|
+
|
|
690
|
+
// ../../node_modules/lucide-react/dist/esm/icons/send.mjs
|
|
691
|
+
var __iconData31 = {
|
|
692
|
+
name: "send",
|
|
693
|
+
size: 24,
|
|
694
|
+
node: [
|
|
695
|
+
[
|
|
696
|
+
"path",
|
|
697
|
+
{
|
|
698
|
+
d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z",
|
|
699
|
+
key: "1ffxy3"
|
|
700
|
+
}
|
|
701
|
+
],
|
|
702
|
+
["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }]
|
|
703
|
+
]
|
|
704
|
+
};
|
|
705
|
+
__iconData31.node;
|
|
706
|
+
var Send = createLucideIcon(__iconData31);
|
|
707
|
+
|
|
708
|
+
// ../../node_modules/lucide-react/dist/esm/icons/shield-alert.mjs
|
|
709
|
+
var __iconData32 = {
|
|
710
|
+
name: "shield-alert",
|
|
711
|
+
size: 24,
|
|
712
|
+
node: [
|
|
713
|
+
[
|
|
714
|
+
"path",
|
|
715
|
+
{
|
|
716
|
+
d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",
|
|
717
|
+
key: "oel41y"
|
|
718
|
+
}
|
|
719
|
+
],
|
|
720
|
+
["path", { d: "M12 8v4", key: "1got3b" }],
|
|
721
|
+
["path", { d: "M12 16h.01", key: "1drbdi" }]
|
|
722
|
+
]
|
|
723
|
+
};
|
|
724
|
+
__iconData32.node;
|
|
725
|
+
var ShieldAlert = createLucideIcon(__iconData32);
|
|
726
|
+
|
|
727
|
+
// ../../node_modules/lucide-react/dist/esm/icons/square-terminal.mjs
|
|
728
|
+
var __iconData33 = {
|
|
729
|
+
name: "square-terminal",
|
|
730
|
+
size: 24,
|
|
731
|
+
node: [
|
|
732
|
+
["path", { d: "m7 11 2-2-2-2", key: "1lz0vl" }],
|
|
733
|
+
["path", { d: "M11 13h4", key: "1p7l4v" }],
|
|
734
|
+
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }]
|
|
735
|
+
],
|
|
736
|
+
aliases: ["terminal-square"]
|
|
737
|
+
};
|
|
738
|
+
__iconData33.node;
|
|
739
|
+
var SquareTerminal = createLucideIcon(__iconData33);
|
|
740
|
+
|
|
741
|
+
// ../../node_modules/lucide-react/dist/esm/icons/square.mjs
|
|
742
|
+
var __iconData34 = {
|
|
743
|
+
name: "square",
|
|
744
|
+
size: 24,
|
|
745
|
+
node: [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]]
|
|
746
|
+
};
|
|
747
|
+
__iconData34.node;
|
|
748
|
+
var Square = createLucideIcon(__iconData34);
|
|
749
|
+
|
|
750
|
+
// ../../node_modules/lucide-react/dist/esm/icons/users-round.mjs
|
|
751
|
+
var __iconData35 = {
|
|
752
|
+
name: "users-round",
|
|
753
|
+
size: 24,
|
|
754
|
+
node: [
|
|
755
|
+
["path", { d: "M18 21a8 8 0 0 0-16 0", key: "3ypg7q" }],
|
|
756
|
+
["circle", { cx: "10", cy: "8", r: "5", key: "o932ke" }],
|
|
757
|
+
["path", { d: "M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3", key: "10s06x" }]
|
|
758
|
+
],
|
|
759
|
+
aliases: ["users-2"]
|
|
760
|
+
};
|
|
761
|
+
__iconData35.node;
|
|
762
|
+
var UsersRound = createLucideIcon(__iconData35);
|
|
763
|
+
|
|
764
|
+
// ../../node_modules/lucide-react/dist/esm/icons/x.mjs
|
|
765
|
+
var __iconData36 = {
|
|
766
|
+
name: "x",
|
|
767
|
+
size: 24,
|
|
768
|
+
node: [
|
|
769
|
+
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
|
|
770
|
+
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
771
|
+
]
|
|
772
|
+
};
|
|
773
|
+
__iconData36.node;
|
|
774
|
+
var X = createLucideIcon(__iconData36);
|
|
775
|
+
|
|
776
|
+
// src/react/Piloto.tsx
|
|
777
|
+
import {
|
|
778
|
+
forwardRef as forwardRef3,
|
|
779
|
+
useCallback as useCallback3,
|
|
780
|
+
useEffect as useEffect6,
|
|
781
|
+
useImperativeHandle,
|
|
782
|
+
useLayoutEffect,
|
|
783
|
+
useMemo as useMemo4,
|
|
784
|
+
useRef as useRef5,
|
|
785
|
+
useState as useState9
|
|
786
|
+
} from "react";
|
|
787
|
+
|
|
788
|
+
// src/react/Bubble.tsx
|
|
789
|
+
import { useState as useState5 } from "react";
|
|
790
|
+
|
|
791
|
+
// src/react/ConfirmCard.tsx
|
|
792
|
+
import { useEffect, useRef, useState } from "react";
|
|
793
|
+
|
|
794
|
+
// src/react/context.tsx
|
|
795
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
796
|
+
|
|
797
|
+
// src/react/strings.ts
|
|
798
|
+
var STRINGS_ES = {
|
|
799
|
+
numberLocale: "es-MX",
|
|
800
|
+
placeholder: (agent) => `Escr\xEDbele a ${agent}`,
|
|
801
|
+
placeholderDefault: "Escr\xEDbele al agente",
|
|
802
|
+
placeholderConnecting: "Conectando con el agente\u2026",
|
|
803
|
+
send: "Enviar",
|
|
804
|
+
stop: "Detener",
|
|
805
|
+
uploading: "Subiendo tu archivo\u2026",
|
|
806
|
+
uploadingN: (n) => `Subiendo tus ${n} archivos\u2026`,
|
|
807
|
+
attachImage: "Adjuntar imagen",
|
|
808
|
+
attachFile: "Adjuntar archivo",
|
|
809
|
+
noImages: "Este agente no ve im\xE1genes",
|
|
810
|
+
removeAttachment: "Quitar adjunto",
|
|
811
|
+
removeReference: "Quitar referencia",
|
|
812
|
+
dropFiles: "Suelta tus archivos",
|
|
813
|
+
dropHint: "Excel, Word, PDF o im\xE1genes",
|
|
814
|
+
tooBig: (name, mb) => `${name} pesa m\xE1s de ${mb} MB`,
|
|
815
|
+
tooMany: (max) => `no caben m\xE1s de ${max} archivos por mensaje`,
|
|
816
|
+
cantSeeImage: (name) => `${name}: este agente no ve im\xE1genes`,
|
|
817
|
+
cantUpload: (name) => `${name}: aqu\xED s\xF3lo se pueden mandar im\xE1genes y texto`,
|
|
818
|
+
uploadFailed: "no se pudo subir el archivo",
|
|
819
|
+
recordVoice: "Grabar nota de voz",
|
|
820
|
+
voiceNote: "Nota de voz",
|
|
821
|
+
cancelVoice: "Cancelar la nota de voz",
|
|
822
|
+
stopAndSendVoice: "Parar y mandar la nota de voz",
|
|
823
|
+
micDenied: "No tengo permiso para usar el micr\xF3fono.",
|
|
824
|
+
voiceEmpty: "no se entendi\xF3 nada en la nota de voz",
|
|
825
|
+
voiceFailed: "no se pudo mandar la nota de voz",
|
|
826
|
+
model: "Modelo",
|
|
827
|
+
thinking: "Pensando\u2026",
|
|
828
|
+
steered: "Se lo pas\xE9 al turno en curso",
|
|
829
|
+
waitPhase: {
|
|
830
|
+
waking: "Me estoy poniendo los lentes \u{1F453}",
|
|
831
|
+
connecting: "Estoy abriendo mi libreta",
|
|
832
|
+
session: "Estoy pensando"
|
|
833
|
+
},
|
|
834
|
+
howItGoes: (s) => s < 6 ? "Pensando\u2026" : s < 15 ? "Trabajando\u2026" : s < 60 ? "Estoy en ello\u2026" : s < 120 ? "Esto me lleva un poco\u2026" : s < 300 ? "Llevo un buen rato\u2026" : "Me est\xE1 tomando mucho \u2014 puedes detenerme",
|
|
835
|
+
stillWorking: "Estoy en ello\u2026",
|
|
836
|
+
cantStart: "No pude arrancar.",
|
|
837
|
+
retry: "Reintentar",
|
|
838
|
+
resume: "Retomar",
|
|
839
|
+
continueTurn: "Continuar",
|
|
840
|
+
interrupted: (reason) => `Me qued\xE9 a medias: ${reason}.`,
|
|
841
|
+
interruptedOurFault: "Se cort\xF3 por una falla nuestra, no por tu mensaje.",
|
|
842
|
+
copy: "Copiar",
|
|
843
|
+
copied: "Copiado",
|
|
844
|
+
copyMessage: "Copiar mensaje",
|
|
845
|
+
worked: (s) => `Trabaj\xF3 ${s} s`,
|
|
846
|
+
stepsExpand: "Ver el detalle de los pasos",
|
|
847
|
+
stepsCollapse: "Plegar los pasos",
|
|
848
|
+
earlierSteps: (n) => n === 1 ? "1 paso antes" : `${n} pasos antes`,
|
|
849
|
+
withProblems: (n) => n === 1 ? "1 con problemas" : `${n} con problemas`,
|
|
850
|
+
running: "Ejecutando",
|
|
851
|
+
done: "Lista",
|
|
852
|
+
failed: "Fall\xF3",
|
|
853
|
+
step: "Paso",
|
|
854
|
+
replyOptions: "Opciones para responder",
|
|
855
|
+
connectSteps: [
|
|
856
|
+
{ label: "Despertando al agente", hint: "si estaba dormido, unos segundos" },
|
|
857
|
+
{ label: "Abriendo el canal", hint: "conexi\xF3n segura con su m\xE1quina" },
|
|
858
|
+
{ label: "Creando la sesi\xF3n", hint: "el agente carga su contexto" }
|
|
859
|
+
],
|
|
860
|
+
connectRetry: "Reintentar",
|
|
861
|
+
linkTitle: "Vas a salir de aqu\xED",
|
|
862
|
+
linkCopy: "Copiar",
|
|
863
|
+
linkCopied: "Copiado",
|
|
864
|
+
linkOpen: "Abrir",
|
|
865
|
+
close: "Cerrar",
|
|
866
|
+
goToLatest: "Ir al \xFAltimo mensaje",
|
|
867
|
+
confirmTitle: "Necesito tu permiso para",
|
|
868
|
+
approve: "Aprobar",
|
|
869
|
+
reject: "Rechazar",
|
|
870
|
+
confirmExpired: "Este pedido ya no estaba abierto (se agot\xF3 el plazo).",
|
|
871
|
+
confirmFailed: "No se pudo mandar tu respuesta. Intenta de nuevo.",
|
|
872
|
+
expiresIn: (t) => `Vence en ${t}`,
|
|
873
|
+
expiredNow: "Venci\xF3 sin respuesta",
|
|
874
|
+
decisionApproved: "Aprobaste",
|
|
875
|
+
decisionRejected: "Rechazaste",
|
|
876
|
+
decisionExpired: "Venci\xF3",
|
|
877
|
+
stampApproved: "Aprobado",
|
|
878
|
+
stampRejected: "Rechazado",
|
|
879
|
+
stampExpired: "Vencido"
|
|
880
|
+
};
|
|
881
|
+
var STRINGS_EN = {
|
|
882
|
+
numberLocale: "en-US",
|
|
883
|
+
placeholder: (agent) => `Message ${agent}`,
|
|
884
|
+
placeholderDefault: "Message the agent",
|
|
885
|
+
placeholderConnecting: "Connecting to the agent\u2026",
|
|
886
|
+
send: "Send",
|
|
887
|
+
stop: "Stop",
|
|
888
|
+
uploading: "Uploading your file\u2026",
|
|
889
|
+
uploadingN: (n) => `Uploading your ${n} files\u2026`,
|
|
890
|
+
attachImage: "Attach image",
|
|
891
|
+
attachFile: "Attach file",
|
|
892
|
+
noImages: "This agent can't see images",
|
|
893
|
+
removeAttachment: "Remove attachment",
|
|
894
|
+
removeReference: "Remove reference",
|
|
895
|
+
dropFiles: "Drop your files",
|
|
896
|
+
dropHint: "Excel, Word, PDF or images",
|
|
897
|
+
tooBig: (name, mb) => `${name} is larger than ${mb} MB`,
|
|
898
|
+
tooMany: (max) => `no more than ${max} files per message`,
|
|
899
|
+
cantSeeImage: (name) => `${name}: this agent can't see images`,
|
|
900
|
+
cantUpload: (name) => `${name}: only images and text can be sent here`,
|
|
901
|
+
uploadFailed: "couldn't upload the file",
|
|
902
|
+
recordVoice: "Record voice note",
|
|
903
|
+
voiceNote: "Voice note",
|
|
904
|
+
cancelVoice: "Cancel voice note",
|
|
905
|
+
stopAndSendVoice: "Stop and send voice note",
|
|
906
|
+
micDenied: "I don't have permission to use the microphone.",
|
|
907
|
+
voiceEmpty: "nothing was understood in the voice note",
|
|
908
|
+
voiceFailed: "couldn't send the voice note",
|
|
909
|
+
model: "Model",
|
|
910
|
+
thinking: "Thinking\u2026",
|
|
911
|
+
steered: "Passed it to the running turn",
|
|
912
|
+
waitPhase: {
|
|
913
|
+
waking: "Putting my glasses on \u{1F453}",
|
|
914
|
+
connecting: "Opening my notebook",
|
|
915
|
+
session: "Thinking"
|
|
916
|
+
},
|
|
917
|
+
howItGoes: (s) => s < 6 ? "Thinking\u2026" : s < 15 ? "Working\u2026" : s < 60 ? "On it\u2026" : s < 120 ? "This is taking a bit\u2026" : s < 300 ? "Been at it a while\u2026" : "This is taking long \u2014 you can stop me",
|
|
918
|
+
stillWorking: "On it\u2026",
|
|
919
|
+
cantStart: "Couldn't start.",
|
|
920
|
+
retry: "Retry",
|
|
921
|
+
resume: "Resume",
|
|
922
|
+
continueTurn: "Continue",
|
|
923
|
+
interrupted: (reason) => `I stopped halfway: ${reason}.`,
|
|
924
|
+
interruptedOurFault: "It stopped because of an error on our side, not your message.",
|
|
925
|
+
copy: "Copy",
|
|
926
|
+
copied: "Copied",
|
|
927
|
+
copyMessage: "Copy message",
|
|
928
|
+
worked: (s) => `Worked ${s} s`,
|
|
929
|
+
stepsExpand: "See step details",
|
|
930
|
+
stepsCollapse: "Collapse the steps",
|
|
931
|
+
earlierSteps: (n) => n === 1 ? "1 earlier step" : `${n} earlier steps`,
|
|
932
|
+
withProblems: (n) => `${n} with problems`,
|
|
933
|
+
running: "Running",
|
|
934
|
+
done: "Done",
|
|
935
|
+
failed: "Failed",
|
|
936
|
+
step: "Step",
|
|
937
|
+
replyOptions: "Reply options",
|
|
938
|
+
connectSteps: [
|
|
939
|
+
{ label: "Waking the agent", hint: "if it was asleep, a few seconds" },
|
|
940
|
+
{ label: "Opening the channel", hint: "secure connection to its machine" },
|
|
941
|
+
{ label: "Creating the session", hint: "the agent loads its context" }
|
|
942
|
+
],
|
|
943
|
+
connectRetry: "Retry",
|
|
944
|
+
linkTitle: "You're about to leave",
|
|
945
|
+
linkCopy: "Copy",
|
|
946
|
+
linkCopied: "Copied",
|
|
947
|
+
linkOpen: "Open",
|
|
948
|
+
close: "Close",
|
|
949
|
+
goToLatest: "Go to latest message",
|
|
950
|
+
confirmTitle: "I need your permission to",
|
|
951
|
+
approve: "Approve",
|
|
952
|
+
reject: "Reject",
|
|
953
|
+
confirmExpired: "This request was no longer open (it timed out).",
|
|
954
|
+
confirmFailed: "Couldn't send your answer. Try again.",
|
|
955
|
+
expiresIn: (t) => `Expires in ${t}`,
|
|
956
|
+
expiredNow: "Expired without an answer",
|
|
957
|
+
decisionApproved: "You approved",
|
|
958
|
+
decisionRejected: "You rejected",
|
|
959
|
+
decisionExpired: "Expired",
|
|
960
|
+
stampApproved: "Approved",
|
|
961
|
+
stampRejected: "Rejected",
|
|
962
|
+
stampExpired: "Expired"
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// src/react/context.tsx
|
|
966
|
+
var PilotoContext = createContext2({
|
|
967
|
+
strings: STRINGS_ES,
|
|
968
|
+
toolVerbs: TOOL_VERBS_ES,
|
|
969
|
+
toolIcons: {},
|
|
970
|
+
formatUserText: cleanUserText,
|
|
971
|
+
theme: "light",
|
|
972
|
+
skin: "ghosty",
|
|
973
|
+
markdown: {}
|
|
974
|
+
});
|
|
975
|
+
var usePiloto = () => useContext2(PilotoContext);
|
|
976
|
+
function rootProps(theme, skin = "ghosty") {
|
|
977
|
+
return { className: "piloto", "data-theme": theme, "data-skin": skin };
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/react/ConfirmCard.tsx
|
|
981
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
982
|
+
var isRejectOption = (o) => /^reject|deny|cancel/i.test(o.kind ?? o.optionId);
|
|
983
|
+
var mmss = (ms) => {
|
|
984
|
+
const s = Math.max(0, Math.ceil(ms / 1e3));
|
|
985
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
986
|
+
};
|
|
987
|
+
function useRemaining(expiresAt) {
|
|
988
|
+
const end = expiresAt === void 0 ? NaN : expiryMs(expiresAt);
|
|
989
|
+
const [now, setNow] = useState(() => Date.now());
|
|
990
|
+
const first = useRef(null);
|
|
991
|
+
useEffect(() => {
|
|
992
|
+
if (Number.isNaN(end)) return;
|
|
993
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
994
|
+
return () => clearInterval(id);
|
|
995
|
+
}, [end]);
|
|
996
|
+
if (Number.isNaN(end)) return null;
|
|
997
|
+
if (first.current === null) first.current = Math.max(1, end - now);
|
|
998
|
+
return { left: end - now, total: first.current };
|
|
999
|
+
}
|
|
1000
|
+
var STAMP_MS = 1100;
|
|
1001
|
+
function ConfirmCard({
|
|
1002
|
+
request,
|
|
1003
|
+
onRespond,
|
|
1004
|
+
onSettled,
|
|
1005
|
+
onExpire
|
|
1006
|
+
}) {
|
|
1007
|
+
const { strings: t } = usePiloto();
|
|
1008
|
+
const [pending, setPending] = useState(null);
|
|
1009
|
+
const [problem, setProblem] = useState(null);
|
|
1010
|
+
const [stamp, setStamp] = useState(null);
|
|
1011
|
+
const time = useRemaining(request.expiresAt);
|
|
1012
|
+
const expired = time !== null && time.left <= 0;
|
|
1013
|
+
const urgent = time !== null && !expired && time.left < 6e4;
|
|
1014
|
+
useEffect(() => {
|
|
1015
|
+
if (!expired || stamp) return;
|
|
1016
|
+
setStamp("expired");
|
|
1017
|
+
const id = setTimeout(() => onExpire?.(request.id), STAMP_MS + 600);
|
|
1018
|
+
return () => clearTimeout(id);
|
|
1019
|
+
}, [expired, stamp, onExpire, request.id]);
|
|
1020
|
+
const options = request.options.length ? request.options : [
|
|
1021
|
+
{ optionId: "reject", name: t.reject, kind: "reject_once" },
|
|
1022
|
+
{ optionId: "allow", name: t.approve, kind: "allow_once" }
|
|
1023
|
+
];
|
|
1024
|
+
const ordered = [...options.filter(isRejectOption), ...options.filter((o) => !isRejectOption(o))];
|
|
1025
|
+
const answer = async (o) => {
|
|
1026
|
+
setPending(o.optionId);
|
|
1027
|
+
setProblem(null);
|
|
1028
|
+
try {
|
|
1029
|
+
const r = await onRespond(request.id, o.optionId);
|
|
1030
|
+
const status = r && r.resolved === false ? "expired" : isRejectOption(o) ? "rejected" : "approved";
|
|
1031
|
+
setStamp(status);
|
|
1032
|
+
setTimeout(() => onSettled?.(request.id, o.optionId, r), STAMP_MS);
|
|
1033
|
+
} catch {
|
|
1034
|
+
setProblem(t.confirmFailed);
|
|
1035
|
+
setPending(null);
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
const stampLabel = stamp === "approved" ? t.stampApproved : stamp === "rejected" ? t.stampRejected : t.stampExpired;
|
|
1039
|
+
const locked = pending !== null || stamp !== null;
|
|
1040
|
+
return /* @__PURE__ */ jsxs("section", { className: "piloto-ticket", "data-state": stamp ?? "open", "data-urgent": urgent || void 0, "aria-label": t.confirmTitle, children: [
|
|
1041
|
+
/* @__PURE__ */ jsxs("div", { className: "piloto-ticket-head", children: [
|
|
1042
|
+
/* @__PURE__ */ jsx("span", { className: "piloto-ticket-icon", "aria-hidden": true, children: /* @__PURE__ */ jsx(ShieldAlert, { className: "h-5 w-5" }) }),
|
|
1043
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1044
|
+
/* @__PURE__ */ jsx("p", { className: "piloto-ticket-ask", children: t.confirmTitle }),
|
|
1045
|
+
/* @__PURE__ */ jsx("p", { className: "piloto-ticket-title", children: request.title }),
|
|
1046
|
+
request.tool && /* @__PURE__ */ jsx("code", { className: "piloto-ticket-tool", children: request.tool })
|
|
1047
|
+
] })
|
|
1048
|
+
] }),
|
|
1049
|
+
/* @__PURE__ */ jsx("div", { className: "piloto-ticket-tear", "aria-hidden": true }),
|
|
1050
|
+
/* @__PURE__ */ jsxs("div", { className: "piloto-ticket-foot", children: [
|
|
1051
|
+
time !== null ? /* @__PURE__ */ jsxs("span", { className: "piloto-ticket-clock", title: t.expiresIn(mmss(time.left)), "aria-label": expired ? t.expiredNow : t.expiresIn(mmss(time.left)), children: [
|
|
1052
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-4 w-4", "aria-hidden": true }),
|
|
1053
|
+
mmss(time.left)
|
|
1054
|
+
] }) : /* @__PURE__ */ jsx("span", {}),
|
|
1055
|
+
/* @__PURE__ */ jsx("div", { className: "piloto-ticket-actions", children: ordered.map((o) => /* @__PURE__ */ jsx(
|
|
1056
|
+
"button",
|
|
1057
|
+
{
|
|
1058
|
+
type: "button",
|
|
1059
|
+
disabled: locked || expired,
|
|
1060
|
+
onClick: () => void answer(o),
|
|
1061
|
+
className: isRejectOption(o) ? "piloto-btn piloto-btn-quiet" : "piloto-btn piloto-btn-go",
|
|
1062
|
+
"aria-busy": pending === o.optionId || void 0,
|
|
1063
|
+
children: o.name
|
|
1064
|
+
},
|
|
1065
|
+
o.optionId
|
|
1066
|
+
)) })
|
|
1067
|
+
] }),
|
|
1068
|
+
time !== null && /* @__PURE__ */ jsx("div", { className: "piloto-fuse", "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { style: { transform: `scaleX(${Math.max(0, Math.min(1, time.left / time.total))})` } }) }),
|
|
1069
|
+
problem && /* @__PURE__ */ jsx("p", { className: "piloto-ticket-problem", role: "alert", children: problem }),
|
|
1070
|
+
stamp && /* @__PURE__ */ jsx("span", { className: "piloto-stamp", "data-kind": stamp, role: "status", children: stampLabel })
|
|
1071
|
+
] });
|
|
1072
|
+
}
|
|
1073
|
+
function DecisionLines({ decisions }) {
|
|
1074
|
+
const { strings: t } = usePiloto();
|
|
1075
|
+
if (!decisions.length) return null;
|
|
1076
|
+
return /* @__PURE__ */ jsx("ul", { className: "piloto-decisions", children: decisions.map((d) => {
|
|
1077
|
+
const Icon2 = d.status === "approved" ? CircleCheck : d.status === "rejected" ? CircleSlash : Clock;
|
|
1078
|
+
const label = d.status === "approved" ? t.decisionApproved : d.status === "rejected" ? t.decisionRejected : t.decisionExpired;
|
|
1079
|
+
return /* @__PURE__ */ jsxs("li", { className: "piloto-decision", "data-status": d.status, children: [
|
|
1080
|
+
/* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4 shrink-0", "aria-hidden": true }),
|
|
1081
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
1082
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
1083
|
+
label,
|
|
1084
|
+
":"
|
|
1085
|
+
] }),
|
|
1086
|
+
" ",
|
|
1087
|
+
d.title,
|
|
1088
|
+
d.detail && /* @__PURE__ */ jsxs("span", { className: "piloto-decision-detail", children: [
|
|
1089
|
+
" (",
|
|
1090
|
+
d.detail,
|
|
1091
|
+
")"
|
|
1092
|
+
] })
|
|
1093
|
+
] })
|
|
1094
|
+
] }, d.id);
|
|
1095
|
+
}) });
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// src/react/Markdown.tsx
|
|
1099
|
+
import { useState as useState3 } from "react";
|
|
1100
|
+
import { Streamdown } from "streamdown";
|
|
1101
|
+
|
|
1102
|
+
// src/react/TextAtPace.tsx
|
|
1103
|
+
import { createContext as createContext3, memo, useContext as useContext3, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
1104
|
+
import { Block } from "streamdown";
|
|
1105
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1106
|
+
var CHUNK_MS = 400;
|
|
1107
|
+
var LETTERS_PER_CHUNK = 20;
|
|
1108
|
+
var INTERVAL = 130;
|
|
1109
|
+
var QUIET_MS = 600;
|
|
1110
|
+
function chunkEnd(c, from, complete) {
|
|
1111
|
+
if (from >= c.length) return null;
|
|
1112
|
+
let i = from;
|
|
1113
|
+
let lastBoundary = null;
|
|
1114
|
+
const blank = (ch) => ch === " " || ch === "\n" || ch === " " || ch === "\r";
|
|
1115
|
+
while (i < c.length) {
|
|
1116
|
+
while (i < c.length && blank(c[i])) i++;
|
|
1117
|
+
while (i < c.length && !blank(c[i])) i++;
|
|
1118
|
+
if (i >= c.length) break;
|
|
1119
|
+
while (i < c.length && blank(c[i]) && c[i] !== "\n") i++;
|
|
1120
|
+
lastBoundary = i;
|
|
1121
|
+
if (i - from >= LETTERS_PER_CHUNK) return i;
|
|
1122
|
+
}
|
|
1123
|
+
return complete ? lastBoundary : c.length;
|
|
1124
|
+
}
|
|
1125
|
+
function useTextAtPace(text, live, whole = false) {
|
|
1126
|
+
const animate = useRef2(null);
|
|
1127
|
+
if (animate.current === null) {
|
|
1128
|
+
const still = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
1129
|
+
animate.current = live && !still;
|
|
1130
|
+
}
|
|
1131
|
+
const [released, setReleased] = useState2(animate.current ? 0 : text.length);
|
|
1132
|
+
const textRef = useRef2(text);
|
|
1133
|
+
textRef.current = text;
|
|
1134
|
+
const liveRef = useRef2(live);
|
|
1135
|
+
liveRef.current = live;
|
|
1136
|
+
const releasedRef = useRef2(released);
|
|
1137
|
+
releasedRef.current = released;
|
|
1138
|
+
useEffect2(() => {
|
|
1139
|
+
if (!animate.current) return;
|
|
1140
|
+
let last = 0;
|
|
1141
|
+
let len = textRef.current.length;
|
|
1142
|
+
let changed = performance.now();
|
|
1143
|
+
const id = window.setInterval(() => {
|
|
1144
|
+
const t = textRef.current;
|
|
1145
|
+
if (t.length !== len) {
|
|
1146
|
+
len = t.length;
|
|
1147
|
+
changed = performance.now();
|
|
1148
|
+
}
|
|
1149
|
+
const s = Math.min(releasedRef.current, t.length);
|
|
1150
|
+
if (!liveRef.current) {
|
|
1151
|
+
if (s < t.length) setReleased(t.length);
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const lag = t.length - s;
|
|
1155
|
+
const every = lag > 300 ? Math.max(30, INTERVAL * 300 / lag) : INTERVAL;
|
|
1156
|
+
const now = performance.now();
|
|
1157
|
+
if (now - last < every) return;
|
|
1158
|
+
if (whole && s >= t.length) return;
|
|
1159
|
+
const quiet = !whole && now - changed > QUIET_MS;
|
|
1160
|
+
const end = quiet && s < t.length ? t.length : chunkEnd(t, s, true) ?? (whole ? t.length : null);
|
|
1161
|
+
if (end != null && end > s) {
|
|
1162
|
+
last = now;
|
|
1163
|
+
setReleased(end);
|
|
1164
|
+
}
|
|
1165
|
+
}, 16);
|
|
1166
|
+
return () => clearInterval(id);
|
|
1167
|
+
}, []);
|
|
1168
|
+
if (!animate.current) return { visible: text, animate: false };
|
|
1169
|
+
return { visible: text.slice(0, Math.min(released, text.length)), animate: true };
|
|
1170
|
+
}
|
|
1171
|
+
var ChunksContext = createContext3(null);
|
|
1172
|
+
function useChunkRegistry() {
|
|
1173
|
+
return useMemo2(() => /* @__PURE__ */ new Map(), []);
|
|
1174
|
+
}
|
|
1175
|
+
var UNTOUCHED = /* @__PURE__ */ new Set(["pre", "code", "svg", "math", "script", "style"]);
|
|
1176
|
+
var STYLE = `piloto-chunk ${CHUNK_MS}ms linear both`;
|
|
1177
|
+
function chunksPlugin(state) {
|
|
1178
|
+
return () => (tree) => {
|
|
1179
|
+
let total = 0;
|
|
1180
|
+
const count = (n) => {
|
|
1181
|
+
if (n.type === "text") total += n.value?.length ?? 0;
|
|
1182
|
+
else if (n.type === "element" && UNTOUCHED.has(n.tagName ?? "")) return;
|
|
1183
|
+
n.children?.forEach(count);
|
|
1184
|
+
};
|
|
1185
|
+
count(tree);
|
|
1186
|
+
const now = performance.now();
|
|
1187
|
+
if (total < state.total) state.chunks = state.chunks.filter((x) => x.start < total);
|
|
1188
|
+
if (total > state.total) state.chunks.push({ start: state.total, t: now });
|
|
1189
|
+
state.total = total;
|
|
1190
|
+
const chunks = state.chunks;
|
|
1191
|
+
if (!chunks.length) return;
|
|
1192
|
+
let pos = 0;
|
|
1193
|
+
const split = (n) => {
|
|
1194
|
+
if (!n.children) return;
|
|
1195
|
+
if (n.type === "element" && UNTOUCHED.has(n.tagName ?? "")) return;
|
|
1196
|
+
const fresh = [];
|
|
1197
|
+
for (const h of n.children) {
|
|
1198
|
+
if (h.type !== "text" || !h.value) {
|
|
1199
|
+
split(h);
|
|
1200
|
+
fresh.push(h);
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
const start = pos;
|
|
1204
|
+
const end = pos + h.value.length;
|
|
1205
|
+
pos = end;
|
|
1206
|
+
let k = 0;
|
|
1207
|
+
while (k < chunks.length - 1 && chunks[k + 1].start <= start) k++;
|
|
1208
|
+
let a = start;
|
|
1209
|
+
while (a < end) {
|
|
1210
|
+
const b = Math.min(end, k + 1 < chunks.length ? chunks[k + 1].start : end);
|
|
1211
|
+
const young = now - chunks[k].t < CHUNK_MS;
|
|
1212
|
+
fresh.push({
|
|
1213
|
+
type: "element",
|
|
1214
|
+
tagName: "span",
|
|
1215
|
+
// Viejo (o recreado tras 400 ms) = sin animación: nunca vuelve a aparecer.
|
|
1216
|
+
properties: young ? { style: `animation:${STYLE}` } : {},
|
|
1217
|
+
children: [{ type: "text", value: h.value.slice(a - start, b - start) }]
|
|
1218
|
+
});
|
|
1219
|
+
a = b;
|
|
1220
|
+
k++;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
n.children = fresh;
|
|
1224
|
+
};
|
|
1225
|
+
split(tree);
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
var ChunkedBlock = memo(function ChunkedBlock2(props) {
|
|
1229
|
+
const ctx = useContext3(ChunksContext);
|
|
1230
|
+
const key = `${ctx?.part ?? 0}-${props.index}`;
|
|
1231
|
+
const plugins = useMemo2(() => {
|
|
1232
|
+
if (!ctx) return props.rehypePlugins;
|
|
1233
|
+
let state = ctx.registry.get(key);
|
|
1234
|
+
if (!state) {
|
|
1235
|
+
state = { chunks: [], total: 0 };
|
|
1236
|
+
ctx.registry.set(key, state);
|
|
1237
|
+
}
|
|
1238
|
+
return [...props.rehypePlugins ?? [], chunksPlugin(state)];
|
|
1239
|
+
}, [props.content, props.rehypePlugins, key]);
|
|
1240
|
+
return /* @__PURE__ */ jsx2(Block, { ...props, rehypePlugins: plugins });
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
// src/react/Markdown.tsx
|
|
1244
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1245
|
+
function LinkSafetyModal({ isOpen, onClose, onConfirm, url }) {
|
|
1246
|
+
const { strings: t, theme, skin } = usePiloto();
|
|
1247
|
+
const [copied, setCopied] = useState3(false);
|
|
1248
|
+
if (!isOpen) return null;
|
|
1249
|
+
const copy = async () => {
|
|
1250
|
+
await navigator.clipboard?.writeText(url).catch(() => {
|
|
1251
|
+
});
|
|
1252
|
+
setCopied(true);
|
|
1253
|
+
setTimeout(() => setCopied(false), 1500);
|
|
1254
|
+
};
|
|
1255
|
+
const open = () => {
|
|
1256
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
1257
|
+
onConfirm();
|
|
1258
|
+
};
|
|
1259
|
+
return /* @__PURE__ */ jsx3("div", { ...rootProps(theme, skin), style: { display: "contents" }, children: /* @__PURE__ */ jsx3("div", { className: "piloto-overlay fixed inset-0 z-[1000] flex items-center justify-center bg-black/60 px-4", onClick: onClose, role: "presentation", children: /* @__PURE__ */ jsxs2(
|
|
1260
|
+
"div",
|
|
1261
|
+
{
|
|
1262
|
+
className: "piloto-dialog w-full max-w-md rounded-(--piloto-radius-card) border border-border-primary bg-background-primary p-6 text-text-primary shadow-(--piloto-shadow-card)",
|
|
1263
|
+
onClick: (e) => e.stopPropagation(),
|
|
1264
|
+
role: "dialog",
|
|
1265
|
+
"aria-modal": "true",
|
|
1266
|
+
"aria-label": t.linkTitle,
|
|
1267
|
+
children: [
|
|
1268
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between gap-4", children: [
|
|
1269
|
+
/* @__PURE__ */ jsxs2("h2", { className: "flex items-center gap-2 text-base font-semibold", children: [
|
|
1270
|
+
/* @__PURE__ */ jsx3(ExternalLink, { className: "h-4 w-4 text-text-secondary" }),
|
|
1271
|
+
t.linkTitle
|
|
1272
|
+
] }),
|
|
1273
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: onClose, "aria-label": t.close, className: "text-text-secondary transition hover:text-text-primary", children: /* @__PURE__ */ jsx3(X, { className: "h-4 w-4" }) })
|
|
1274
|
+
] }),
|
|
1275
|
+
/* @__PURE__ */ jsx3("p", { className: "mt-4 break-all rounded-(--piloto-radius-input) border border-border-primary bg-background-secondary px-3 py-2.5 font-mono text-xs", children: url }),
|
|
1276
|
+
/* @__PURE__ */ jsxs2("div", { className: "mt-5 flex gap-2", children: [
|
|
1277
|
+
/* @__PURE__ */ jsx3(
|
|
1278
|
+
"button",
|
|
1279
|
+
{
|
|
1280
|
+
type: "button",
|
|
1281
|
+
onClick: copy,
|
|
1282
|
+
className: "flex flex-1 items-center justify-center gap-2 rounded-(--piloto-radius-button) border border-border-primary bg-background-secondary px-4 py-2 text-sm transition hover:border-text-tertiary",
|
|
1283
|
+
children: copied ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1284
|
+
/* @__PURE__ */ jsx3(Check, { className: "h-4 w-4 text-text-success" }),
|
|
1285
|
+
t.linkCopied
|
|
1286
|
+
] }) : t.linkCopy
|
|
1287
|
+
}
|
|
1288
|
+
),
|
|
1289
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: open, className: "piloto-primary flex-1 rounded-(--piloto-radius-button) px-4 py-2 text-sm transition hover:opacity-90", children: t.linkOpen })
|
|
1290
|
+
] })
|
|
1291
|
+
]
|
|
1292
|
+
}
|
|
1293
|
+
) }) });
|
|
1294
|
+
}
|
|
1295
|
+
var linkSafety = { enabled: true, renderModal: (p) => /* @__PURE__ */ jsx3(LinkSafetyModal, { ...p }) };
|
|
1296
|
+
function Markdown({ children, animate = false, className }) {
|
|
1297
|
+
const { markdown } = usePiloto();
|
|
1298
|
+
const registry = useChunkRegistry();
|
|
1299
|
+
const text = markdown.transform ? markdown.transform(children) : children;
|
|
1300
|
+
const onClick = markdown.onLinkClick ? (e) => {
|
|
1301
|
+
const a = e.target.closest("a[href]");
|
|
1302
|
+
if (!a) return;
|
|
1303
|
+
if (markdown.onLinkClick(a.getAttribute("href") ?? "", e.nativeEvent)) {
|
|
1304
|
+
e.preventDefault();
|
|
1305
|
+
e.stopPropagation();
|
|
1306
|
+
}
|
|
1307
|
+
} : void 0;
|
|
1308
|
+
return (
|
|
1309
|
+
// `min-w-0 break-words` y el scroll interno en pre/tablas: una URL larga no ensancha el chat.
|
|
1310
|
+
/* @__PURE__ */ jsx3("div", { onClickCapture: onClick, className: `piloto-md min-w-0 max-w-full break-words ${className ?? ""}`, 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 }) })
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// src/react/Steps.tsx
|
|
1315
|
+
import { useState as useState4 } from "react";
|
|
1316
|
+
|
|
1317
|
+
// src/react/InlineMarkdown.tsx
|
|
1318
|
+
import { Fragment as Fragment2 } from "react";
|
|
1319
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1320
|
+
var TOKEN = /(`[^`\n]+`)|(\*\*[^*\n]+\*\*|__[^_\n]+__)|(\*[^*\s][^*\n]*\*|_[^_\s][^_\n]*_)|(\[[^\]\n]+\]\(https?:\/\/[^)\s]+\))/g;
|
|
1321
|
+
function renderInline(text) {
|
|
1322
|
+
const out = [];
|
|
1323
|
+
let last = 0;
|
|
1324
|
+
let k = 0;
|
|
1325
|
+
for (const m of text.matchAll(TOKEN)) {
|
|
1326
|
+
const at = m.index ?? 0;
|
|
1327
|
+
if (at > last) out.push(text.slice(last, at));
|
|
1328
|
+
const t = m[0];
|
|
1329
|
+
if (m[1]) out.push(/* @__PURE__ */ jsx4("code", { children: t.slice(1, -1) }, k++));
|
|
1330
|
+
else if (m[2]) out.push(/* @__PURE__ */ jsx4("strong", { children: renderInline(t.slice(2, -2)) }, k++));
|
|
1331
|
+
else if (m[3]) out.push(/* @__PURE__ */ jsx4("em", { children: renderInline(t.slice(1, -1)) }, k++));
|
|
1332
|
+
else {
|
|
1333
|
+
const [, label, href] = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(t) ?? [];
|
|
1334
|
+
out.push(
|
|
1335
|
+
/* @__PURE__ */ jsx4("a", { href, target: "_blank", rel: "noopener noreferrer", children: label }, k++)
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
last = at + t.length;
|
|
1339
|
+
}
|
|
1340
|
+
if (last < text.length) out.push(text.slice(last));
|
|
1341
|
+
return out;
|
|
1342
|
+
}
|
|
1343
|
+
function InlineMarkdown({ children }) {
|
|
1344
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: renderInline(children) });
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// src/react/Steps.tsx
|
|
1348
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1349
|
+
function ClawIcon({ className }) {
|
|
1350
|
+
return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", className: `piloto-claw ${className ?? ""}`, "aria-hidden": "true", children: /* @__PURE__ */ jsxs3("g", { className: "piloto-claw-rig", children: [
|
|
1351
|
+
/* @__PURE__ */ jsx5("path", { d: "M12 2v4" }),
|
|
1352
|
+
/* @__PURE__ */ jsx5("rect", { x: "8.5", y: "6", width: "7", height: "3.5", rx: "1" }),
|
|
1353
|
+
/* @__PURE__ */ jsx5("g", { className: "piloto-claw-l", children: /* @__PURE__ */ jsx5("path", { d: "M9.5 9.5 6 14l2 4.5" }) }),
|
|
1354
|
+
/* @__PURE__ */ jsx5("g", { className: "piloto-claw-r", children: /* @__PURE__ */ jsx5("path", { d: "M14.5 9.5 18 14l-2 4.5" }) }),
|
|
1355
|
+
/* @__PURE__ */ jsx5("path", { d: "M12 9.5v4" })
|
|
1356
|
+
] }) });
|
|
1357
|
+
}
|
|
1358
|
+
var DEFAULT_TOOL_ICONS = {
|
|
1359
|
+
tool_search: Search,
|
|
1360
|
+
bash: SquareTerminal,
|
|
1361
|
+
shell: SquareTerminal,
|
|
1362
|
+
terminal: SquareTerminal,
|
|
1363
|
+
execute: SquareTerminal,
|
|
1364
|
+
code: SquareTerminal,
|
|
1365
|
+
script: SquareTerminal,
|
|
1366
|
+
read: FileText,
|
|
1367
|
+
read_file: FileText,
|
|
1368
|
+
write: FilePenLine,
|
|
1369
|
+
write_file: FilePenLine,
|
|
1370
|
+
edit: FilePenLine,
|
|
1371
|
+
edit_file: FilePenLine,
|
|
1372
|
+
grep: Search,
|
|
1373
|
+
glob: Search,
|
|
1374
|
+
search: Search,
|
|
1375
|
+
web_search: Globe,
|
|
1376
|
+
web_fetch: Globe,
|
|
1377
|
+
fetch: Globe,
|
|
1378
|
+
browser: Globe,
|
|
1379
|
+
site_publish: Globe,
|
|
1380
|
+
pdf: FileText,
|
|
1381
|
+
todo: ListChecks,
|
|
1382
|
+
list: ListChecks,
|
|
1383
|
+
folder: Folder,
|
|
1384
|
+
image: Image,
|
|
1385
|
+
delegate: UsersRound,
|
|
1386
|
+
guide: BookOpen,
|
|
1387
|
+
deliver: Send,
|
|
1388
|
+
sheet: FileSpreadsheet,
|
|
1389
|
+
view: AppWindow
|
|
1390
|
+
};
|
|
1391
|
+
function iconFor(t, icons) {
|
|
1392
|
+
const name = toolName(t);
|
|
1393
|
+
return name && icons[name] || t.kind && (icons[t.kind] ?? DEFAULT_TOOL_ICONS[t.kind]) || ClawIcon;
|
|
1394
|
+
}
|
|
1395
|
+
var VISIBLE = 5;
|
|
1396
|
+
function AgentSteps({ tools, live }) {
|
|
1397
|
+
const { strings: t, toolVerbs } = usePiloto();
|
|
1398
|
+
const [open, setOpen] = useState4(false);
|
|
1399
|
+
const label = (x) => stepLabel(x, toolVerbs, t.step);
|
|
1400
|
+
const failed = tools.filter((x) => x.status === "failed").length;
|
|
1401
|
+
const groups = groupSteps(tools, label);
|
|
1402
|
+
const shown = groups.slice(-VISIBLE);
|
|
1403
|
+
const hidden = groups.length - shown.length;
|
|
1404
|
+
return /* @__PURE__ */ jsxs3("div", { className: "piloto-steps mb-3 rounded-(--piloto-radius-card) px-3 py-2.5 transition-[background-color,border-color] duration-300", "data-live": live || void 0, children: [
|
|
1405
|
+
/* @__PURE__ */ jsx5("button", { type: "button", onClick: () => setOpen((v) => !v), "aria-expanded": open, title: open ? t.stepsCollapse : t.stepsExpand, className: "block w-full cursor-pointer text-left", children: open ? /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1.5 text-xs font-medium text-text-primary", children: [
|
|
1406
|
+
/* @__PURE__ */ jsx5(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 rotate-90 text-text-tertiary" }),
|
|
1407
|
+
/* @__PURE__ */ jsxs3("span", { className: "truncate", children: [
|
|
1408
|
+
summarizeTools(tools, toolVerbs),
|
|
1409
|
+
failed > 0 && /* @__PURE__ */ jsxs3("span", { className: "text-text-danger", children: [
|
|
1410
|
+
", ",
|
|
1411
|
+
t.withProblems(failed)
|
|
1412
|
+
] })
|
|
1413
|
+
] })
|
|
1414
|
+
] }) : /* @__PURE__ */ jsxs3("ul", { className: "flex flex-col gap-1.5", children: [
|
|
1415
|
+
hidden > 0 && /* @__PURE__ */ jsx5("li", { className: "text-[11px] font-medium text-text-tertiary", children: t.earlierSteps(hidden) }),
|
|
1416
|
+
shown.map((g) => /* @__PURE__ */ jsx5(StepLine, { tool: g.tool, times: g.times, live }, g.tool.id))
|
|
1417
|
+
] }) }),
|
|
1418
|
+
open && /* @__PURE__ */ jsx5("ul", { className: "mt-2 flex flex-col gap-1", children: tools.map((x) => /* @__PURE__ */ jsx5(ToolRow, { tool: x }, x.id)) })
|
|
1419
|
+
] });
|
|
1420
|
+
}
|
|
1421
|
+
function StepLine({ tool, times, live }) {
|
|
1422
|
+
const { strings: t, toolVerbs, toolIcons } = usePiloto();
|
|
1423
|
+
const Icon2 = iconFor(tool, toolIcons);
|
|
1424
|
+
const spins = isRunning(tool) && live;
|
|
1425
|
+
const label = stepLabel(tool, toolVerbs, t.step);
|
|
1426
|
+
return /* @__PURE__ */ jsxs3("li", { className: "piloto-step flex min-w-0 items-center gap-2 text-[13px] leading-6 text-text-secondary", children: [
|
|
1427
|
+
/* @__PURE__ */ jsx5("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", "aria-label": spins ? t.running : tool.status === "failed" ? t.failed : t.done, children: spins ? /* @__PURE__ */ jsx5(LoaderCircle, { className: "h-4 w-4 animate-spin text-text-info" }) : tool.status === "failed" ? /* @__PURE__ */ jsx5(CircleX, { className: "h-4 w-4 text-text-danger" }) : /* @__PURE__ */ jsx5(CircleCheck, { className: "h-4 w-4 text-text-success" }) }),
|
|
1428
|
+
/* @__PURE__ */ jsx5(Icon2, { className: "h-[22px] w-[22px] shrink-0 text-text-secondary" }),
|
|
1429
|
+
/* @__PURE__ */ jsxs3("span", { className: "min-w-0 truncate", children: [
|
|
1430
|
+
/* @__PURE__ */ jsx5("span", { className: spins ? "text-text-primary" : void 0, children: label }),
|
|
1431
|
+
tool.detail && /* @__PURE__ */ jsx5("span", { className: "piloto-step-detail", children: tool.detail }),
|
|
1432
|
+
times > 1 && /* @__PURE__ */ jsxs3("span", { className: "ml-1 tabular-nums text-text-tertiary", children: [
|
|
1433
|
+
"\xD7",
|
|
1434
|
+
times
|
|
1435
|
+
] })
|
|
1436
|
+
] })
|
|
1437
|
+
] });
|
|
1438
|
+
}
|
|
1439
|
+
var STATUS = {
|
|
1440
|
+
pending: { Icon: Clock, className: "text-text-tertiary", key: "step" },
|
|
1441
|
+
in_progress: { Icon: LoaderCircle, className: "animate-spin text-text-info", key: "running" },
|
|
1442
|
+
completed: { Icon: CircleCheck, className: "text-text-success", key: "done" },
|
|
1443
|
+
failed: { Icon: CircleX, className: "text-text-danger", key: "failed" }
|
|
1444
|
+
};
|
|
1445
|
+
function ToolRow({ tool }) {
|
|
1446
|
+
const { strings: t, toolVerbs, toolIcons } = usePiloto();
|
|
1447
|
+
const s = STATUS[tool.status ?? "pending"] ?? STATUS.pending;
|
|
1448
|
+
const KindIcon = iconFor(tool, toolIcons);
|
|
1449
|
+
return /* @__PURE__ */ jsxs3("li", { className: "piloto-tool-row flex items-start gap-2.5 rounded-(--piloto-radius-input) border border-border-secondary bg-background-secondary/50 px-3 py-2 transition-colors hover:border-border-primary hover:bg-background-secondary", children: [
|
|
1450
|
+
/* @__PURE__ */ jsx5("span", { className: "mt-px flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-background-primary text-text-secondary shadow-sm", children: /* @__PURE__ */ jsx5(KindIcon, { className: "h-3.5 w-3.5" }) }),
|
|
1451
|
+
/* @__PURE__ */ jsxs3("span", { className: "min-w-0 flex-1", children: [
|
|
1452
|
+
/* @__PURE__ */ jsx5("span", { className: "block min-w-0 truncate text-xs font-medium text-text-primary", children: stepLabel(tool, toolVerbs, tool.id) }),
|
|
1453
|
+
tool.detail && /* @__PURE__ */ jsx5("span", { className: "mt-0.5 block truncate text-[11px] text-text-tertiary", children: tool.detail }),
|
|
1454
|
+
tool.path && /* @__PURE__ */ jsx5("span", { className: "mt-0.5 block truncate font-mono text-[10px] text-text-tertiary", children: tool.path })
|
|
1455
|
+
] }),
|
|
1456
|
+
/* @__PURE__ */ jsx5("span", { className: "mt-0.5 shrink-0", title: t[s.key], "aria-label": t[s.key], children: /* @__PURE__ */ jsx5(s.Icon, { className: `h-3.5 w-3.5 ${s.className}` }) })
|
|
1457
|
+
] });
|
|
1458
|
+
}
|
|
1459
|
+
function Narration({ steps, children }) {
|
|
1460
|
+
return /* @__PURE__ */ jsxs3("ul", { className: "piloto-narration mb-3 flex flex-col gap-1.5", children: [
|
|
1461
|
+
steps.map((p, i) => /* @__PURE__ */ jsxs3("li", { className: "piloto-fade-up flex items-start gap-2 text-text-secondary", children: [
|
|
1462
|
+
/* @__PURE__ */ jsx5("span", { className: "mt-[3px] grid size-4 shrink-0 place-items-center rounded-full bg-brand-500/15 text-brand-600", children: /* @__PURE__ */ jsx5(CheckMini, {}) }),
|
|
1463
|
+
/* @__PURE__ */ jsx5("span", { className: "piloto-narration-text min-w-0", children: /* @__PURE__ */ jsx5(InlineMarkdown, { children: p }) })
|
|
1464
|
+
] }, i)),
|
|
1465
|
+
children
|
|
1466
|
+
] });
|
|
1467
|
+
}
|
|
1468
|
+
function CheckMini() {
|
|
1469
|
+
return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 24 24", width: "11", height: "11", fill: "none", stroke: "currentColor", strokeWidth: 3, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("path", { d: "M20 6 9 17l-5-5" }) });
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// src/react/Bubble.tsx
|
|
1473
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1474
|
+
async function copyText(text) {
|
|
1475
|
+
try {
|
|
1476
|
+
await navigator.clipboard.writeText(text);
|
|
1477
|
+
return true;
|
|
1478
|
+
} catch {
|
|
1479
|
+
return false;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
function UserBubble({ turn }) {
|
|
1483
|
+
const { strings: t, formatUserText } = usePiloto();
|
|
1484
|
+
const note = systemNoteOf(turn.text);
|
|
1485
|
+
if (note) {
|
|
1486
|
+
return /* @__PURE__ */ jsx6("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxs4("span", { className: "piloto-system-note max-w-[80%] truncate rounded-full bg-background-secondary px-3 py-1 text-[11px] text-text-tertiary", children: [
|
|
1487
|
+
"\u23F0 ",
|
|
1488
|
+
note
|
|
1489
|
+
] }) });
|
|
1490
|
+
}
|
|
1491
|
+
const visible = formatUserText(turn.text);
|
|
1492
|
+
const atts = turn.attachments ?? [];
|
|
1493
|
+
const inBubble = atts.filter((a) => a.mimeType.startsWith("image/") && (a.data || a.uri) || a.mimeType.startsWith("audio/") && a.uri);
|
|
1494
|
+
const docs = atts.filter((a) => !inBubble.includes(a));
|
|
1495
|
+
return /* @__PURE__ */ jsxs4("div", { className: "piloto-user group/msg flex flex-col items-end gap-1", children: [
|
|
1496
|
+
turn.steer && /* @__PURE__ */ jsxs4("span", { className: "text-[11px] text-text-tertiary", children: [
|
|
1497
|
+
"\u21AA ",
|
|
1498
|
+
t.steered
|
|
1499
|
+
] }),
|
|
1500
|
+
docs.length > 0 && /* @__PURE__ */ jsx6("div", { className: "mb-1 flex max-w-[75%] flex-wrap justify-end gap-2", children: docs.map((a, i) => /* @__PURE__ */ jsxs4("span", { className: "piloto-attachment-card inline-flex max-w-[16rem] items-center gap-1.5 rounded-(--piloto-radius-input) border border-border-primary bg-background-primary px-2.5 py-1.5 text-xs text-text-secondary", children: [
|
|
1501
|
+
/* @__PURE__ */ jsx6(FileText, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
1502
|
+
a.uri ? /* @__PURE__ */ jsx6("a", { href: a.uri, target: "_blank", rel: "noopener noreferrer", className: "truncate hover:underline", children: a.name ?? a.mimeType }) : /* @__PURE__ */ jsx6("span", { className: "truncate", children: a.name ?? a.mimeType })
|
|
1503
|
+
] }, i)) }),
|
|
1504
|
+
(inBubble.length > 0 || visible.trim()) && /* @__PURE__ */ jsxs4("div", { className: "piloto-bubble-user max-w-[75%] whitespace-pre-wrap break-words rounded-(--piloto-radius-bubble) px-4 py-3 text-[15px] leading-[22px]", children: [
|
|
1505
|
+
inBubble.length > 0 && /* @__PURE__ */ jsx6("div", { className: `flex flex-wrap justify-end gap-1.5 ${visible.trim() ? "mb-2" : ""}`, children: inBubble.map(
|
|
1506
|
+
(a, i) => a.mimeType.startsWith("image/") ? /* @__PURE__ */ jsx6("img", { src: a.data ? `data:${a.mimeType};base64,${a.data}` : a.uri, alt: "", className: "h-20 w-20 rounded-lg object-cover" }, i) : /* @__PURE__ */ jsx6("audio", { src: a.uri, controls: true, preload: "metadata", className: "h-9 max-w-[16rem]" }, i)
|
|
1507
|
+
) }),
|
|
1508
|
+
visible
|
|
1509
|
+
] }),
|
|
1510
|
+
visible.trim() && /* @__PURE__ */ jsx6(CopyUserMessage, { text: visible })
|
|
1511
|
+
] });
|
|
1512
|
+
}
|
|
1513
|
+
function CopyUserMessage({ text }) {
|
|
1514
|
+
const { strings: t } = usePiloto();
|
|
1515
|
+
const [copied, setCopied] = useState5(false);
|
|
1516
|
+
return /* @__PURE__ */ jsx6(
|
|
1517
|
+
"button",
|
|
1518
|
+
{
|
|
1519
|
+
type: "button",
|
|
1520
|
+
onClick: async () => {
|
|
1521
|
+
if (await copyText(text)) {
|
|
1522
|
+
setCopied(true);
|
|
1523
|
+
setTimeout(() => setCopied(false), 1500);
|
|
1524
|
+
}
|
|
1525
|
+
},
|
|
1526
|
+
"aria-label": copied ? t.copied : t.copyMessage,
|
|
1527
|
+
title: copied ? t.copied : t.copy,
|
|
1528
|
+
className: `piloto-copy-user grid h-7 w-7 place-items-center rounded-md text-text-tertiary transition-opacity hover:bg-background-secondary hover:text-text-primary focus-visible:opacity-100 [@media(hover:none)]:opacity-100 ${copied ? "opacity-100" : "opacity-0 group-hover/msg:opacity-100"}`,
|
|
1529
|
+
children: copied ? /* @__PURE__ */ jsx6(Check, { className: "h-3.5 w-3.5 text-text-success" }) : /* @__PURE__ */ jsx6(Copy, { className: "h-3.5 w-3.5" })
|
|
1530
|
+
}
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
function AnswerText({ text, live, whole = false }) {
|
|
1534
|
+
const { visible, animate } = useTextAtPace(text, live, whole);
|
|
1535
|
+
if (!visible) return null;
|
|
1536
|
+
return /* @__PURE__ */ jsx6(Markdown, { animate, children: visible });
|
|
1537
|
+
}
|
|
1538
|
+
function ReplyOptionButtons({ options, onPick }) {
|
|
1539
|
+
const { strings: t } = usePiloto();
|
|
1540
|
+
return /* @__PURE__ */ jsx6("ul", { className: "piloto-options mt-3 flex flex-col gap-2", "aria-label": t.replyOptions, children: options.items.map((item, i) => {
|
|
1541
|
+
const clean = cleanReplyOption(item);
|
|
1542
|
+
return /* @__PURE__ */ jsx6("li", { className: "piloto-chunk-in", children: /* @__PURE__ */ jsxs4(
|
|
1543
|
+
"button",
|
|
1544
|
+
{
|
|
1545
|
+
type: "button",
|
|
1546
|
+
onClick: () => onPick(clean),
|
|
1547
|
+
className: "piloto-option group flex w-full cursor-pointer items-center gap-3 rounded-(--piloto-radius-input) border border-border-primary bg-background-primary px-4 py-2.5 text-left text-sm text-text-primary transition-colors hover:border-brand-300 hover:bg-background-secondary",
|
|
1548
|
+
children: [
|
|
1549
|
+
options.numbered && /* @__PURE__ */ jsxs4("span", { className: "shrink-0 tabular-nums text-text-tertiary", children: [
|
|
1550
|
+
i + 1,
|
|
1551
|
+
"."
|
|
1552
|
+
] }),
|
|
1553
|
+
/* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1", children: clean }),
|
|
1554
|
+
/* @__PURE__ */ jsx6(ArrowUpRight, { className: "h-4 w-4 shrink-0 text-text-tertiary transition-colors group-hover:text-text-primary" })
|
|
1555
|
+
]
|
|
1556
|
+
}
|
|
1557
|
+
) }, i);
|
|
1558
|
+
}) });
|
|
1559
|
+
}
|
|
1560
|
+
function UsageStats({ used, size, cost }) {
|
|
1561
|
+
const { strings: t } = usePiloto();
|
|
1562
|
+
if (!used && !cost) return null;
|
|
1563
|
+
const pct = size > 0 ? Math.min(100, Math.round(used / size * 100)) : 0;
|
|
1564
|
+
return /* @__PURE__ */ jsxs4("div", { className: "piloto-usage mt-2 flex items-center gap-4 text-[11px] text-text-tertiary", children: [
|
|
1565
|
+
/* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1", children: [
|
|
1566
|
+
/* @__PURE__ */ jsx6(Gauge, { className: "h-3 w-3" }),
|
|
1567
|
+
used.toLocaleString(t.numberLocale),
|
|
1568
|
+
size > 0 && ` / ${size.toLocaleString(t.numberLocale)} (${pct}%)`
|
|
1569
|
+
] }),
|
|
1570
|
+
cost > 0 && /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-1", children: [
|
|
1571
|
+
/* @__PURE__ */ jsx6(Coins, { className: "h-3 w-3" }),
|
|
1572
|
+
cost.toLocaleString(t.numberLocale, { style: "currency", currency: "USD", maximumFractionDigits: 4 })
|
|
1573
|
+
] })
|
|
1574
|
+
] });
|
|
1575
|
+
}
|
|
1576
|
+
function TurnFooter({ text, seconds, onRetry, showCopy = true }) {
|
|
1577
|
+
const { strings: t } = usePiloto();
|
|
1578
|
+
const [copied, setCopied] = useState5(false);
|
|
1579
|
+
return /* @__PURE__ */ jsxs4("div", { className: "piloto-footer mt-2 flex items-center gap-3 text-xs text-text-tertiary", children: [
|
|
1580
|
+
seconds ? /* @__PURE__ */ jsx6("span", { children: t.worked(seconds) }) : null,
|
|
1581
|
+
showCopy && /* @__PURE__ */ jsxs4(
|
|
1582
|
+
"button",
|
|
1583
|
+
{
|
|
1584
|
+
type: "button",
|
|
1585
|
+
onClick: async () => {
|
|
1586
|
+
if (await copyText(text)) {
|
|
1587
|
+
setCopied(true);
|
|
1588
|
+
setTimeout(() => setCopied(false), 1500);
|
|
1589
|
+
}
|
|
1590
|
+
},
|
|
1591
|
+
className: "flex items-center gap-1 transition-colors hover:text-text-primary",
|
|
1592
|
+
children: [
|
|
1593
|
+
copied ? /* @__PURE__ */ jsx6(Check, { className: "h-3.5 w-3.5 text-text-success" }) : /* @__PURE__ */ jsx6(Copy, { className: "h-3.5 w-3.5" }),
|
|
1594
|
+
copied ? t.copied : t.copy
|
|
1595
|
+
]
|
|
1596
|
+
}
|
|
1597
|
+
),
|
|
1598
|
+
onRetry && /* @__PURE__ */ jsxs4("button", { type: "button", onClick: onRetry, className: "flex items-center gap-1 transition-colors hover:text-text-primary", children: [
|
|
1599
|
+
/* @__PURE__ */ jsx6(RotateCcw, { className: "h-3.5 w-3.5" }),
|
|
1600
|
+
t.retry
|
|
1601
|
+
] })
|
|
1602
|
+
] });
|
|
1603
|
+
}
|
|
1604
|
+
function DeliveryChips({ deliveries }) {
|
|
1605
|
+
const shown = deliveries.filter((d) => !d.capture);
|
|
1606
|
+
if (!shown.length) return null;
|
|
1607
|
+
return /* @__PURE__ */ jsx6("div", { className: "mt-2 flex flex-wrap gap-1.5", children: shown.map((d, i) => /* @__PURE__ */ jsxs4("span", { className: "piloto-delivery inline-flex max-w-[16rem] items-center gap-1.5 rounded-(--piloto-radius-input) border border-border-primary px-2 py-1 text-xs text-text-secondary", children: [
|
|
1608
|
+
/* @__PURE__ */ jsx6(FileText, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
1609
|
+
/* @__PURE__ */ jsx6("span", { className: "truncate", children: d.name })
|
|
1610
|
+
] }, i)) });
|
|
1611
|
+
}
|
|
1612
|
+
function AgentBubble({ turn, live, onRetry, onOption, showUsage = true, showCopy = true, renderDeliveries }) {
|
|
1613
|
+
const { strings: t } = usePiloto();
|
|
1614
|
+
const narrated = splitNarration(turn.text ?? "", turn.cuts);
|
|
1615
|
+
const visible = narrated.rest;
|
|
1616
|
+
const options = onOption && !live && visible ? extractReplyOptions(visible) : null;
|
|
1617
|
+
return /* @__PURE__ */ jsxs4("div", { className: "piloto-agent min-w-0 max-w-[92%]", children: [
|
|
1618
|
+
turn.thought && /* @__PURE__ */ jsxs4("details", { className: "piloto-thought mb-3 text-xs text-text-secondary", children: [
|
|
1619
|
+
/* @__PURE__ */ jsx6("summary", { className: "cursor-pointer select-none", children: t.thinking }),
|
|
1620
|
+
/* @__PURE__ */ jsx6("p", { className: "mt-2 whitespace-pre-wrap border-l-2 border-border-secondary pl-3", children: turn.thought })
|
|
1621
|
+
] }),
|
|
1622
|
+
turn.tools && turn.tools.length > 0 && /* @__PURE__ */ jsx6(AgentSteps, { tools: turn.tools, live }),
|
|
1623
|
+
narrated.steps.length > 0 && /* @__PURE__ */ jsx6(Narration, { steps: narrated.steps }),
|
|
1624
|
+
options ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1625
|
+
options.before && /* @__PURE__ */ jsx6(AnswerText, { text: options.before, live }),
|
|
1626
|
+
/* @__PURE__ */ jsx6(ReplyOptionButtons, { options, onPick: onOption })
|
|
1627
|
+
] }) : (
|
|
1628
|
+
// Cada tramo nuevo tras una herramienta monta su propio ritmo (empieza de cero). Lo que llegó
|
|
1629
|
+
// entero fuera de un turno se escribe al mismo ritmo; al recargar ya sale completo.
|
|
1630
|
+
visible && /* @__PURE__ */ jsx6(AnswerText, { text: visible, live: live || !!turn.arrived, whole: !!turn.arrived }, `r${narrated.steps.length}`)
|
|
1631
|
+
),
|
|
1632
|
+
renderDeliveries ? renderDeliveries(turn, live) : turn.deliveries?.length ? /* @__PURE__ */ jsx6(DeliveryChips, { deliveries: turn.deliveries }) : null,
|
|
1633
|
+
turn.decisions?.length ? /* @__PURE__ */ jsx6(DecisionLines, { decisions: turn.decisions }) : null,
|
|
1634
|
+
showUsage && turn.usage && /* @__PURE__ */ jsx6(UsageStats, { ...turn.usage }),
|
|
1635
|
+
turn.text && !live && /* @__PURE__ */ jsx6(TurnFooter, { text: turn.text, seconds: turn.seconds, onRetry, showCopy })
|
|
1636
|
+
] });
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// src/react/Chips.tsx
|
|
1640
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1641
|
+
function ReferenceChips({
|
|
1642
|
+
references,
|
|
1643
|
+
onRemove,
|
|
1644
|
+
onActivate
|
|
1645
|
+
}) {
|
|
1646
|
+
const { strings: t } = usePiloto();
|
|
1647
|
+
if (!references.length) return null;
|
|
1648
|
+
return /* @__PURE__ */ jsx7("div", { className: "piloto-references flex flex-wrap gap-1.5", children: references.map((r) => /* @__PURE__ */ jsxs5("span", { className: "piloto-reference inline-flex max-w-full items-center gap-1.5 rounded-full border py-1 pl-2.5 pr-1 text-xs text-text-primary", children: [
|
|
1649
|
+
/* @__PURE__ */ jsx7(AtSign, { className: "h-3 w-3 shrink-0 text-brand-600" }),
|
|
1650
|
+
/* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onActivate?.(r), className: "shrink-0 font-semibold underline-offset-2 hover:underline", children: r.label }),
|
|
1651
|
+
r.hint && /* @__PURE__ */ jsxs5("span", { className: "truncate text-text-secondary", children: [
|
|
1652
|
+
"\xAB",
|
|
1653
|
+
r.hint,
|
|
1654
|
+
"\xBB"
|
|
1655
|
+
] }),
|
|
1656
|
+
/* @__PURE__ */ jsx7(
|
|
1657
|
+
"button",
|
|
1658
|
+
{
|
|
1659
|
+
type: "button",
|
|
1660
|
+
onClick: () => onRemove(r.id),
|
|
1661
|
+
"aria-label": t.removeReference,
|
|
1662
|
+
className: "grid size-5 shrink-0 cursor-pointer place-items-center rounded-full text-text-tertiary hover:bg-background-secondary hover:text-text-primary",
|
|
1663
|
+
children: /* @__PURE__ */ jsx7(X, { className: "h-3 w-3" })
|
|
1664
|
+
}
|
|
1665
|
+
)
|
|
1666
|
+
] }, r.id)) });
|
|
1667
|
+
}
|
|
1668
|
+
function Suggestions({ items, onPick, onPrefill }) {
|
|
1669
|
+
if (!items.length) return null;
|
|
1670
|
+
return /* @__PURE__ */ jsx7("div", { className: "piloto-suggestions mt-3 flex flex-wrap justify-center gap-2", children: items.map((s, i) => /* @__PURE__ */ jsx7(
|
|
1671
|
+
"button",
|
|
1672
|
+
{
|
|
1673
|
+
type: "button",
|
|
1674
|
+
onClick: () => onPrefill && /:\s*$/.test(s) ? onPrefill(s) : onPick(s),
|
|
1675
|
+
className: "piloto-suggestion rounded-full border border-border-primary bg-background-primary px-3.5 py-1.5 text-sm text-text-secondary transition-colors hover:border-brand-300 hover:text-text-primary",
|
|
1676
|
+
children: s
|
|
1677
|
+
},
|
|
1678
|
+
i
|
|
1679
|
+
)) });
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// src/react/Composer.tsx
|
|
1683
|
+
import { useCallback, useEffect as useEffect3, useRef as useRef3, useState as useState6 } from "react";
|
|
1684
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1685
|
+
var MAX_HEIGHT = 240;
|
|
1686
|
+
var BY_EXTENSION = {
|
|
1687
|
+
png: "image/png",
|
|
1688
|
+
jpg: "image/jpeg",
|
|
1689
|
+
jpeg: "image/jpeg",
|
|
1690
|
+
gif: "image/gif",
|
|
1691
|
+
webp: "image/webp",
|
|
1692
|
+
heic: "image/heic",
|
|
1693
|
+
pdf: "application/pdf",
|
|
1694
|
+
csv: "text/csv",
|
|
1695
|
+
txt: "text/plain",
|
|
1696
|
+
md: "text/markdown",
|
|
1697
|
+
json: "application/json"
|
|
1698
|
+
};
|
|
1699
|
+
function mimeOf(file) {
|
|
1700
|
+
if (file.type) return file.type;
|
|
1701
|
+
const ext = (file.name.match(/\.([A-Za-z0-9]+)$/)?.[1] || "").toLowerCase();
|
|
1702
|
+
return BY_EXTENSION[ext] ?? "application/octet-stream";
|
|
1703
|
+
}
|
|
1704
|
+
var isText = (mime) => mime.startsWith("text/") || mime === "application/json" || mime.endsWith("+json") || mime.endsWith("+xml");
|
|
1705
|
+
function shortSize(bytes) {
|
|
1706
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1707
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
1708
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
1709
|
+
}
|
|
1710
|
+
function readInline(file) {
|
|
1711
|
+
return new Promise((resolve, reject) => {
|
|
1712
|
+
const fr = new FileReader();
|
|
1713
|
+
fr.onload = () => {
|
|
1714
|
+
const result = String(fr.result);
|
|
1715
|
+
const comma = result.indexOf(",");
|
|
1716
|
+
resolve({ name: file.name, mimeType: mimeOf(file), data: comma === -1 ? result : result.slice(comma + 1) });
|
|
1717
|
+
};
|
|
1718
|
+
fr.onerror = () => reject(fr.error);
|
|
1719
|
+
fr.readAsDataURL(file);
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
function Composer({
|
|
1723
|
+
onSubmit,
|
|
1724
|
+
onStop,
|
|
1725
|
+
busy = false,
|
|
1726
|
+
canSteer = true,
|
|
1727
|
+
placeholder,
|
|
1728
|
+
initialValue = "",
|
|
1729
|
+
autoFocus = true,
|
|
1730
|
+
canSeeImages = true,
|
|
1731
|
+
attachments: withFiles = false,
|
|
1732
|
+
upload,
|
|
1733
|
+
transcribe,
|
|
1734
|
+
voiceNoteText,
|
|
1735
|
+
models = [],
|
|
1736
|
+
currentModel = null,
|
|
1737
|
+
onModelChange,
|
|
1738
|
+
references = [],
|
|
1739
|
+
onRemoveReference,
|
|
1740
|
+
onActivateReference,
|
|
1741
|
+
extras,
|
|
1742
|
+
maxFiles = 8,
|
|
1743
|
+
maxFileMb = 25
|
|
1744
|
+
}) {
|
|
1745
|
+
const { strings: t } = usePiloto();
|
|
1746
|
+
const [value, setValue] = useState6(initialValue);
|
|
1747
|
+
const [files, setFiles] = useState6([]);
|
|
1748
|
+
const [dragging, setDragging] = useState6(false);
|
|
1749
|
+
const [notice, setNotice] = useState6(null);
|
|
1750
|
+
const [uploading, setUploading] = useState6(false);
|
|
1751
|
+
const ref = useRef3(null);
|
|
1752
|
+
const fileRef = useRef3(null);
|
|
1753
|
+
const nextId = useRef3(0);
|
|
1754
|
+
const urls = useRef3(/* @__PURE__ */ new Set());
|
|
1755
|
+
const releaseUrl = (url) => {
|
|
1756
|
+
urls.current.delete(url);
|
|
1757
|
+
URL.revokeObjectURL(url);
|
|
1758
|
+
};
|
|
1759
|
+
useEffect3(() => {
|
|
1760
|
+
if (!autoFocus) return;
|
|
1761
|
+
const id = requestAnimationFrame(() => {
|
|
1762
|
+
const el = ref.current;
|
|
1763
|
+
if (!el) return;
|
|
1764
|
+
el.focus();
|
|
1765
|
+
el.setSelectionRange(el.value.length, el.value.length);
|
|
1766
|
+
});
|
|
1767
|
+
return () => cancelAnimationFrame(id);
|
|
1768
|
+
}, [autoFocus]);
|
|
1769
|
+
useEffect3(
|
|
1770
|
+
() => () => {
|
|
1771
|
+
for (const url of urls.current) URL.revokeObjectURL(url);
|
|
1772
|
+
urls.current.clear();
|
|
1773
|
+
},
|
|
1774
|
+
[]
|
|
1775
|
+
);
|
|
1776
|
+
useEffect3(() => {
|
|
1777
|
+
const el = ref.current;
|
|
1778
|
+
if (!el) return;
|
|
1779
|
+
if (!value) {
|
|
1780
|
+
el.style.height = "";
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
el.style.height = "0px";
|
|
1784
|
+
el.style.height = `${Math.min(el.scrollHeight, MAX_HEIGHT)}px`;
|
|
1785
|
+
}, [value]);
|
|
1786
|
+
const addFiles = useCallback(
|
|
1787
|
+
(list) => {
|
|
1788
|
+
if (busy) return;
|
|
1789
|
+
const rejected = [];
|
|
1790
|
+
setFiles((prev) => {
|
|
1791
|
+
const next = [...prev];
|
|
1792
|
+
for (const file of Array.from(list)) {
|
|
1793
|
+
const mime = mimeOf(file);
|
|
1794
|
+
if (file.size > maxFileMb * 1024 * 1024) {
|
|
1795
|
+
rejected.push(t.tooBig(file.name, maxFileMb));
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
if (next.length >= maxFiles) {
|
|
1799
|
+
rejected.push(t.tooMany(maxFiles));
|
|
1800
|
+
break;
|
|
1801
|
+
}
|
|
1802
|
+
if (mime.startsWith("image/") && !canSeeImages) {
|
|
1803
|
+
rejected.push(t.cantSeeImage(file.name));
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
if (!mime.startsWith("image/") && !isText(mime) && !upload) {
|
|
1807
|
+
rejected.push(t.cantUpload(file.name));
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
const url = URL.createObjectURL(file);
|
|
1811
|
+
urls.current.add(url);
|
|
1812
|
+
next.push({ id: ++nextId.current, file, url });
|
|
1813
|
+
}
|
|
1814
|
+
return next;
|
|
1815
|
+
});
|
|
1816
|
+
setNotice(rejected.length ? rejected.join(" \xB7 ") : null);
|
|
1817
|
+
ref.current?.focus();
|
|
1818
|
+
},
|
|
1819
|
+
[busy, canSeeImages, upload, maxFiles, maxFileMb, t]
|
|
1820
|
+
);
|
|
1821
|
+
useEffect3(() => {
|
|
1822
|
+
if (!withFiles) return;
|
|
1823
|
+
let inside = 0;
|
|
1824
|
+
const hasFiles = (e) => Array.from(e.dataTransfer?.types ?? []).includes("Files");
|
|
1825
|
+
const enter = (e) => {
|
|
1826
|
+
if (!hasFiles(e)) return;
|
|
1827
|
+
e.preventDefault();
|
|
1828
|
+
inside++;
|
|
1829
|
+
if (!busy) setDragging(true);
|
|
1830
|
+
};
|
|
1831
|
+
const over = (e) => {
|
|
1832
|
+
if (hasFiles(e)) e.preventDefault();
|
|
1833
|
+
};
|
|
1834
|
+
const leave = (e) => {
|
|
1835
|
+
if (!hasFiles(e)) return;
|
|
1836
|
+
inside = Math.max(0, inside - 1);
|
|
1837
|
+
if (inside === 0) setDragging(false);
|
|
1838
|
+
};
|
|
1839
|
+
const drop = (e) => {
|
|
1840
|
+
if (!hasFiles(e)) return;
|
|
1841
|
+
e.preventDefault();
|
|
1842
|
+
inside = 0;
|
|
1843
|
+
setDragging(false);
|
|
1844
|
+
if (e.dataTransfer?.files.length) addFiles(e.dataTransfer.files);
|
|
1845
|
+
};
|
|
1846
|
+
window.addEventListener("dragenter", enter);
|
|
1847
|
+
window.addEventListener("dragover", over);
|
|
1848
|
+
window.addEventListener("dragleave", leave);
|
|
1849
|
+
window.addEventListener("drop", drop);
|
|
1850
|
+
return () => {
|
|
1851
|
+
window.removeEventListener("dragenter", enter);
|
|
1852
|
+
window.removeEventListener("dragover", over);
|
|
1853
|
+
window.removeEventListener("dragleave", leave);
|
|
1854
|
+
window.removeEventListener("drop", drop);
|
|
1855
|
+
};
|
|
1856
|
+
}, [withFiles, busy, addFiles]);
|
|
1857
|
+
const removeFile = (id) => setFiles((prev) => {
|
|
1858
|
+
const out = prev.find((a) => a.id === id);
|
|
1859
|
+
if (out) releaseUrl(out.url);
|
|
1860
|
+
return prev.filter((a) => a.id !== id);
|
|
1861
|
+
});
|
|
1862
|
+
const submit = async () => {
|
|
1863
|
+
const body = value.trim();
|
|
1864
|
+
if (!body && files.length === 0 && references.length === 0) return;
|
|
1865
|
+
if (busy && (files.length > 0 || !canSteer)) return;
|
|
1866
|
+
if (uploading) return;
|
|
1867
|
+
let atts = [];
|
|
1868
|
+
if (files.length) setUploading(true);
|
|
1869
|
+
try {
|
|
1870
|
+
atts = await Promise.all(
|
|
1871
|
+
files.map(async (a) => {
|
|
1872
|
+
const mime = mimeOf(a.file);
|
|
1873
|
+
if (mime.startsWith("image/") || isText(mime)) return readInline(a.file);
|
|
1874
|
+
const up = await upload(a.file);
|
|
1875
|
+
return { name: a.file.name, mimeType: mime, data: "", uri: up.url, ...up.id ? { fileId: up.id } : {} };
|
|
1876
|
+
})
|
|
1877
|
+
);
|
|
1878
|
+
} catch (e) {
|
|
1879
|
+
setNotice(e instanceof Error ? e.message : t.uploadFailed);
|
|
1880
|
+
return;
|
|
1881
|
+
} finally {
|
|
1882
|
+
setUploading(false);
|
|
1883
|
+
}
|
|
1884
|
+
onSubmit(body, atts);
|
|
1885
|
+
setValue("");
|
|
1886
|
+
setNotice(null);
|
|
1887
|
+
for (const a of files) releaseUrl(a.url);
|
|
1888
|
+
setFiles([]);
|
|
1889
|
+
};
|
|
1890
|
+
const [recording, setRecording] = useState6(false);
|
|
1891
|
+
const [transcribing, setTranscribing] = useState6(false);
|
|
1892
|
+
const [wave, setWave] = useState6([]);
|
|
1893
|
+
const [seconds, setSeconds] = useState6(0);
|
|
1894
|
+
const recRef = useRef3(null);
|
|
1895
|
+
const pieces = useRef3([]);
|
|
1896
|
+
const cancelRef = useRef3(false);
|
|
1897
|
+
const meter = useRef3(null);
|
|
1898
|
+
const [hasMic, setHasMic] = useState6(false);
|
|
1899
|
+
useEffect3(() => setHasMic(typeof window !== "undefined" && "MediaRecorder" in window && Boolean(navigator.mediaDevices?.getUserMedia)), []);
|
|
1900
|
+
const canRecord = Boolean(transcribe) && hasMic;
|
|
1901
|
+
const toggleRecording = async () => {
|
|
1902
|
+
if (recording) {
|
|
1903
|
+
cancelRef.current = false;
|
|
1904
|
+
recRef.current?.stop();
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
try {
|
|
1908
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1909
|
+
const mime = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"].find((m) => MediaRecorder.isTypeSupported(m)) ?? "";
|
|
1910
|
+
const rec = new MediaRecorder(stream, mime ? { mimeType: mime } : void 0);
|
|
1911
|
+
pieces.current = [];
|
|
1912
|
+
rec.ondataavailable = (e) => {
|
|
1913
|
+
if (e.data.size > 0) pieces.current.push(e.data);
|
|
1914
|
+
};
|
|
1915
|
+
const ctx = new AudioContext();
|
|
1916
|
+
const an = ctx.createAnalyser();
|
|
1917
|
+
an.fftSize = 512;
|
|
1918
|
+
ctx.createMediaStreamSource(stream).connect(an);
|
|
1919
|
+
const buf = new Uint8Array(an.fftSize);
|
|
1920
|
+
let lastTick = 0;
|
|
1921
|
+
const tick = (time) => {
|
|
1922
|
+
if (time - lastTick > 80) {
|
|
1923
|
+
lastTick = time;
|
|
1924
|
+
an.getByteTimeDomainData(buf);
|
|
1925
|
+
let acc = 0;
|
|
1926
|
+
for (let i = 0; i < buf.length; i++) {
|
|
1927
|
+
const v = (buf[i] - 128) / 128;
|
|
1928
|
+
acc += v * v;
|
|
1929
|
+
}
|
|
1930
|
+
setWave((w) => [...w.slice(-59), Math.min(1, Math.sqrt(acc / buf.length) * 4)]);
|
|
1931
|
+
}
|
|
1932
|
+
if (meter.current) meter.current.raf = requestAnimationFrame(tick);
|
|
1933
|
+
};
|
|
1934
|
+
const start = Date.now();
|
|
1935
|
+
meter.current = { ctx, raf: requestAnimationFrame(tick), timer: window.setInterval(() => setSeconds(Math.floor((Date.now() - start) / 1e3)), 250) };
|
|
1936
|
+
rec.onstop = async () => {
|
|
1937
|
+
stream.getTracks().forEach((tr) => tr.stop());
|
|
1938
|
+
if (meter.current) {
|
|
1939
|
+
cancelAnimationFrame(meter.current.raf);
|
|
1940
|
+
clearInterval(meter.current.timer);
|
|
1941
|
+
void meter.current.ctx.close();
|
|
1942
|
+
meter.current = null;
|
|
1943
|
+
}
|
|
1944
|
+
setRecording(false);
|
|
1945
|
+
setWave([]);
|
|
1946
|
+
setSeconds(0);
|
|
1947
|
+
if (cancelRef.current) return;
|
|
1948
|
+
const type = rec.mimeType || mime || "audio/webm";
|
|
1949
|
+
const blob = new Blob(pieces.current, { type });
|
|
1950
|
+
if (blob.size < 1e3) return;
|
|
1951
|
+
setTranscribing(true);
|
|
1952
|
+
try {
|
|
1953
|
+
const ext = type.includes("mp4") ? "m4a" : "webm";
|
|
1954
|
+
const name = `nota-de-voz-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.${ext}`;
|
|
1955
|
+
const [said, up] = await Promise.all([transcribe(blob), upload ? upload(new File([blob], name, { type })) : Promise.resolve(null)]);
|
|
1956
|
+
const text = said.trim();
|
|
1957
|
+
if (!text) throw new Error(t.voiceEmpty);
|
|
1958
|
+
const atts = up ? [{ name, mimeType: type, data: "", uri: up.url, ...up.id ? { fileId: up.id } : {} }] : [];
|
|
1959
|
+
onSubmit(voiceNoteText ? voiceNoteText(text) : text, atts);
|
|
1960
|
+
setNotice(null);
|
|
1961
|
+
} catch (e) {
|
|
1962
|
+
setNotice(e instanceof Error ? e.message : t.voiceFailed);
|
|
1963
|
+
} finally {
|
|
1964
|
+
setTranscribing(false);
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
recRef.current = rec;
|
|
1968
|
+
rec.start();
|
|
1969
|
+
setRecording(true);
|
|
1970
|
+
} catch {
|
|
1971
|
+
setNotice(t.micDenied);
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
const canSend = Boolean(value.trim() || files.length > 0 || references.length > 0);
|
|
1975
|
+
const iconButton = "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-background-secondary hover:text-text-primary disabled:opacity-30";
|
|
1976
|
+
return /* @__PURE__ */ jsx8("div", { className: "piloto-composer rounded-(--piloto-radius-input)", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-2 p-3", children: [
|
|
1977
|
+
withFiles && dragging && /* @__PURE__ */ jsx8("div", { className: "piloto-drop pointer-events-none fixed inset-0 z-[1000] flex items-center justify-center p-6", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-center gap-2 rounded-(--piloto-radius-card) border-2 border-dashed border-brand-500 bg-background-primary px-10 py-8 text-center shadow-(--piloto-shadow-card)", children: [
|
|
1978
|
+
/* @__PURE__ */ jsx8(Paperclip, { className: "size-7 text-brand-600" }),
|
|
1979
|
+
/* @__PURE__ */ jsx8("p", { className: "text-base font-semibold text-text-primary", children: t.dropFiles }),
|
|
1980
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-text-secondary", children: t.dropHint })
|
|
1981
|
+
] }) }),
|
|
1982
|
+
/* @__PURE__ */ jsx8(
|
|
1983
|
+
"input",
|
|
1984
|
+
{
|
|
1985
|
+
ref: fileRef,
|
|
1986
|
+
type: "file",
|
|
1987
|
+
multiple: true,
|
|
1988
|
+
hidden: true,
|
|
1989
|
+
onChange: (e) => {
|
|
1990
|
+
if (e.target.files) addFiles(e.target.files);
|
|
1991
|
+
e.target.value = "";
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
),
|
|
1995
|
+
notice && /* @__PURE__ */ jsx8("p", { className: "text-xs text-text-danger", children: notice }),
|
|
1996
|
+
uploading && /* @__PURE__ */ jsxs6("p", { className: "flex items-center gap-1.5 text-xs text-text-secondary", children: [
|
|
1997
|
+
/* @__PURE__ */ jsx8(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
1998
|
+
files.length > 1 ? t.uploadingN(files.length) : t.uploading
|
|
1999
|
+
] }),
|
|
2000
|
+
files.length > 0 && /* @__PURE__ */ jsx8("div", { className: "flex flex-wrap gap-2", children: files.map((a) => /* @__PURE__ */ jsxs6("div", { className: "relative h-16 w-16 overflow-hidden rounded-lg border border-border-primary bg-background-secondary", children: [
|
|
2001
|
+
mimeOf(a.file).startsWith("image/") ? /* @__PURE__ */ jsx8("img", { src: a.url, alt: a.file.name, className: "h-full w-full object-cover" }) : (
|
|
2002
|
+
// Un PDF pintado con <img> es un recuadro roto: nombre, extensión y peso.
|
|
2003
|
+
/* @__PURE__ */ jsxs6("span", { className: "flex h-full w-full flex-col items-center justify-center gap-1 px-1 text-center", title: a.file.name, children: [
|
|
2004
|
+
/* @__PURE__ */ jsx8(FileText, { className: "h-4 w-4 shrink-0 text-text-tertiary" }),
|
|
2005
|
+
/* @__PURE__ */ jsx8("span", { className: "w-full truncate text-[9px] font-medium uppercase leading-none text-text-secondary", children: a.file.name.match(/\.([A-Za-z0-9]+)$/)?.[1] ?? "file" }),
|
|
2006
|
+
/* @__PURE__ */ jsx8("span", { className: "w-full truncate text-[9px] leading-none text-text-tertiary", children: shortSize(a.file.size) })
|
|
2007
|
+
] })
|
|
2008
|
+
),
|
|
2009
|
+
/* @__PURE__ */ jsx8(
|
|
2010
|
+
"button",
|
|
2011
|
+
{
|
|
2012
|
+
type: "button",
|
|
2013
|
+
onClick: () => removeFile(a.id),
|
|
2014
|
+
"aria-label": t.removeAttachment,
|
|
2015
|
+
className: "absolute right-0.5 top-0.5 flex h-5 w-5 items-center justify-center rounded-full bg-background-inverse/80 text-text-inverse shadow-sm transition-colors hover:bg-background-inverse",
|
|
2016
|
+
children: /* @__PURE__ */ jsx8(X, { className: "h-3 w-3" })
|
|
2017
|
+
}
|
|
2018
|
+
)
|
|
2019
|
+
] }, a.id)) }),
|
|
2020
|
+
/* @__PURE__ */ jsx8(ReferenceChips, { references, onRemove: (id) => onRemoveReference?.(id), onActivate: onActivateReference }),
|
|
2021
|
+
recording ? (
|
|
2022
|
+
// Grabando: la barra sustituye al campo de texto — punto rojo, reloj, la onda y las dos salidas.
|
|
2023
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex min-h-[24px] items-center gap-3 px-1", children: [
|
|
2024
|
+
/* @__PURE__ */ jsx8(
|
|
2025
|
+
"button",
|
|
2026
|
+
{
|
|
2027
|
+
type: "button",
|
|
2028
|
+
onClick: () => {
|
|
2029
|
+
cancelRef.current = true;
|
|
2030
|
+
recRef.current?.stop();
|
|
2031
|
+
},
|
|
2032
|
+
"aria-label": t.cancelVoice,
|
|
2033
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-text-secondary hover:bg-background-secondary hover:text-text-primary",
|
|
2034
|
+
children: /* @__PURE__ */ jsx8(X, { className: "h-4 w-4" })
|
|
2035
|
+
}
|
|
2036
|
+
),
|
|
2037
|
+
/* @__PURE__ */ jsx8("span", { className: "piloto-rec-dot h-2 w-2 shrink-0 rounded-full" }),
|
|
2038
|
+
/* @__PURE__ */ jsxs6("span", { className: "w-10 shrink-0 font-mono text-xs tabular-nums text-text-secondary", children: [
|
|
2039
|
+
Math.floor(seconds / 60),
|
|
2040
|
+
":",
|
|
2041
|
+
String(seconds % 60).padStart(2, "0")
|
|
2042
|
+
] }),
|
|
2043
|
+
/* @__PURE__ */ jsx8("div", { className: "flex h-6 min-w-0 flex-1 items-center gap-[2px] overflow-hidden", children: Array.from({ length: 60 }, (_, i) => wave[wave.length - 60 + i] ?? 0).map((v, i) => /* @__PURE__ */ jsx8("span", { className: "w-[3px] shrink-0 rounded-full bg-text-secondary/70", style: { height: `${Math.max(3, Math.round(v * 22))}px` } }, i)) }),
|
|
2044
|
+
/* @__PURE__ */ jsx8("button", { type: "button", onClick: toggleRecording, "aria-label": t.stopAndSendVoice, className: "piloto-send flex h-8 w-8 shrink-0 items-center justify-center rounded-full", children: /* @__PURE__ */ jsx8(ArrowUp, { className: "h-4 w-4" }) })
|
|
2045
|
+
] })
|
|
2046
|
+
) : /* @__PURE__ */ jsx8(
|
|
2047
|
+
"textarea",
|
|
2048
|
+
{
|
|
2049
|
+
ref,
|
|
2050
|
+
rows: 1,
|
|
2051
|
+
value,
|
|
2052
|
+
placeholder,
|
|
2053
|
+
onChange: (e) => setValue(e.target.value),
|
|
2054
|
+
onKeyDown: (e) => {
|
|
2055
|
+
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
|
|
2056
|
+
e.preventDefault();
|
|
2057
|
+
void submit();
|
|
2058
|
+
} else if (e.key === "Backspace" && !value && references.length) {
|
|
2059
|
+
e.preventDefault();
|
|
2060
|
+
onRemoveReference?.(references[references.length - 1].id);
|
|
2061
|
+
}
|
|
2062
|
+
},
|
|
2063
|
+
className: "piloto-textarea max-h-60 min-h-[24px] w-full flex-none resize-none overflow-y-auto bg-transparent px-1 text-sm leading-6 text-text-primary outline-none placeholder:text-text-tertiary"
|
|
2064
|
+
}
|
|
2065
|
+
),
|
|
2066
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
|
|
2067
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden", children: [
|
|
2068
|
+
withFiles && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
2069
|
+
/* @__PURE__ */ jsx8(
|
|
2070
|
+
"button",
|
|
2071
|
+
{
|
|
2072
|
+
type: "button",
|
|
2073
|
+
onClick: () => {
|
|
2074
|
+
if (!fileRef.current) return;
|
|
2075
|
+
fileRef.current.accept = "image/*";
|
|
2076
|
+
fileRef.current.click();
|
|
2077
|
+
},
|
|
2078
|
+
disabled: busy || !canSeeImages || files.length >= maxFiles,
|
|
2079
|
+
"aria-label": canSeeImages ? t.attachImage : t.noImages,
|
|
2080
|
+
title: canSeeImages ? t.attachImage : t.noImages,
|
|
2081
|
+
className: iconButton,
|
|
2082
|
+
children: /* @__PURE__ */ jsx8(Image, { className: "h-4 w-4" })
|
|
2083
|
+
}
|
|
2084
|
+
),
|
|
2085
|
+
/* @__PURE__ */ jsx8(
|
|
2086
|
+
"button",
|
|
2087
|
+
{
|
|
2088
|
+
type: "button",
|
|
2089
|
+
onClick: () => {
|
|
2090
|
+
if (!fileRef.current) return;
|
|
2091
|
+
fileRef.current.accept = "";
|
|
2092
|
+
fileRef.current.click();
|
|
2093
|
+
},
|
|
2094
|
+
disabled: busy || files.length >= maxFiles,
|
|
2095
|
+
"aria-label": t.attachFile,
|
|
2096
|
+
title: t.attachFile,
|
|
2097
|
+
className: iconButton,
|
|
2098
|
+
children: /* @__PURE__ */ jsx8(Paperclip, { className: "h-4 w-4" })
|
|
2099
|
+
}
|
|
2100
|
+
)
|
|
2101
|
+
] }),
|
|
2102
|
+
extras,
|
|
2103
|
+
models.length > 0 && onModelChange && /* @__PURE__ */ jsx8(ModelPicker, { models, current: currentModel, onChange: onModelChange })
|
|
2104
|
+
] }),
|
|
2105
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex shrink-0 items-center gap-1.5", children: [
|
|
2106
|
+
canRecord && !recording && /* @__PURE__ */ jsx8("button", { type: "button", onClick: toggleRecording, disabled: busy || transcribing, "aria-label": t.recordVoice, title: t.voiceNote, className: `${iconButton} h-8 w-8`, children: transcribing ? /* @__PURE__ */ jsx8(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx8(Mic, { className: "h-4 w-4" }) }),
|
|
2107
|
+
/* @__PURE__ */ jsx8(
|
|
2108
|
+
"button",
|
|
2109
|
+
{
|
|
2110
|
+
type: "button",
|
|
2111
|
+
onClick: busy ? onStop : () => void submit(),
|
|
2112
|
+
disabled: !busy && !canSend || uploading,
|
|
2113
|
+
"aria-label": busy ? t.stop : uploading ? t.uploading : t.send,
|
|
2114
|
+
"data-ready": busy || canSend || uploading || void 0,
|
|
2115
|
+
className: "piloto-send flex h-8 w-8 items-center justify-center rounded-full transition-colors",
|
|
2116
|
+
children: busy ? /* @__PURE__ */ jsx8(Square, { className: "h-3 w-3 fill-current" }) : uploading ? /* @__PURE__ */ jsx8(LoaderCircle, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx8(ArrowUp, { className: "h-4 w-4" })
|
|
2117
|
+
}
|
|
2118
|
+
)
|
|
2119
|
+
] })
|
|
2120
|
+
] })
|
|
2121
|
+
] }) });
|
|
2122
|
+
}
|
|
2123
|
+
function ModelPicker({ models, current, onChange }) {
|
|
2124
|
+
const { strings: t } = usePiloto();
|
|
2125
|
+
const [open, setOpen] = useState6(false);
|
|
2126
|
+
const box = useRef3(null);
|
|
2127
|
+
useEffect3(() => {
|
|
2128
|
+
if (!open) return;
|
|
2129
|
+
const outside = (e) => {
|
|
2130
|
+
if (!box.current?.contains(e.target)) setOpen(false);
|
|
2131
|
+
};
|
|
2132
|
+
const escape = (e) => e.key === "Escape" && setOpen(false);
|
|
2133
|
+
document.addEventListener("mousedown", outside);
|
|
2134
|
+
document.addEventListener("keydown", escape);
|
|
2135
|
+
return () => {
|
|
2136
|
+
document.removeEventListener("mousedown", outside);
|
|
2137
|
+
document.removeEventListener("keydown", escape);
|
|
2138
|
+
};
|
|
2139
|
+
}, [open]);
|
|
2140
|
+
if (!models.length) return null;
|
|
2141
|
+
const active = models.find((m) => m.value === current) ?? null;
|
|
2142
|
+
return /* @__PURE__ */ jsxs6("div", { ref: box, className: "relative", children: [
|
|
2143
|
+
/* @__PURE__ */ jsxs6(
|
|
2144
|
+
"button",
|
|
2145
|
+
{
|
|
2146
|
+
type: "button",
|
|
2147
|
+
"aria-expanded": open,
|
|
2148
|
+
onClick: () => setOpen((v) => !v),
|
|
2149
|
+
className: "flex max-w-56 items-center gap-1.5 rounded-full border border-border-primary px-2.5 py-1 text-xs text-text-secondary transition-colors hover:bg-background-secondary hover:text-text-primary",
|
|
2150
|
+
children: [
|
|
2151
|
+
/* @__PURE__ */ jsx8(Cpu, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
2152
|
+
/* @__PURE__ */ jsx8("span", { className: "truncate", children: active?.name ?? t.model }),
|
|
2153
|
+
/* @__PURE__ */ jsx8(ChevronDown, { className: "h-3.5 w-3.5 shrink-0 opacity-70" })
|
|
2154
|
+
]
|
|
2155
|
+
}
|
|
2156
|
+
),
|
|
2157
|
+
open && /* @__PURE__ */ jsx8("div", { className: "piloto-menu absolute bottom-full left-0 z-20 mb-1 max-h-80 min-w-52 overflow-y-auto rounded-(--piloto-radius-input) border border-border-primary bg-background-primary py-1 shadow-(--piloto-shadow-card)", children: models.map((m) => /* @__PURE__ */ jsxs6(
|
|
2158
|
+
"button",
|
|
2159
|
+
{
|
|
2160
|
+
type: "button",
|
|
2161
|
+
onClick: () => {
|
|
2162
|
+
onChange(m.value);
|
|
2163
|
+
setOpen(false);
|
|
2164
|
+
},
|
|
2165
|
+
className: "flex w-full items-center justify-between gap-3 px-3 py-1.5 text-left text-xs text-text-secondary transition-colors hover:bg-background-secondary hover:text-text-primary",
|
|
2166
|
+
children: [
|
|
2167
|
+
/* @__PURE__ */ jsx8("span", { className: "truncate", children: m.name }),
|
|
2168
|
+
m.value === current && /* @__PURE__ */ jsx8(Check, { className: "h-3.5 w-3.5 shrink-0 text-text-success" })
|
|
2169
|
+
]
|
|
2170
|
+
},
|
|
2171
|
+
m.value
|
|
2172
|
+
)) })
|
|
2173
|
+
] });
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// src/react/Status.tsx
|
|
2177
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
2178
|
+
import { useEffect as useEffect4, useState as useState7 } from "react";
|
|
2179
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2180
|
+
var WAKING_GIVEUP_S = 45;
|
|
2181
|
+
var STILL_WORKING_S = 90;
|
|
2182
|
+
function TypingDots() {
|
|
2183
|
+
return /* @__PURE__ */ jsx9("div", { className: "piloto-typing", "aria-hidden": true, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx9("span", { className: "piloto-typing-dot" }, i)) });
|
|
2184
|
+
}
|
|
2185
|
+
function WaitLabel({
|
|
2186
|
+
phase,
|
|
2187
|
+
since,
|
|
2188
|
+
elsewhere,
|
|
2189
|
+
onLost,
|
|
2190
|
+
showDuration = true
|
|
2191
|
+
}) {
|
|
2192
|
+
const { strings: t } = usePiloto();
|
|
2193
|
+
const [mounted] = useState7(() => Date.now());
|
|
2194
|
+
const [now, setNow] = useState7(() => Date.now());
|
|
2195
|
+
useEffect4(() => {
|
|
2196
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
2197
|
+
return () => clearInterval(id);
|
|
2198
|
+
}, []);
|
|
2199
|
+
const seconds = Math.max(0, Math.floor((now - (since || mounted)) / 1e3));
|
|
2200
|
+
const phrases = t.thinkingPhrases;
|
|
2201
|
+
const what = phase !== "session" ? t.waitPhase[phase] : phrases?.length ? (
|
|
2202
|
+
// Frases propias de la app (el widget de un cliente): rotan cada 4 s.
|
|
2203
|
+
elsewhere || seconds >= STILL_WORKING_S ? t.stillWorking : phrases[Math.floor(seconds / 4) % phrases.length]
|
|
2204
|
+
) : elsewhere ? t.stillWorking : t.howItGoes(seconds);
|
|
2205
|
+
const lost = phase === "waking" && seconds >= WAKING_GIVEUP_S;
|
|
2206
|
+
return /* @__PURE__ */ jsx9("div", { className: "piloto-wait flex flex-col gap-0.5 text-sm text-text-secondary", children: lost ? /* @__PURE__ */ jsxs7("p", { className: "text-text-primary", children: [
|
|
2207
|
+
t.cantStart,
|
|
2208
|
+
" ",
|
|
2209
|
+
/* @__PURE__ */ jsx9("button", { type: "button", onClick: onLost, className: "cursor-pointer underline underline-offset-2", children: t.retry })
|
|
2210
|
+
] }) : (
|
|
2211
|
+
// La frase cambia con fundido (sale y entra): de golpe se leía brusco.
|
|
2212
|
+
/* @__PURE__ */ jsxs7("p", { className: "flex items-center", children: [
|
|
2213
|
+
/* @__PURE__ */ jsx9(AnimatePresence, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx9(motion.span, { className: "piloto-wait-text", initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.3, ease: "easeInOut" }, children: what }, what) }),
|
|
2214
|
+
showDuration && /* @__PURE__ */ jsx9("span", { className: "piloto-wait-time", children: formatDuration(seconds) })
|
|
2215
|
+
] })
|
|
2216
|
+
) });
|
|
2217
|
+
}
|
|
2218
|
+
var PHASES = ["waking", "connecting", "session"];
|
|
2219
|
+
function ConnectingState({ phase, error, avatar, onRetry }) {
|
|
2220
|
+
const { strings: t } = usePiloto();
|
|
2221
|
+
const current = PHASES.indexOf(phase);
|
|
2222
|
+
return /* @__PURE__ */ jsxs7(motion.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.3 }, className: "piloto-connecting flex min-h-[40vh] flex-col items-center justify-center gap-8 text-center", children: [
|
|
2223
|
+
/* @__PURE__ */ jsxs7("div", { className: "relative flex h-20 w-20 items-center justify-center", children: [
|
|
2224
|
+
!error && [0, 1].map((i) => /* @__PURE__ */ jsx9(
|
|
2225
|
+
motion.span,
|
|
2226
|
+
{
|
|
2227
|
+
className: "absolute inset-0 rounded-full border border-border-primary",
|
|
2228
|
+
initial: { scale: 0.6, opacity: 0.8 },
|
|
2229
|
+
animate: { scale: 1.6, opacity: 0 },
|
|
2230
|
+
transition: { duration: 2.2, repeat: Infinity, ease: "easeOut", delay: i * 1.1 }
|
|
2231
|
+
},
|
|
2232
|
+
i
|
|
2233
|
+
)),
|
|
2234
|
+
/* @__PURE__ */ jsx9(
|
|
2235
|
+
motion.div,
|
|
2236
|
+
{
|
|
2237
|
+
className: "grid h-12 w-12 place-items-center",
|
|
2238
|
+
animate: error ? { scale: 1, filter: "grayscale(1)" } : { scale: [1, 1.06, 1] },
|
|
2239
|
+
transition: error ? { duration: 0.3 } : { duration: 2.2, repeat: Infinity, ease: "easeInOut" },
|
|
2240
|
+
children: avatar ?? /* @__PURE__ */ jsx9("span", { className: "piloto-avatar-default" })
|
|
2241
|
+
}
|
|
2242
|
+
)
|
|
2243
|
+
] }),
|
|
2244
|
+
/* @__PURE__ */ jsx9("ol", { className: "flex flex-col gap-3 text-left", children: t.connectSteps.map((step, i) => {
|
|
2245
|
+
const done = !error && i < current;
|
|
2246
|
+
const active = !error && i === current;
|
|
2247
|
+
const failed = !!error && i === current;
|
|
2248
|
+
return /* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-3", children: [
|
|
2249
|
+
/* @__PURE__ */ jsx9("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: done ? /* @__PURE__ */ jsx9("span", { className: "piloto-pop flex h-5 w-5 items-center justify-center rounded-full bg-background-success", children: /* @__PURE__ */ jsx9(Check, { className: "h-3 w-3 text-text-inverse", strokeWidth: 3 }) }) : active ? /* @__PURE__ */ jsx9("span", { className: "piloto-pulse h-2.5 w-2.5 rounded-full bg-text-primary" }) : failed ? /* @__PURE__ */ jsx9("span", { className: "h-2.5 w-2.5 rounded-full bg-text-danger" }) : /* @__PURE__ */ jsx9("span", { className: "h-2 w-2 rounded-full bg-border-primary" }) }),
|
|
2250
|
+
/* @__PURE__ */ jsxs7("div", { className: "leading-tight", children: [
|
|
2251
|
+
/* @__PURE__ */ jsxs7("p", { className: `text-sm ${active || failed ? "text-text-primary" : done ? "text-text-secondary" : "text-text-tertiary"}`, children: [
|
|
2252
|
+
step.label,
|
|
2253
|
+
active && /* @__PURE__ */ jsx9("span", { className: "piloto-ellipsis inline-block w-4 text-left", children: "\u2026" })
|
|
2254
|
+
] }),
|
|
2255
|
+
active && /* @__PURE__ */ jsx9("p", { className: "text-xs text-text-tertiary", children: step.hint })
|
|
2256
|
+
] })
|
|
2257
|
+
] }, i);
|
|
2258
|
+
}) }),
|
|
2259
|
+
error && /* @__PURE__ */ jsxs7("div", { className: "flex max-w-md flex-col items-center gap-3", children: [
|
|
2260
|
+
/* @__PURE__ */ jsx9("p", { className: "text-sm text-text-danger", children: error }),
|
|
2261
|
+
onRetry && /* @__PURE__ */ jsx9("button", { type: "button", onClick: onRetry, className: "rounded-full border border-border-primary px-4 py-1.5 text-sm text-text-primary hover:bg-background-secondary", children: t.connectRetry })
|
|
2262
|
+
] })
|
|
2263
|
+
] });
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// src/react/usePilot.ts
|
|
2267
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef4, useState as useState8 } from "react";
|
|
2268
|
+
var message = (e, fallback) => e instanceof Error && e.message ? e.message : fallback;
|
|
2269
|
+
function usePilot(transport, opts = {}) {
|
|
2270
|
+
const [state, setState] = useState8(() => createPilotState(opts.initialTurns ?? []));
|
|
2271
|
+
const ref = useRef4(state);
|
|
2272
|
+
const optsRef = useRef4(opts);
|
|
2273
|
+
optsRef.current = opts;
|
|
2274
|
+
const transportRef = useRef4(transport);
|
|
2275
|
+
transportRef.current = transport;
|
|
2276
|
+
const pendingWakes = useRef4([]);
|
|
2277
|
+
const commit = useCallback2((next) => {
|
|
2278
|
+
const prev = ref.current;
|
|
2279
|
+
if (next === prev) return;
|
|
2280
|
+
ref.current = next;
|
|
2281
|
+
setState(next);
|
|
2282
|
+
const o = optsRef.current;
|
|
2283
|
+
for (const e of next.effects) {
|
|
2284
|
+
if (e.type === "turn-start") o.onTurnStart?.();
|
|
2285
|
+
else if (e.type === "turn-done") o.onTurnDone?.({ text: e.text, turnId: e.turnId, seconds: e.seconds });
|
|
2286
|
+
else if (e.type === "turn-interrupted") {
|
|
2287
|
+
const last = next.turns[next.turns.length - 1];
|
|
2288
|
+
o.onTurnDone?.({ text: last?.role === "assistant" ? last.text : "", turnId: e.turnId, interrupted: { reason: e.reason, turnId: e.turnId } });
|
|
2289
|
+
} else if (e.type === "needs-login") o.onNeedsLogin?.();
|
|
2290
|
+
else if (e.type === "title") o.onTitle?.(e.title);
|
|
2291
|
+
else if (e.type === "disconnected") o.onDisconnected?.();
|
|
2292
|
+
}
|
|
2293
|
+
if (prev.busy && !next.busy && pendingWakes.current.length) {
|
|
2294
|
+
const texts = pendingWakes.current.splice(0);
|
|
2295
|
+
const after = appendAgentMessages(ref.current, texts);
|
|
2296
|
+
ref.current = after;
|
|
2297
|
+
setState(after);
|
|
2298
|
+
}
|
|
2299
|
+
}, []);
|
|
2300
|
+
useEffect5(() => {
|
|
2301
|
+
if (!transport) return;
|
|
2302
|
+
let off = null;
|
|
2303
|
+
let gone = false;
|
|
2304
|
+
const subscribe = () => {
|
|
2305
|
+
if (gone) return;
|
|
2306
|
+
off = transport.subscribe(
|
|
2307
|
+
(name, data) => {
|
|
2308
|
+
optsRef.current.onEvent?.(name, data);
|
|
2309
|
+
commit(reducePilotEvent(ref.current, name, data));
|
|
2310
|
+
},
|
|
2311
|
+
{ isBusy: () => ref.current.busy }
|
|
2312
|
+
);
|
|
2313
|
+
};
|
|
2314
|
+
if (transport.history && !optsRef.current.initialTurns) {
|
|
2315
|
+
transport.history().then((h) => {
|
|
2316
|
+
if (gone) return;
|
|
2317
|
+
const { turns, permissions } = Array.isArray(h) ? { turns: h, permissions: [] } : h;
|
|
2318
|
+
commit(applyHistory(ref.current, turns, permissions));
|
|
2319
|
+
}).catch(() => {
|
|
2320
|
+
}).finally(subscribe);
|
|
2321
|
+
} else subscribe();
|
|
2322
|
+
return () => {
|
|
2323
|
+
gone = true;
|
|
2324
|
+
off?.();
|
|
2325
|
+
};
|
|
2326
|
+
}, [transport, commit]);
|
|
2327
|
+
const first = useRef4(true);
|
|
2328
|
+
useEffect5(() => {
|
|
2329
|
+
if (first.current) {
|
|
2330
|
+
first.current = false;
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
if (opts.initialTurns) commit(reconcileWith(ref.current, opts.initialTurns));
|
|
2334
|
+
}, [opts.initialTurns, commit]);
|
|
2335
|
+
const send = useCallback2(
|
|
2336
|
+
async (text, attachments = [], extra = {}) => {
|
|
2337
|
+
const t = transportRef.current;
|
|
2338
|
+
if (!t) return;
|
|
2339
|
+
const s0 = ref.current;
|
|
2340
|
+
const inFlight = s0.busy;
|
|
2341
|
+
const ctx = optsRef.current.context;
|
|
2342
|
+
const input = {
|
|
2343
|
+
text,
|
|
2344
|
+
attachments,
|
|
2345
|
+
firstTurn: s0.turns.length === 0,
|
|
2346
|
+
...extra.resume ? { resume: true } : {},
|
|
2347
|
+
...extra.resumeTurnId ? { resumeTurnId: extra.resumeTurnId } : {},
|
|
2348
|
+
...extra.references?.length ? { references: extra.references } : {},
|
|
2349
|
+
...ctx !== void 0 ? { context: typeof ctx === "function" ? ctx() : ctx } : {}
|
|
2350
|
+
};
|
|
2351
|
+
commit(beginSend(s0, text, attachments));
|
|
2352
|
+
optsRef.current.onSend?.(input);
|
|
2353
|
+
try {
|
|
2354
|
+
const r = await t.send(input);
|
|
2355
|
+
commit(sendAccepted(ref.current, r, inFlight));
|
|
2356
|
+
} catch (e) {
|
|
2357
|
+
if (e?.code === "turn_busy") {
|
|
2358
|
+
commit(sendRejectedBusy(ref.current, message(e, "Ya hay un turno en curso.")));
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
commit(sendFailed(ref.current, message(e, "No se pudo mandar el mensaje"), Boolean(e?.needsLogin)));
|
|
2362
|
+
}
|
|
2363
|
+
},
|
|
2364
|
+
[commit]
|
|
2365
|
+
);
|
|
2366
|
+
const stop = useCallback2(() => {
|
|
2367
|
+
commit(stopTurn(ref.current));
|
|
2368
|
+
void transportRef.current?.cancel();
|
|
2369
|
+
}, [commit]);
|
|
2370
|
+
const retry = useCallback2(() => {
|
|
2371
|
+
const s = ref.current;
|
|
2372
|
+
const last = lastUserTurn(s.turns);
|
|
2373
|
+
if (!last || s.busy) return;
|
|
2374
|
+
commit(dropLastExchange(s));
|
|
2375
|
+
void send(last.text, last.attachments ?? []);
|
|
2376
|
+
}, [commit, send]);
|
|
2377
|
+
const abandonAndRetry = useCallback2(() => {
|
|
2378
|
+
const s = ref.current;
|
|
2379
|
+
const last = lastUserTurn(s.turns);
|
|
2380
|
+
if (!last) return;
|
|
2381
|
+
commit({ ...dropLastExchange(s), busy: false });
|
|
2382
|
+
void send(last.text, last.attachments ?? []);
|
|
2383
|
+
}, [commit, send]);
|
|
2384
|
+
const resume = useCallback2(() => {
|
|
2385
|
+
const s = ref.current;
|
|
2386
|
+
if (s.busy) return;
|
|
2387
|
+
commit({ ...s, interrupted: null, effects: [] });
|
|
2388
|
+
if (resumeMode(s.turns) === "resend") abandonAndRetry();
|
|
2389
|
+
else void send(RESUME_TEXT, [], { resume: true, resumeTurnId: s.interrupted?.turnId });
|
|
2390
|
+
}, [commit, send, abandonAndRetry]);
|
|
2391
|
+
const setModel = useCallback2(
|
|
2392
|
+
(value) => {
|
|
2393
|
+
commit({ ...ref.current, currentModel: value, effects: [] });
|
|
2394
|
+
void transportRef.current?.setModel?.(value);
|
|
2395
|
+
},
|
|
2396
|
+
[commit]
|
|
2397
|
+
);
|
|
2398
|
+
const respondPermission = useCallback2(
|
|
2399
|
+
async (id, optionId, via) => {
|
|
2400
|
+
const t = transportRef.current;
|
|
2401
|
+
const handler = via ?? (t?.respondPermission ? (a, b) => t.respondPermission(a, b) : void 0);
|
|
2402
|
+
if (!handler) throw new Error("ghostykit: el transporte no sabe contestar permisos (pasa `onPermission`)");
|
|
2403
|
+
const asked = ref.current.permissions.find((p) => p.id === id);
|
|
2404
|
+
const r = await handler(id, optionId);
|
|
2405
|
+
commit(resolvePermission(ref.current, id, optionId, r ? r.resolved !== false : true, r ? r.detail : void 0, asked));
|
|
2406
|
+
return r;
|
|
2407
|
+
},
|
|
2408
|
+
[commit]
|
|
2409
|
+
);
|
|
2410
|
+
const recordDecision = useCallback2(
|
|
2411
|
+
(id, optionId, r, asked) => commit(resolvePermission(ref.current, id, optionId, r ? r.resolved !== false : true, r ? r.detail : void 0, asked)),
|
|
2412
|
+
[commit]
|
|
2413
|
+
);
|
|
2414
|
+
const expirePermission = useCallback2(
|
|
2415
|
+
(id) => {
|
|
2416
|
+
if (ref.current.permissions.some((p) => p.id === id)) commit(resolvePermission(ref.current, id, "", false));
|
|
2417
|
+
},
|
|
2418
|
+
[commit]
|
|
2419
|
+
);
|
|
2420
|
+
const appendAgentMessage = useCallback2(
|
|
2421
|
+
(text) => {
|
|
2422
|
+
if (ref.current.busy) {
|
|
2423
|
+
pendingWakes.current.push(text);
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
commit(appendAgentMessages(ref.current, [text]));
|
|
2427
|
+
},
|
|
2428
|
+
[commit]
|
|
2429
|
+
);
|
|
2430
|
+
return { state, send, stop, retry, abandonAndRetry, resume, setModel, respondPermission, recordDecision, expirePermission, appendAgentMessage };
|
|
2431
|
+
}
|
|
2432
|
+
function useSseTransport(base, opts) {
|
|
2433
|
+
const optsRef = useRef4(opts);
|
|
2434
|
+
optsRef.current = opts;
|
|
2435
|
+
return useMemo3(() => base ? createSseTransport(base, optsRef.current) : null, [base]);
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
// src/react/Piloto.tsx
|
|
2439
|
+
import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2440
|
+
var useIsoLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect;
|
|
2441
|
+
var ANCHOR_GAP = 24;
|
|
2442
|
+
var Piloto = forwardRef3(function Piloto2(props, handle) {
|
|
2443
|
+
const { transport, pilot: external, locale = "es", theme = "light", features = {}, slots = {} } = props;
|
|
2444
|
+
const own = usePilot(external ? null : transport, props);
|
|
2445
|
+
const pilot = external ?? own;
|
|
2446
|
+
const { state } = pilot;
|
|
2447
|
+
const { turns, busy, connected, phase, error, notice, interrupted, permissions } = state;
|
|
2448
|
+
const strings = useMemo4(() => ({ ...locale === "en" ? STRINGS_EN : STRINGS_ES, ...props.strings }), [locale, props.strings]);
|
|
2449
|
+
const config = useMemo4(
|
|
2450
|
+
() => ({
|
|
2451
|
+
strings,
|
|
2452
|
+
toolVerbs: { ...locale === "en" ? TOOL_VERBS_EN : TOOL_VERBS_ES, ...props.toolVerbs },
|
|
2453
|
+
toolIcons: props.toolIcons ?? {},
|
|
2454
|
+
formatUserText: props.formatUserText ?? cleanUserText,
|
|
2455
|
+
theme,
|
|
2456
|
+
skin: props.skin ?? "ghosty",
|
|
2457
|
+
markdown: props.markdown ?? {}
|
|
2458
|
+
}),
|
|
2459
|
+
[strings, locale, props.toolVerbs, props.toolIcons, props.formatUserText, theme, props.skin, props.markdown]
|
|
2460
|
+
);
|
|
2461
|
+
const t = strings;
|
|
2462
|
+
const [ownRefs, setOwnRefs] = useState9([]);
|
|
2463
|
+
const refs = props.references ?? ownRefs;
|
|
2464
|
+
const refsRef = useRef5(refs);
|
|
2465
|
+
refsRef.current = refs;
|
|
2466
|
+
const setRefs = useCallback3(
|
|
2467
|
+
(next) => {
|
|
2468
|
+
if (!props.references) setOwnRefs(next);
|
|
2469
|
+
props.onReferencesChange?.(next);
|
|
2470
|
+
},
|
|
2471
|
+
[props.references, props.onReferencesChange]
|
|
2472
|
+
);
|
|
2473
|
+
const composerBox = useRef5(null);
|
|
2474
|
+
const draftText = typeof props.draft === "string" ? props.draft : props.draft?.text;
|
|
2475
|
+
const draftKey = typeof props.draft === "object" && props.draft ? props.draft.key : void 0;
|
|
2476
|
+
const [draft, setDraftState] = useState9(() => ({ text: draftText ?? "", n: 0 }));
|
|
2477
|
+
const draftSeen = useRef5(`${draftText}\0${draftKey}`);
|
|
2478
|
+
useEffect6(() => {
|
|
2479
|
+
const id = `${draftText}\0${draftKey}`;
|
|
2480
|
+
if (draftText == null || id === draftSeen.current) return;
|
|
2481
|
+
draftSeen.current = id;
|
|
2482
|
+
setDraftState((d) => ({ text: draftText, n: d.n + 1 }));
|
|
2483
|
+
}, [draftText, draftKey]);
|
|
2484
|
+
const send = useCallback3(
|
|
2485
|
+
(text, attachments = []) => {
|
|
2486
|
+
const current = refsRef.current;
|
|
2487
|
+
const full = applyReferences(text, current);
|
|
2488
|
+
if (current.length) setRefs([]);
|
|
2489
|
+
void pilot.send(full, attachments, current.length ? { references: current } : {});
|
|
2490
|
+
},
|
|
2491
|
+
[pilot, setRefs]
|
|
2492
|
+
);
|
|
2493
|
+
useImperativeHandle(
|
|
2494
|
+
handle,
|
|
2495
|
+
() => ({
|
|
2496
|
+
send,
|
|
2497
|
+
addReference: (r) => {
|
|
2498
|
+
const current = refsRef.current;
|
|
2499
|
+
if (!current.some((x) => x.id === r.id)) setRefs([...current, r]);
|
|
2500
|
+
requestAnimationFrame(() => composerBox.current?.querySelector("textarea")?.focus());
|
|
2501
|
+
},
|
|
2502
|
+
setDraft: (text) => setDraftState((d) => ({ text, n: d.n + 1 })),
|
|
2503
|
+
focus: () => composerBox.current?.querySelector("textarea")?.focus()
|
|
2504
|
+
}),
|
|
2505
|
+
[send, setRefs]
|
|
2506
|
+
);
|
|
2507
|
+
const [held, setHeld] = useState9({});
|
|
2508
|
+
const releaseHeld = (id) => setHeld(({ [id]: _, ...rest }) => rest);
|
|
2509
|
+
const respond = async (id, optionId) => {
|
|
2510
|
+
const req = permissions.find((p) => p.id === id);
|
|
2511
|
+
if (req) setHeld((h) => ({ ...h, [id]: req }));
|
|
2512
|
+
const handler = props.onPermission ?? (transport?.respondPermission ? (a, b) => transport.respondPermission(a, b) : void 0);
|
|
2513
|
+
if (!handler) throw new Error("ghostykit: falta `onPermission` (el transporte no contesta permisos)");
|
|
2514
|
+
try {
|
|
2515
|
+
return await handler(id, optionId);
|
|
2516
|
+
} catch (e) {
|
|
2517
|
+
releaseHeld(id);
|
|
2518
|
+
throw e;
|
|
2519
|
+
}
|
|
2520
|
+
};
|
|
2521
|
+
const settle = (id, optionId, answer) => {
|
|
2522
|
+
pilot.recordDecision(id, optionId, answer, held[id]);
|
|
2523
|
+
releaseHeld(id);
|
|
2524
|
+
};
|
|
2525
|
+
const cards = [...permissions, ...Object.values(held).filter((h) => !permissions.some((p) => p.id === h.id))];
|
|
2526
|
+
const scroller = useRef5(null);
|
|
2527
|
+
const column = useRef5(null);
|
|
2528
|
+
const spacer = useRef5(null);
|
|
2529
|
+
const following = useRef5(true);
|
|
2530
|
+
const touchY = useRef5(null);
|
|
2531
|
+
const [atBottom, setAtBottom] = useState9(true);
|
|
2532
|
+
const message2 = (n) => scroller.current?.querySelector(`[data-turn="${n}"]`) ?? null;
|
|
2533
|
+
const placed = useRef5(false);
|
|
2534
|
+
useEffect6(() => {
|
|
2535
|
+
if (placed.current || turns.length === 0) return;
|
|
2536
|
+
placed.current = true;
|
|
2537
|
+
const el = scroller.current;
|
|
2538
|
+
if (el) el.scrollTop = el.scrollHeight;
|
|
2539
|
+
}, [turns.length]);
|
|
2540
|
+
let mineIndex = -1;
|
|
2541
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
2542
|
+
const x = turns[i];
|
|
2543
|
+
if (x.role === "user" && !x.steer && !x.text.startsWith("\u23F0 ")) {
|
|
2544
|
+
mineIndex = i;
|
|
2545
|
+
break;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
const mine = mineIndex >= 0 ? turns[mineIndex] : null;
|
|
2549
|
+
const mineSeen = useRef5(mine);
|
|
2550
|
+
const [anchor, setAnchor] = useState9(null);
|
|
2551
|
+
useEffect6(() => {
|
|
2552
|
+
if (mine === mineSeen.current) return;
|
|
2553
|
+
mineSeen.current = mine;
|
|
2554
|
+
if (mine) setAnchor(mineIndex);
|
|
2555
|
+
following.current = true;
|
|
2556
|
+
}, [mine, mineIndex]);
|
|
2557
|
+
const gapAbove = (a) => anchor === 0 ? a.offsetTop : ANCHOR_GAP;
|
|
2558
|
+
const measureSpacer = useCallback3(() => {
|
|
2559
|
+
const sc = scroller.current;
|
|
2560
|
+
const sp = spacer.current;
|
|
2561
|
+
if (!sc || !sp) return;
|
|
2562
|
+
const a = anchor != null ? message2(anchor) : null;
|
|
2563
|
+
if (!a) {
|
|
2564
|
+
sp.style.height = "0px";
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2567
|
+
const fromAnchor = sp.getBoundingClientRect().top - a.getBoundingClientRect().top;
|
|
2568
|
+
sp.style.height = `${Math.max(0, sc.clientHeight - fromAnchor - gapAbove(a))}px`;
|
|
2569
|
+
}, [anchor]);
|
|
2570
|
+
useIsoLayoutEffect(() => {
|
|
2571
|
+
measureSpacer();
|
|
2572
|
+
const sc = scroller.current;
|
|
2573
|
+
const a = anchor != null ? message2(anchor) : null;
|
|
2574
|
+
if (sc && a) sc.scrollTo({ top: a.offsetTop - gapAbove(a), behavior: "smooth" });
|
|
2575
|
+
}, [anchor, measureSpacer]);
|
|
2576
|
+
useEffect6(() => {
|
|
2577
|
+
const col = column.current;
|
|
2578
|
+
const sc = scroller.current;
|
|
2579
|
+
if (!col || !sc || typeof ResizeObserver === "undefined") return;
|
|
2580
|
+
const ro = new ResizeObserver(() => {
|
|
2581
|
+
measureSpacer();
|
|
2582
|
+
if (following.current && spacer.current?.style.height === "0px") sc.scrollTop = sc.scrollHeight;
|
|
2583
|
+
});
|
|
2584
|
+
ro.observe(col);
|
|
2585
|
+
ro.observe(sc);
|
|
2586
|
+
return () => ro.disconnect();
|
|
2587
|
+
}, [measureSpacer]);
|
|
2588
|
+
const onScroll = () => {
|
|
2589
|
+
const el = scroller.current;
|
|
2590
|
+
if (!el) return;
|
|
2591
|
+
const end = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
2592
|
+
setAtBottom(end);
|
|
2593
|
+
if (end) following.current = true;
|
|
2594
|
+
};
|
|
2595
|
+
const release = () => {
|
|
2596
|
+
following.current = false;
|
|
2597
|
+
};
|
|
2598
|
+
const toBottom = () => {
|
|
2599
|
+
const el = scroller.current;
|
|
2600
|
+
if (el) el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
|
|
2601
|
+
};
|
|
2602
|
+
const last = turns[turns.length - 1];
|
|
2603
|
+
const pageInProgress = last?.role === "assistant" && (Boolean(last.planning) || (last.pages ?? []).some((p) => !p.error && (!p.readyAt || p.review?.status === "running")));
|
|
2604
|
+
const toolRunning = busy && last?.role === "assistant" && ((last.tools ?? []).some(isRunning) || pageInProgress);
|
|
2605
|
+
const empty = turns.length === 0 && !busy;
|
|
2606
|
+
const placeholder = !connected ? t.placeholderConnecting : props.placeholder ?? (props.agentName ? t.placeholder(props.agentName) : t.placeholderDefault);
|
|
2607
|
+
const showFiles = features.attachments ?? Boolean(props.upload);
|
|
2608
|
+
const [slowConnect, setSlowConnect] = useState9(false);
|
|
2609
|
+
useEffect6(() => {
|
|
2610
|
+
if (connected) return setSlowConnect(false);
|
|
2611
|
+
const id = setTimeout(() => setSlowConnect(true), 800);
|
|
2612
|
+
return () => clearTimeout(id);
|
|
2613
|
+
}, [connected]);
|
|
2614
|
+
const composer = /* @__PURE__ */ jsx10("div", { ref: composerBox, children: /* @__PURE__ */ jsx10(
|
|
2615
|
+
Composer,
|
|
2616
|
+
{
|
|
2617
|
+
initialValue: draft.text,
|
|
2618
|
+
onSubmit: send,
|
|
2619
|
+
onStop: pilot.stop,
|
|
2620
|
+
busy,
|
|
2621
|
+
canSteer: (external ? true : transport?.steer) !== false,
|
|
2622
|
+
placeholder,
|
|
2623
|
+
canSeeImages: state.canSeeImages,
|
|
2624
|
+
attachments: showFiles,
|
|
2625
|
+
upload: props.upload,
|
|
2626
|
+
transcribe: props.transcribe,
|
|
2627
|
+
voiceNoteText: props.voiceNoteText,
|
|
2628
|
+
models: features.models === false ? [] : state.models,
|
|
2629
|
+
currentModel: state.currentModel,
|
|
2630
|
+
onModelChange: pilot.setModel,
|
|
2631
|
+
references: refs,
|
|
2632
|
+
onRemoveReference: (id) => setRefs(refsRef.current.filter((r) => r.id !== id)),
|
|
2633
|
+
onActivateReference: props.onReferenceClick,
|
|
2634
|
+
extras: slots.composerExtras
|
|
2635
|
+
},
|
|
2636
|
+
draft.n
|
|
2637
|
+
) });
|
|
2638
|
+
const permissionCards = cards.map((p) => /* @__PURE__ */ jsx10(ConfirmCard, { request: p, onRespond: respond, onSettled: settle, onExpire: pilot.expirePermission }, p.id));
|
|
2639
|
+
return /* @__PURE__ */ jsx10(PilotoContext.Provider, { value: config, children: /* @__PURE__ */ jsx10("div", { ...rootProps(theme, props.skin), "data-busy": busy || void 0, className: `piloto ${props.className ?? ""}`, style: props.style, children: empty ? (
|
|
2640
|
+
// Si no cabe (panel bajo, saludo largo), se desplaza en vez de recortarse: el centrado es con
|
|
2641
|
+
// márgenes automáticos, que nunca empujan contenido fuera del contenedor.
|
|
2642
|
+
/* @__PURE__ */ jsxs8("div", { className: "piloto-empty", children: [
|
|
2643
|
+
slots.headerActions && /* @__PURE__ */ jsx10("div", { className: "absolute right-4 top-3 flex gap-2", children: slots.headerActions }),
|
|
2644
|
+
/* @__PURE__ */ jsxs8("div", { className: "piloto-empty-inner", children: [
|
|
2645
|
+
!connected && slowConnect ? /* @__PURE__ */ jsx10(ConnectingState, { phase, error, avatar: props.avatar }) : slots.emptyState ?? /* @__PURE__ */ jsxs8("div", { className: "piloto-welcome mb-6 flex flex-col items-center gap-3 text-center", children: [
|
|
2646
|
+
props.avatar,
|
|
2647
|
+
props.welcome && // Un saludo largo (una explicación) va en letra de lectura; uno corto, en la de cartel.
|
|
2648
|
+
/* @__PURE__ */ jsx10("div", { className: "piloto-welcome-text", "data-long": typeof props.welcome === "string" && props.welcome.length > 60 ? "" : void 0, children: props.welcome })
|
|
2649
|
+
] }),
|
|
2650
|
+
/* @__PURE__ */ jsxs8("div", { className: "w-full max-w-3xl", children: [
|
|
2651
|
+
slots.aboveComposer,
|
|
2652
|
+
composer,
|
|
2653
|
+
props.suggestions && /* @__PURE__ */ jsx10(Suggestions, { items: props.suggestions, onPick: (s) => send(s), onPrefill: (s) => setDraftState((d) => ({ text: s, n: d.n + 1 })) })
|
|
2654
|
+
] })
|
|
2655
|
+
] })
|
|
2656
|
+
] })
|
|
2657
|
+
) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
|
|
2658
|
+
slots.headerActions && /* @__PURE__ */ jsx10("div", { className: "flex shrink-0 justify-end gap-2 px-4 pt-3", children: slots.headerActions }),
|
|
2659
|
+
/* @__PURE__ */ jsxs8(
|
|
2660
|
+
"div",
|
|
2661
|
+
{
|
|
2662
|
+
ref: scroller,
|
|
2663
|
+
onScroll,
|
|
2664
|
+
onWheel: (e) => e.deltaY < 0 && release(),
|
|
2665
|
+
onTouchStart: (e) => {
|
|
2666
|
+
touchY.current = e.touches[0]?.clientY ?? null;
|
|
2667
|
+
},
|
|
2668
|
+
onTouchMove: (e) => {
|
|
2669
|
+
const y = e.touches[0]?.clientY;
|
|
2670
|
+
if (touchY.current != null && y != null && y > touchY.current + 4) release();
|
|
2671
|
+
},
|
|
2672
|
+
onKeyDown: (e) => ["ArrowUp", "PageUp", "Home"].includes(e.key) && release(),
|
|
2673
|
+
className: "piloto-scroll relative min-h-0 flex-1 overflow-y-auto overflow-x-hidden",
|
|
2674
|
+
children: [
|
|
2675
|
+
/* @__PURE__ */ jsxs8("div", { ref: column, className: "piloto-column mx-auto flex w-full flex-col gap-6 px-4 py-6", children: [
|
|
2676
|
+
turns.map((turn, i) => {
|
|
2677
|
+
const isLast = i === turns.length - 1;
|
|
2678
|
+
const live = busy && isLast;
|
|
2679
|
+
return /* @__PURE__ */ jsx10("div", { "data-turn": i, children: turn.role === "user" ? /* @__PURE__ */ jsx10(UserBubble, { turn }) : /* @__PURE__ */ jsx10(
|
|
2680
|
+
AgentBubble,
|
|
2681
|
+
{
|
|
2682
|
+
turn,
|
|
2683
|
+
live,
|
|
2684
|
+
onRetry: isLast && features.retry !== false ? pilot.retry : void 0,
|
|
2685
|
+
onOption: isLast && !busy && features.replyOptions !== false ? (text) => send(text) : void 0,
|
|
2686
|
+
showUsage: features.usage !== false,
|
|
2687
|
+
showCopy: features.copy !== false,
|
|
2688
|
+
renderDeliveries: slots.renderDeliveries
|
|
2689
|
+
}
|
|
2690
|
+
) }, i);
|
|
2691
|
+
}),
|
|
2692
|
+
permissionCards,
|
|
2693
|
+
busy && !toolRunning && !cards.length && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1.5", children: [
|
|
2694
|
+
/* @__PURE__ */ jsx10(TypingDots, {}),
|
|
2695
|
+
/* @__PURE__ */ jsx10(WaitLabel, { phase, since: state.turnStartedAt, elsewhere: state.elsewhere, onLost: pilot.abandonAndRetry, showDuration: features.duration !== false })
|
|
2696
|
+
] }),
|
|
2697
|
+
interrupted && !busy && /* @__PURE__ */ jsxs8("p", { className: "piloto-interrupted text-sm text-text-secondary", children: [
|
|
2698
|
+
/me quedé a medias|i stopped halfway/i.test(last?.text ?? "") ? t.interruptedOurFault : t.interrupted(interrupted.reason),
|
|
2699
|
+
" ",
|
|
2700
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: pilot.resume, className: "cursor-pointer text-text-primary underline underline-offset-2", children: interrupted.canContinue ? t.continueTurn : t.resume })
|
|
2701
|
+
] }),
|
|
2702
|
+
notice && /* @__PURE__ */ jsx10("p", { className: "piloto-notice text-sm text-text-tertiary", children: notice }),
|
|
2703
|
+
error && /* @__PURE__ */ jsx10("p", { className: "piloto-error text-sm text-text-danger", children: error })
|
|
2704
|
+
] }),
|
|
2705
|
+
/* @__PURE__ */ jsx10("div", { ref: spacer, "aria-hidden": true })
|
|
2706
|
+
]
|
|
2707
|
+
}
|
|
2708
|
+
),
|
|
2709
|
+
/* @__PURE__ */ jsxs8("div", { className: "piloto-dock relative mx-auto w-full px-4 pb-4", children: [
|
|
2710
|
+
!atBottom && /* @__PURE__ */ jsx10(
|
|
2711
|
+
"button",
|
|
2712
|
+
{
|
|
2713
|
+
type: "button",
|
|
2714
|
+
onClick: toBottom,
|
|
2715
|
+
"aria-label": t.goToLatest,
|
|
2716
|
+
className: "piloto-to-bottom absolute -top-12 left-1/2 z-10 flex h-9 w-9 -translate-x-1/2 items-center justify-center rounded-full border border-border-primary bg-background-secondary text-text-secondary shadow-(--piloto-shadow-card) transition hover:text-text-primary",
|
|
2717
|
+
children: /* @__PURE__ */ jsx10(ArrowDown, { className: "h-4 w-4" })
|
|
2718
|
+
}
|
|
2719
|
+
),
|
|
2720
|
+
slots.aboveComposer,
|
|
2721
|
+
composer
|
|
2722
|
+
] })
|
|
2723
|
+
] }) }) });
|
|
2724
|
+
});
|
|
2725
|
+
export {
|
|
2726
|
+
AgentBubble,
|
|
2727
|
+
AgentSteps,
|
|
2728
|
+
ClawIcon,
|
|
2729
|
+
Composer,
|
|
2730
|
+
ConfirmCard,
|
|
2731
|
+
ConnectingState,
|
|
2732
|
+
CopyUserMessage,
|
|
2733
|
+
DEFAULT_TOOL_ICONS,
|
|
2734
|
+
DecisionLines,
|
|
2735
|
+
DeliveryChips,
|
|
2736
|
+
InlineMarkdown,
|
|
2737
|
+
LinkSafetyModal,
|
|
2738
|
+
Markdown,
|
|
2739
|
+
ModelPicker,
|
|
2740
|
+
Narration,
|
|
2741
|
+
PilotHttpError,
|
|
2742
|
+
Piloto,
|
|
2743
|
+
PilotoContext,
|
|
2744
|
+
RESTART_NOTICE,
|
|
2745
|
+
RESUME_TEXT,
|
|
2746
|
+
ReferenceChips,
|
|
2747
|
+
ReplyOptionButtons,
|
|
2748
|
+
STRINGS_EN,
|
|
2749
|
+
STRINGS_ES,
|
|
2750
|
+
Suggestions,
|
|
2751
|
+
TOOL_VERBS_EN,
|
|
2752
|
+
TOOL_VERBS_ES,
|
|
2753
|
+
ToolRow,
|
|
2754
|
+
TurnFooter,
|
|
2755
|
+
TypingDots,
|
|
2756
|
+
UsageStats,
|
|
2757
|
+
UserBubble,
|
|
2758
|
+
WaitLabel,
|
|
2759
|
+
appendAgentMessages,
|
|
2760
|
+
applyHistory,
|
|
2761
|
+
applyReferences,
|
|
2762
|
+
beginSend,
|
|
2763
|
+
cleanReplyOption,
|
|
2764
|
+
cleanUserText,
|
|
2765
|
+
createPilotState,
|
|
2766
|
+
createPostStreamTransport,
|
|
2767
|
+
createSseTransport,
|
|
2768
|
+
Piloto as default,
|
|
2769
|
+
dropLastExchange,
|
|
2770
|
+
expiryMs,
|
|
2771
|
+
extractReplyOptions,
|
|
2772
|
+
formatDuration,
|
|
2773
|
+
groupSteps,
|
|
2774
|
+
isNarration,
|
|
2775
|
+
isRunning,
|
|
2776
|
+
lastUserTurn,
|
|
2777
|
+
mimeOf,
|
|
2778
|
+
narrationToMarkdown,
|
|
2779
|
+
readSse,
|
|
2780
|
+
reconcileTurns,
|
|
2781
|
+
reconcileWith,
|
|
2782
|
+
reducePilotEvent,
|
|
2783
|
+
renderInline,
|
|
2784
|
+
resolvePermission,
|
|
2785
|
+
resumeMode,
|
|
2786
|
+
sendAccepted,
|
|
2787
|
+
sendFailed,
|
|
2788
|
+
sendRejectedBusy,
|
|
2789
|
+
splitNarration,
|
|
2790
|
+
stepLabel,
|
|
2791
|
+
stopTurn,
|
|
2792
|
+
summarizeTools,
|
|
2793
|
+
systemNoteOf,
|
|
2794
|
+
toolName,
|
|
2795
|
+
usePilot,
|
|
2796
|
+
usePiloto,
|
|
2797
|
+
useSseTransport,
|
|
2798
|
+
useTextAtPace
|
|
2799
|
+
};
|