@vnl-works/agentjoy-react 0.1.2 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2,45 +2,6 @@
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
3
 
4
4
  // src/utils.ts
5
- function phaseLabel(phase, tone, locale = "ja") {
6
- const ja = {
7
- prepare: "\u6E96\u5099",
8
- research: "\u8ABF\u67FB",
9
- execute: "\u5B9F\u884C",
10
- format: "\u6574\u5F62",
11
- verify: "\u691C\u8A3C",
12
- finalize: "\u4ED5\u4E0A\u3052"
13
- };
14
- const en = {
15
- prepare: "Prepare",
16
- research: "Research",
17
- execute: "Execute",
18
- format: "Format",
19
- verify: "Verify",
20
- finalize: "Finalize"
21
- };
22
- const base = locale === "ja" ? ja[phase] : en[phase];
23
- return base;
24
- }
25
- function statusLabel(status, tone, locale = "ja") {
26
- if (locale === "en") {
27
- if (status === "running") return "Running";
28
- if (status === "completed") return "Completed";
29
- return "Failed";
30
- }
31
- const polite = { running: "\u5B9F\u884C\u4E2D", completed: "\u5B8C\u4E86", failed: "\u5931\u6557" };
32
- const formal = { running: "\u5B9F\u884C\u4E2D", completed: "\u5B8C\u4E86", failed: "\u5931\u6557" };
33
- const casual = { running: "\u5B9F\u884C\u4E2D", completed: "\u5B8C\u4E86", failed: "\u5931\u6557" };
34
- const map = tone === "formal" ? formal : tone === "casual" ? casual : polite;
35
- return map[status];
36
- }
37
- function formatTs(ts, locale = "ja") {
38
- try {
39
- return new Date(ts).toLocaleString(locale === "ja" ? "ja-JP" : "en-US");
40
- } catch {
41
- return ts;
42
- }
43
- }
44
5
  function avatarForPack(packId) {
45
6
  const m = {
46
7
  default: "\u{1F9ED}",
@@ -56,245 +17,739 @@ function clamp01(x) {
56
17
  if (x > 1) return 1;
57
18
  return x;
58
19
  }
20
+ function formatRelative(date, now = /* @__PURE__ */ new Date(), locale = "en") {
21
+ const d = date instanceof Date ? date : new Date(date);
22
+ const diffMs = now.getTime() - d.getTime();
23
+ const diffSec = Math.floor(diffMs / 1e3);
24
+ if (!Number.isFinite(diffSec)) return "";
25
+ if (diffSec < 0) return locale === "ja" ? "\u672A\u6765" : "in the future";
26
+ if (diffSec < 10) return locale === "ja" ? "\u305F\u3063\u305F\u4ECA" : "just now";
27
+ if (diffSec < 60) return locale === "ja" ? `${diffSec}\u79D2\u524D` : `${diffSec}s ago`;
28
+ const diffMin = Math.floor(diffSec / 60);
29
+ if (diffMin < 60) return locale === "ja" ? `${diffMin}\u5206\u524D` : `${diffMin}m ago`;
30
+ const diffHr = Math.floor(diffMin / 60);
31
+ if (diffHr < 24) return locale === "ja" ? `${diffHr}\u6642\u9593\u524D` : `${diffHr}h ago`;
32
+ const diffDay = Math.floor(diffHr / 24);
33
+ return locale === "ja" ? `${diffDay}\u65E5\u524D` : `${diffDay}d ago`;
34
+ }
35
+ var EVENT_LABELS = {
36
+ "run.created": "Run created",
37
+ "phase.started": "Phase started",
38
+ "phase.progress": "Progress",
39
+ "tool.called": "Tool called",
40
+ "tool.completed": "Tool completed",
41
+ "artifact.ready": "Artifact ready",
42
+ warning: "Warning",
43
+ error: "Error",
44
+ "run.completed": "Completed",
45
+ "run.failed": "Failed",
46
+ "system.comment": "Comment"
47
+ };
48
+ function humanizeEventType(t) {
49
+ const key = String(t);
50
+ if (EVENT_LABELS[key]) return EVENT_LABELS[key];
51
+ return key.split(".").map((s) => s ? s[0].toUpperCase() + s.slice(1) : s).join(" ");
52
+ }
53
+ function xpFloorForLevel(level, base = 40, inc = 20) {
54
+ const l = Math.max(1, Math.floor(level));
55
+ const n = l - 1;
56
+ return n * base + inc * n * (n - 1) / 2;
57
+ }
58
+ function levelFromXp(xp) {
59
+ const safeXp = Math.max(0, Math.floor(Number.isFinite(xp) ? xp : 0));
60
+ let level = 1;
61
+ while (level < 999 && safeXp >= xpFloorForLevel(level + 1)) level++;
62
+ return { level, xpFloor: xpFloorForLevel(level), xpCeil: xpFloorForLevel(level + 1) };
63
+ }
64
+ function hashString(s) {
65
+ let h = 0;
66
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
67
+ return Math.abs(h);
68
+ }
69
+ function pickFrom(list, key) {
70
+ if (list.length === 0) return "";
71
+ return list[hashString(key) % list.length];
72
+ }
73
+ function pickComment(mode, pack, eventType) {
74
+ const p = (pack || "default").toLowerCase();
75
+ const suffix = p.includes("cat") || p.includes("nyan") ? " \u306B\u3083" : "";
76
+ const professional = {
77
+ "tool.called": ["\u51E6\u7406\u3092\u958B\u59CB\u3057\u307E\u3057\u305F\u3002", "\u30C4\u30FC\u30EB\u3092\u547C\u3073\u51FA\u3057\u3066\u3044\u307E\u3059\u3002", "\u4F5C\u696D\u3092\u9032\u3081\u307E\u3059\u3002"],
78
+ "tool.completed": ["\u51E6\u7406\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002", "\u30C4\u30FC\u30EB\u306E\u5B9F\u884C\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002", "\u6B21\u306E\u5DE5\u7A0B\u306B\u9032\u307F\u307E\u3059\u3002"],
79
+ "artifact.ready": ["\u6210\u679C\u7269\u3092\u751F\u6210\u3057\u307E\u3057\u305F\u3002", "\u51FA\u529B\u3092\u6E96\u5099\u3057\u307E\u3057\u305F\u3002", "\u78BA\u8A8D\u3067\u304D\u308B\u72B6\u614B\u306B\u306A\u308A\u307E\u3057\u305F\u3002"],
80
+ "phase.progress": ["\u9032\u6357\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F\u3002", "\u9806\u8ABF\u306B\u9032\u3093\u3067\u3044\u307E\u3059\u3002", "\u5F15\u304D\u7D9A\u304D\u51E6\u7406\u4E2D\u3067\u3059\u3002"],
81
+ warning: ["\u6CE8\u610F\u70B9\u304C\u3042\u308A\u307E\u3059\u3002", "\u8EFD\u5FAE\u306A\u554F\u984C\u304C\u691C\u51FA\u3055\u308C\u307E\u3057\u305F\u3002", "\u5FF5\u306E\u305F\u3081\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"],
82
+ error: ["\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002", "\u51E6\u7406\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002", "\u539F\u56E0\u3092\u78BA\u8A8D\u3057\u3066\u3044\u307E\u3059\u3002"],
83
+ "run.completed": ["\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002\u304A\u75B2\u308C\u3055\u307E\u3067\u3057\u305F\u3002", "\u6B63\u5E38\u306B\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002", "\u5B9F\u884C\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\u3002"],
84
+ "run.failed": ["\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u539F\u56E0\u3092\u78BA\u8A8D\u3057\u307E\u3059\u3002", "\u51E6\u7406\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3057\u305F\u3002", "\u518D\u5B9F\u884C\u307E\u305F\u306F\u8A2D\u5B9A\u78BA\u8A8D\u3092\u63A8\u5968\u3057\u307E\u3059\u3002"]
85
+ };
86
+ const playful = {
87
+ "tool.called": ["\u3084\u3063\u3066\u307F\u308B\u306D\uFF01", "\u30C4\u30FC\u30EB\u547C\u3076\u3088\u301C", "\u3044\u304F\u3088\u3063\uFF01"],
88
+ "tool.completed": ["\u3067\u304D\u305F\uFF01", "\u5B8C\u4E86\u301C\uFF01", "\u3046\u307E\u304F\u3044\u3063\u305F\uFF01"],
89
+ "artifact.ready": ["\u3067\u304D\u3042\u304C\u308A\uFF01", "\u6210\u679C\u7269\u3067\u304D\u305F\u3088\uFF01", "\u307B\u3044\u3063\uFF01"],
90
+ "phase.progress": ["\u9032\u3093\u3067\u308B\u3088\u301C", "\u3044\u3044\u611F\u3058\uFF01", "\u3082\u3046\u5C11\u3057\uFF01"],
91
+ warning: ["\u3061\u3087\u3063\u3068\u602A\u3057\u3044\u304B\u3082", "\u6CE8\u610F\u3060\u3088\uFF01", "\u78BA\u8A8D\u3057\u3066\u306D"],
92
+ error: ["\u3046\u308F\u3063\u3001\u30A8\u30E9\u30FC\uFF01", "\u3084\u3089\u304B\u3057\u305F\u2026", "\u3054\u3081\u3093\u3001\u5931\u6557\uFF01"],
93
+ "run.completed": ["\u3084\u308A\u5207\u3063\u305F\uFF01\u{1F389}", "\u5B8C\u4E86\u301C\uFF01", "\u304A\u3064\u304B\u308C\u3055\u307E\uFF01"],
94
+ "run.failed": ["\u3080\u3080\u2026\u3082\u3046\u4E00\u56DE\u3044\u304F\uFF1F", "\u5931\u6557\u3057\u3061\u3083\u3063\u305F\u2026", "\u6B21\u306F\u3046\u307E\u304F\u3084\u308B\uFF01"]
95
+ };
96
+ const dict = mode === "playful" ? playful : professional;
97
+ const candidates = dict[eventType] || dict["phase.progress"] || ["..."];
98
+ return pickFrom(candidates, `${mode}:${pack}:${eventType}`) + suffix;
99
+ }
59
100
 
60
101
  // src/AgentJoyRun.tsx
61
- import { jsx, jsxs } from "react/jsx-runtime";
62
- function xpForEvent(e) {
63
- switch (e.type) {
64
- case "phase.started":
65
- return 18;
66
- case "phase.progress":
67
- return 6;
68
- case "tool.called":
69
- return 10;
70
- case "tool.completed":
71
- return 12;
72
- case "warning":
73
- return 2;
74
- case "error":
75
- case "run.failed":
76
- return 1;
77
- case "run.completed":
78
- return 25;
79
- case "system.comment":
80
- return 0;
81
- default:
82
- return 4;
102
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
103
+ function safeOrigin() {
104
+ try {
105
+ if (typeof window !== "undefined" && window.location?.origin) return window.location.origin;
106
+ } catch {
83
107
  }
108
+ return "";
84
109
  }
85
- function useBeep() {
86
- const ctxRef = useRef(null);
87
- function ensure() {
88
- if (!ctxRef.current) {
89
- const AC = window.AudioContext || window.webkitAudioContext;
90
- if (AC) ctxRef.current = new AC();
91
- }
92
- return ctxRef.current;
93
- }
94
- function tone(freq, ms) {
95
- const ctx = ensure();
96
- if (!ctx) return;
97
- const o = ctx.createOscillator();
98
- const g = ctx.createGain();
99
- o.type = "sine";
100
- o.frequency.value = freq;
101
- g.gain.value = 0.03;
102
- o.connect(g);
103
- g.connect(ctx.destination);
104
- o.start();
105
- setTimeout(() => {
106
- o.stop();
107
- o.disconnect();
108
- g.disconnect();
109
- }, ms);
110
- }
111
- function success() {
112
- tone(740, 90);
113
- setTimeout(() => tone(880, 120), 110);
114
- }
115
- function error() {
116
- tone(220, 200);
117
- }
118
- function tick() {
119
- tone(520, 55);
110
+ function isImportantEvent(e) {
111
+ return e.type === "warning" || e.type === "error" || e.type === "run.failed" || e.type === "run.completed" || e.type === "artifact.ready";
112
+ }
113
+ function isToolEvent(e) {
114
+ return e.type.startsWith("tool.") || !!e.tool;
115
+ }
116
+ function isArtifactEvent(e) {
117
+ return e.type === "artifact.ready" || !!e.artifacts && e.artifacts.length > 0;
118
+ }
119
+ function eventSearchText(e) {
120
+ const parts = [];
121
+ parts.push(e.type || "");
122
+ parts.push(e.message || "");
123
+ parts.push(e.detail || "");
124
+ if (e.tool?.name) parts.push(e.tool.name);
125
+ if (e.tool?.input_summary) parts.push(e.tool.input_summary);
126
+ if (e.tool?.output_summary) parts.push(e.tool.output_summary);
127
+ if (e.artifacts?.length) {
128
+ for (const a of e.artifacts) parts.push(`${a.label ?? ""} ${a.value ?? ""}`.trim());
120
129
  }
121
- return { tick, success, error };
130
+ return parts.join(" ").toLowerCase();
122
131
  }
132
+ var DEMO_SCRIPT = [
133
+ { type: "run.created", message: "AgentJoy demo: watching an AI agent work\u2026", progress: 0.02 },
134
+ {
135
+ type: "phase.started",
136
+ message: "Phase: research",
137
+ progress: 0.08
138
+ },
139
+ {
140
+ type: "tool.called",
141
+ message: "Searching docs for pricing benchmarks\u2026",
142
+ progress: 0.15,
143
+ tool: { name: "web.search", input_summary: "agent monitoring widget pricing", latency_ms: 320 }
144
+ },
145
+ {
146
+ type: "tool.completed",
147
+ message: "Summarized 6 sources. Drafting plan\u2026",
148
+ progress: 0.32,
149
+ tool: { name: "web.search", output_summary: "key patterns + positioning", latency_ms: 1420 }
150
+ },
151
+ {
152
+ type: "phase.progress",
153
+ message: "Writing LP copy (hero + CTA + FAQ)\u2026",
154
+ progress: 0.52
155
+ },
156
+ {
157
+ type: "warning",
158
+ message: "Minor: rate limited once, retrying gracefully.",
159
+ progress: 0.62
160
+ },
161
+ {
162
+ type: "artifact.ready",
163
+ message: "LP copy draft is ready.",
164
+ progress: 0.82,
165
+ artifacts: [
166
+ { label: "Landing page copy", kind: "url", value: "https://example.com/agentjoy-demo-lp" },
167
+ { label: "Pricing bullets", kind: "text", value: "Starter / Pro / Team tiers" }
168
+ ]
169
+ },
170
+ {
171
+ type: "phase.progress",
172
+ message: "Final checks\u2026",
173
+ progress: 0.94
174
+ },
175
+ {
176
+ type: "run.completed",
177
+ message: "Done. Ready to ship \u{1F680}",
178
+ progress: 1
179
+ }
180
+ ];
123
181
  function AgentJoyRun(props) {
124
- const apiBaseUrl = props.apiBaseUrl ?? "";
125
- const locale = props.locale ?? "ja";
126
- const limit = props.limit ?? 200;
127
- const { tick, success, error } = useBeep();
182
+ const apiBase = (props.apiBaseUrl ?? "").replace(/\/$/, "");
183
+ const isDemo = !!props.demo;
128
184
  const [run, setRun] = useState(null);
129
- const [events, setEvents] = useState([]);
130
185
  const [brand, setBrand] = useState(null);
131
- const [muted, setMuted] = useState(props.mute ?? true);
132
- const esRef = useRef(null);
186
+ const [events, setEvents] = useState([]);
187
+ const [loading, setLoading] = useState(true);
188
+ const [loadError, setLoadError] = useState(null);
189
+ const [conn, setConn] = useState("idle");
190
+ const [muted, setMuted] = useState(props.mute ?? false);
191
+ const [soundEnabled, setSoundEnabled] = useState(props.sound ?? true);
192
+ const [xp, setXp] = useState(0);
193
+ const listRef = useRef(null);
194
+ const [stickToBottom, setStickToBottom] = useState(true);
195
+ const audioCtxRef = useRef(null);
196
+ const lastBeepAtRef = useRef(0);
197
+ const seenIdsRef = useRef(/* @__PURE__ */ new Set());
198
+ const lastSeenEventIdRef = useRef(null);
199
+ const maxEvents = props.maxEvents ?? 5e3;
200
+ const autoScroll = props.autoScroll ?? true;
201
+ const compact = props.compact ?? false;
202
+ const showHeader = props.showHeader ?? true;
203
+ const showControls = props.showControls ?? true;
204
+ const showToolbar = props.showToolbar ?? true;
205
+ const pollIntervalMs = props.pollIntervalMs ?? (isDemo ? 0 : 8e3);
206
+ const showXp = props.showXp ?? (brand?.show_xp ?? true);
207
+ const showPersona = props.showPersona ?? (brand?.show_persona ?? true);
208
+ const locale = props.locale ?? (brand?.locale ?? "en");
209
+ const [filter, setFilter] = useState(props.defaultFilter ?? "all");
210
+ const [query, setQuery] = useState("");
133
211
  useEffect(() => {
134
- let cancelled = false;
135
- async function load() {
136
- const url = `${apiBaseUrl}/v1/runs/${encodeURIComponent(props.runId)}?token=${encodeURIComponent(props.token)}&limit=2000`;
137
- const res = await fetch(url);
138
- if (!res.ok) throw new Error(await res.text());
139
- const data = await res.json();
140
- if (cancelled) return;
141
- setRun(data.run);
142
- setEvents(data.events || []);
143
- const mergedBrand = { ...data.workspace_brand, ...props.brandOverride || {} };
144
- setBrand(mergedBrand);
145
- setMuted(props.mute ?? !!mergedBrand.mute_default);
212
+ if (props.mute !== void 0) setMuted(props.mute);
213
+ }, [props.mute]);
214
+ function resetEvents(next) {
215
+ const trimmed = next.length > maxEvents ? next.slice(next.length - maxEvents) : next;
216
+ seenIdsRef.current = new Set(trimmed.map((e) => String(e.id)));
217
+ let lastId = null;
218
+ for (let i = trimmed.length - 1; i >= 0; i--) {
219
+ const idAny = trimmed[i]?.id;
220
+ if (typeof idAny === "number" && Number.isFinite(idAny)) {
221
+ lastId = idAny;
222
+ break;
223
+ }
146
224
  }
147
- load().catch(() => {
225
+ lastSeenEventIdRef.current = lastId;
226
+ setEvents(trimmed);
227
+ }
228
+ function appendEvent(ev) {
229
+ if (ev == null || ev.id == null) return;
230
+ const evId = String(ev.id);
231
+ if (seenIdsRef.current.has(evId)) return;
232
+ seenIdsRef.current.add(evId);
233
+ if (typeof ev.id === "number" && Number.isFinite(ev.id)) {
234
+ lastSeenEventIdRef.current = ev.id;
235
+ }
236
+ if (ev.type === "run.completed") {
237
+ setRun((r) => r ? { ...r, status: "completed", ended_at: r.ended_at ?? ev.ts } : r);
238
+ } else if (ev.type === "run.failed") {
239
+ setRun((r) => r ? { ...r, status: "failed", ended_at: r.ended_at ?? ev.ts } : r);
240
+ }
241
+ setEvents((prev) => {
242
+ const next = [...prev, ev];
243
+ if (next.length <= maxEvents) return next;
244
+ return next.slice(next.length - maxEvents);
148
245
  });
149
- return () => {
150
- cancelled = true;
151
- };
152
- }, [apiBaseUrl, props.runId, props.token]);
153
- useEffect(() => {
154
- const url = `${apiBaseUrl}/v1/runs/${encodeURIComponent(props.runId)}/stream?token=${encodeURIComponent(props.token)}`;
155
- const es = new EventSource(url);
156
- esRef.current = es;
157
- es.onmessage = (msg) => {
246
+ }
247
+ async function fetchPublic(opts = {}) {
248
+ if (!props.runId || !props.token) {
249
+ setLoadError("Missing runId or token (or use `<AgentJoyRun demo />` to try instantly).");
250
+ setLoading(false);
251
+ return;
252
+ }
253
+ const limit = Math.max(1, Math.min(5e3, maxEvents));
254
+ const qs = new URLSearchParams();
255
+ qs.set("token", props.token);
256
+ qs.set("limit", String(limit));
257
+ const id = encodeURIComponent(props.runId);
258
+ const urls = [
259
+ `${apiBase}/v1/runs/${id}/public?${qs.toString()}`,
260
+ `${apiBase}/v1/runs/${id}?${qs.toString()}`
261
+ ];
262
+ let lastErr = null;
263
+ for (const url of urls) {
158
264
  try {
159
- const e = JSON.parse(msg.data);
160
- setEvents((prev) => [...prev, e]);
161
- if (e.type === "run.completed") {
162
- setRun((r) => r ? { ...r, status: "completed", ended_at: e.ts } : r);
265
+ const res = await fetch(url);
266
+ if (!res.ok) {
267
+ const t = await res.text().catch(() => "");
268
+ lastErr = new Error(`${res.status} ${res.statusText}${t ? `: ${t}` : ""}`);
269
+ continue;
163
270
  }
164
- if (e.type === "run.failed") {
165
- setRun((r) => r ? { ...r, status: "failed", ended_at: e.ts } : r);
271
+ const r = await res.json();
272
+ if (!r?.run) {
273
+ lastErr = new Error("Invalid response from AgentJoy API.");
274
+ continue;
166
275
  }
167
- if (props.sound && !muted) {
168
- if (e.type === "phase.started") tick();
169
- if (e.type === "run.completed") success();
170
- if (e.type === "run.failed" || e.type === "error") error();
276
+ setRun(r.run);
277
+ setBrand(r.workspace_brand);
278
+ if (opts.reset) {
279
+ resetEvents(r.events ?? []);
280
+ } else if (r.events?.length) {
281
+ for (const e of r.events) appendEvent(e);
171
282
  }
172
- } catch {
283
+ if (props.mute === void 0) setMuted(!!r.workspace_brand?.mute_default);
284
+ return;
285
+ } catch (e) {
286
+ lastErr = e;
287
+ }
288
+ }
289
+ throw lastErr ?? new Error("Failed to load run detail.");
290
+ }
291
+ async function fetchEventsAfter(afterId) {
292
+ if (!props.runId || !props.token) return;
293
+ const safeAfter = typeof afterId === "number" && Number.isFinite(afterId) ? afterId : null;
294
+ if (safeAfter === null) {
295
+ await fetchPublic({ reset: false });
296
+ return;
297
+ }
298
+ const qs = new URLSearchParams();
299
+ qs.set("token", props.token);
300
+ qs.set("after_id", String(safeAfter));
301
+ qs.set("limit", "1000");
302
+ const url = `${apiBase}/v1/runs/${encodeURIComponent(props.runId)}/events?${qs.toString()}`;
303
+ const res = await fetch(url);
304
+ if (!res.ok) {
305
+ if (res.status === 404 || res.status === 405) {
306
+ await fetchPublic({ reset: false });
307
+ return;
173
308
  }
309
+ const t = await res.text().catch(() => "");
310
+ throw new Error(`${res.status} ${res.statusText}${t ? `: ${t}` : ""}`);
311
+ }
312
+ const data = await res.json();
313
+ const evs = Array.isArray(data?.events) ? data.events : [];
314
+ if (evs.length) {
315
+ for (const e of evs) appendEvent(e);
316
+ }
317
+ }
318
+ async function load() {
319
+ if (isDemo) return;
320
+ setLoading(true);
321
+ setLoadError(null);
322
+ try {
323
+ await fetchPublic({ reset: true });
324
+ } catch (e) {
325
+ setLoadError(e?.message ?? String(e));
326
+ } finally {
327
+ setLoading(false);
328
+ }
329
+ }
330
+ useEffect(() => {
331
+ if (!isDemo) return;
332
+ setLoading(false);
333
+ setLoadError(null);
334
+ setConn("open");
335
+ const demoBrand = {
336
+ product_name: "AgentJoy",
337
+ show_xp: true,
338
+ show_persona: true,
339
+ mute_default: false,
340
+ tone: "playful"
174
341
  };
175
- es.onerror = () => {
342
+ const demoRun = {
343
+ id: "demo",
344
+ title: "Demo: AgentJoy Widget",
345
+ status: "running",
346
+ mode: "playful",
347
+ pack: "nyan",
348
+ share_token: ""
176
349
  };
350
+ setBrand(demoBrand);
351
+ setRun(demoRun);
352
+ resetEvents([]);
353
+ let i = 0;
354
+ const speed = Math.max(200, props.demoSpeedMs ?? 700);
355
+ const timer = window.setInterval(() => {
356
+ const src = DEMO_SCRIPT[i];
357
+ if (!src) return;
358
+ const ev = {
359
+ id: `demo-${i + 1}`,
360
+ ts: new Date(Date.now() + i * speed).toISOString(),
361
+ ...src
362
+ };
363
+ appendEvent(ev);
364
+ if (src.type === "run.completed") {
365
+ setRun((r) => r ? { ...r, status: "completed" } : r);
366
+ }
367
+ i += 1;
368
+ if (i >= DEMO_SCRIPT.length) window.clearInterval(timer);
369
+ }, speed);
370
+ return () => window.clearInterval(timer);
371
+ }, [props.demo]);
372
+ useEffect(() => {
373
+ if (isDemo) return;
374
+ load();
375
+ }, [apiBase, props.runId, props.token, isDemo]);
376
+ useEffect(() => {
377
+ const total = events.reduce((acc, e) => {
378
+ if (e.type === "tool.completed") return acc + 8;
379
+ if (e.type === "artifact.ready") return acc + 12;
380
+ if (e.type === "warning") return acc + 2;
381
+ if (e.type === "error" || e.type === "run.failed") return acc + 1;
382
+ if (e.type === "phase.progress") return acc + 1;
383
+ return acc;
384
+ }, 0);
385
+ setXp(total);
386
+ }, [events]);
387
+ const level = useMemo(() => levelFromXp(xp), [xp]);
388
+ const shareUrl = useMemo(() => {
389
+ if (props.shareUrl) return props.shareUrl;
390
+ if (run?.share_token) {
391
+ const origin = apiBase ? apiBase.replace(/\/$/, "") : safeOrigin();
392
+ return `${origin}/share/${run.share_token}`;
393
+ }
394
+ return null;
395
+ }, [props.shareUrl, run?.share_token, apiBase]);
396
+ function onScroll() {
397
+ const el = listRef.current;
398
+ if (!el) return;
399
+ const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 40;
400
+ setStickToBottom(nearBottom);
401
+ }
402
+ function scrollToBottom() {
403
+ const el = listRef.current;
404
+ if (!el) return;
405
+ el.scrollTop = el.scrollHeight;
406
+ setStickToBottom(true);
407
+ }
408
+ useEffect(() => {
409
+ if (!autoScroll) return;
410
+ if (!stickToBottom) return;
411
+ const el = listRef.current;
412
+ if (!el) return;
413
+ el.scrollTop = el.scrollHeight;
414
+ }, [events.length, stickToBottom, autoScroll]);
415
+ function ensureAudio() {
416
+ if (audioCtxRef.current) return audioCtxRef.current;
417
+ const AC = window?.AudioContext || window?.webkitAudioContext;
418
+ if (!AC) return null;
419
+ try {
420
+ audioCtxRef.current = new AC();
421
+ } catch {
422
+ return null;
423
+ }
424
+ return audioCtxRef.current;
425
+ }
426
+ function beep(freq) {
427
+ if (!soundEnabled || muted) return;
428
+ if (typeof window === "undefined") return;
429
+ const now = Date.now();
430
+ if (now - lastBeepAtRef.current < 120) return;
431
+ lastBeepAtRef.current = now;
432
+ const ac = ensureAudio();
433
+ if (!ac) return;
434
+ try {
435
+ const osc = ac.createOscillator();
436
+ const gain = ac.createGain();
437
+ osc.type = "sine";
438
+ osc.frequency.value = freq;
439
+ gain.gain.value = 0.02;
440
+ osc.connect(gain);
441
+ gain.connect(ac.destination);
442
+ osc.start();
443
+ osc.stop(ac.currentTime + 0.08);
444
+ } catch {
445
+ }
446
+ }
447
+ useEffect(() => {
448
+ if (isDemo) return;
449
+ if (!run) return;
450
+ if (run.status !== "running") return;
451
+ if (!props.runId || !props.token) return;
452
+ const runId = props.runId;
453
+ const token = props.token;
454
+ const buildUrl = () => {
455
+ const qs = new URLSearchParams();
456
+ qs.set("token", token);
457
+ const afterId = lastSeenEventIdRef.current;
458
+ if (typeof afterId === "number" && Number.isFinite(afterId)) {
459
+ qs.set("after_id", String(afterId));
460
+ }
461
+ return `${apiBase}/v1/runs/${encodeURIComponent(runId)}/stream?${qs.toString()}`;
462
+ };
463
+ let closed = false;
464
+ let es = null;
465
+ let retry = 0;
466
+ let timer = null;
467
+ const connect = () => {
468
+ if (closed) return;
469
+ setConn(retry === 0 ? "connecting" : "reconnecting");
470
+ try {
471
+ es = new EventSource(buildUrl());
472
+ } catch {
473
+ setConn("error");
474
+ return;
475
+ }
476
+ es.onopen = () => {
477
+ retry = 0;
478
+ setConn("open");
479
+ };
480
+ es.onerror = () => {
481
+ if (closed) return;
482
+ setConn("reconnecting");
483
+ try {
484
+ es?.close();
485
+ } catch {
486
+ }
487
+ es = null;
488
+ retry = Math.min(retry + 1, 8);
489
+ const base = Math.min(3e4, 500 * Math.pow(2, retry));
490
+ const jitter = Math.floor(Math.random() * 250);
491
+ timer = window.setTimeout(connect, base + jitter);
492
+ };
493
+ es.onmessage = (msg) => {
494
+ try {
495
+ const payload = JSON.parse(msg.data);
496
+ const ev = payload && payload.event ? payload.event : payload;
497
+ if (!ev || ev.id == null) return;
498
+ props.onEvent?.(ev);
499
+ appendEvent(ev);
500
+ if (ev.type === "warning") beep(420);
501
+ else if (ev.type === "error" || ev.type === "run.failed") beep(180);
502
+ else if (ev.type === "artifact.ready") beep(660);
503
+ else if (ev.type === "tool.completed") beep(520);
504
+ } catch {
505
+ }
506
+ };
507
+ };
508
+ connect();
177
509
  return () => {
178
- es.close();
179
- esRef.current = null;
510
+ closed = true;
511
+ if (timer) window.clearTimeout(timer);
512
+ try {
513
+ es?.close();
514
+ } catch {
515
+ }
180
516
  };
181
- }, [apiBaseUrl, props.runId, props.token, props.sound, muted]);
517
+ }, [apiBase, props.runId, props.token, isDemo, run?.status]);
182
518
  useEffect(() => {
519
+ if (isDemo) return;
520
+ if (!props.runId || !props.token) return;
183
521
  if (!run) return;
184
- if (run.status === "completed") props.onComplete?.(run);
185
- if (run.status === "failed") props.onError?.(run);
186
- }, [run?.status]);
187
- const resolvedBrand = useMemo(() => {
188
- if (!brand) return null;
189
- return { ...brand, ...props.brandOverride || {} };
190
- }, [brand, props.brandOverride]);
191
- const showXp = props.showXp ?? resolvedBrand?.show_xp ?? true;
192
- const showPersona = props.showPersona ?? resolvedBrand?.show_persona ?? true;
193
- const filtered = useMemo(() => {
194
- const arr = showPersona ? events : events.filter((e) => e.type !== "system.comment");
195
- return arr.slice(-limit);
196
- }, [events, limit, showPersona]);
197
- const currentPhase = useMemo(() => {
198
- for (let i = events.length - 1; i >= 0; i--) {
199
- const p = events[i].phase;
200
- if (p) return p;
522
+ if (pollIntervalMs <= 0) return;
523
+ let stopped = false;
524
+ const tick = async () => {
525
+ if (stopped) return;
526
+ try {
527
+ await fetchEventsAfter(lastSeenEventIdRef.current);
528
+ } catch {
529
+ }
530
+ };
531
+ tick();
532
+ const id = window.setInterval(tick, pollIntervalMs);
533
+ return () => {
534
+ stopped = true;
535
+ window.clearInterval(id);
536
+ };
537
+ }, [apiBase, props.runId, props.token, isDemo, run?.status, pollIntervalMs]);
538
+ async function copyToClipboard(text) {
539
+ try {
540
+ await navigator.clipboard.writeText(text);
541
+ beep(740);
542
+ } catch {
201
543
  }
202
- return "prepare";
203
- }, [events]);
204
- const lastProgress = useMemo(() => {
205
- for (let i = events.length - 1; i >= 0; i--) {
206
- const p = clamp01(events[i].progress ?? null);
207
- if (typeof p === "number") return p;
544
+ }
545
+ function downloadJsonl() {
546
+ try {
547
+ const lines = events.map((e) => JSON.stringify(e));
548
+ const blob = new Blob([lines.join("\n") + "\n"], { type: "application/x-ndjson" });
549
+ const url = URL.createObjectURL(blob);
550
+ const a = document.createElement("a");
551
+ a.href = url;
552
+ a.download = `agentjoy-${run?.id ?? "run"}-events.jsonl`;
553
+ document.body.appendChild(a);
554
+ a.click();
555
+ a.remove();
556
+ URL.revokeObjectURL(url);
557
+ } catch {
208
558
  }
209
- return null;
210
- }, [events]);
211
- const progressPct = useMemo(() => {
212
- if (typeof lastProgress === "number") return Math.round(lastProgress * 100);
213
- const order = ["prepare", "research", "execute", "format", "verify", "finalize"];
214
- const idx = Math.max(0, order.indexOf(currentPhase));
215
- return Math.round((idx + 0.4) / order.length * 100);
216
- }, [currentPhase, lastProgress]);
217
- const xp = useMemo(() => events.reduce((acc, e) => acc + xpForEvent(e), 0), [events]);
218
- const level = Math.floor(xp / 250) + 1;
219
- const title = resolvedBrand?.product_name || "AgentJoy";
220
- const tone = resolvedBrand?.tone || "polite";
221
- const status = run?.status || "running";
222
- const rootStyle = {
223
- // @ts-ignore CSS variable typing
224
- ["--aj-primary"]: resolvedBrand?.primary_color || "#4F46E5"
225
- };
226
- if (!run) {
227
- return /* @__PURE__ */ jsx("div", { className: "ajw-root", style: rootStyle, children: /* @__PURE__ */ jsxs("div", { className: "ajw-card", children: [
228
- /* @__PURE__ */ jsxs("div", { className: "ajw-header", children: [
229
- /* @__PURE__ */ jsx("div", { className: "ajw-logo", children: "AJ" }),
230
- /* @__PURE__ */ jsxs("div", { children: [
231
- /* @__PURE__ */ jsx("div", { className: "ajw-title", children: title }),
232
- /* @__PURE__ */ jsx("div", { className: "ajw-sub", children: "loading\u2026" })
233
- ] })
234
- ] }),
235
- /* @__PURE__ */ jsx("div", { className: "ajw-body", children: /* @__PURE__ */ jsxs("div", { className: "ajw-event ajw-eventSystem", children: [
236
- /* @__PURE__ */ jsx("div", { className: "ajw-eventMsg", children: "Loading run\u2026" }),
237
- /* @__PURE__ */ jsx("div", { className: "ajw-eventMeta", children: /* @__PURE__ */ jsxs("span", { className: "ajw-mono", children: [
238
- "runId=",
239
- props.runId
240
- ] }) })
241
- ] }) })
559
+ }
560
+ const title = run?.title || brand?.product_name || "AgentJoy";
561
+ const progressRaw = clamp01(Math.max(...events.map((e) => e.progress ?? 0), 0));
562
+ const progressValue = progressRaw ?? 0;
563
+ const progressPct = Math.round(progressValue * 100);
564
+ const progressLabel = progressRaw === null ? "\u2014" : `${progressPct}%`;
565
+ const lastEventAt = (() => {
566
+ if (!events.length) return null;
567
+ const ts = events[events.length - 1]?.ts;
568
+ return ts ? new Date(ts) : null;
569
+ })();
570
+ const statusLabel = (() => {
571
+ if (!run) return "loading";
572
+ if (run.status === "running") return conn === "open" ? "running" : conn;
573
+ return run.status;
574
+ })();
575
+ const persona = useMemo(() => {
576
+ const pack = run?.pack || "default";
577
+ return avatarForPack(pack);
578
+ }, [run?.pack]);
579
+ const comment = useMemo(() => {
580
+ if (!showPersona) return null;
581
+ const last = events[events.length - 1];
582
+ if (!last) return null;
583
+ if (last.type === "system.comment") return last.message;
584
+ return pickComment(run?.mode || "professional", run?.pack || "default", last.type);
585
+ }, [events, run?.mode, run?.pack, showPersona]);
586
+ const listMaxHeight = props.height ?? 360;
587
+ const normalizedQuery = query.trim().toLowerCase();
588
+ const visibleEvents = useMemo(() => {
589
+ let base = events;
590
+ if (filter === "important") base = base.filter(isImportantEvent);
591
+ if (filter === "tools") base = base.filter(isToolEvent);
592
+ if (filter === "artifacts") base = base.filter(isArtifactEvent);
593
+ if (normalizedQuery) {
594
+ base = base.filter((e) => eventSearchText(e).includes(normalizedQuery));
595
+ }
596
+ return base;
597
+ }, [events, filter, normalizedQuery]);
598
+ if (!isDemo && (!props.runId || !props.token)) {
599
+ return /* @__PURE__ */ jsx("div", { className: "ajw-wrap", children: /* @__PURE__ */ jsxs("div", { className: "ajw-error", children: [
600
+ /* @__PURE__ */ jsx("div", { className: "ajw-error-title", children: "Missing configuration" }),
601
+ /* @__PURE__ */ jsxs("div", { className: "ajw-error-detail", children: [
602
+ "Provide ",
603
+ /* @__PURE__ */ jsx("code", { children: "runId" }),
604
+ " and ",
605
+ /* @__PURE__ */ jsx("code", { children: "token" }),
606
+ " (live mode), or use ",
607
+ /* @__PURE__ */ jsx("code", { children: "<AgentJoyRun demo />" }),
608
+ " ",
609
+ "to try instantly."
610
+ ] })
242
611
  ] }) });
243
612
  }
244
- return /* @__PURE__ */ jsx("div", { className: "ajw-root", style: rootStyle, children: /* @__PURE__ */ jsxs("div", { className: "ajw-card", children: [
245
- /* @__PURE__ */ jsxs("div", { className: "ajw-header", children: [
246
- /* @__PURE__ */ jsx("div", { className: "ajw-logo", children: resolvedBrand?.logo_url ? /* @__PURE__ */ jsx("img", { src: resolvedBrand.logo_url, style: { width: 28, height: 28, objectFit: "cover" } }) : "AJ" }),
247
- /* @__PURE__ */ jsxs("div", { children: [
613
+ return /* @__PURE__ */ jsxs("div", { className: `ajw-wrap ${compact ? "ajw-compact" : ""}`, "data-locale": locale, children: [
614
+ showHeader && /* @__PURE__ */ jsxs("div", { className: "ajw-header", children: [
615
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
248
616
  /* @__PURE__ */ jsx("div", { className: "ajw-title", children: title }),
249
- /* @__PURE__ */ jsxs("div", { className: "ajw-sub", children: [
250
- statusLabel(status, tone, locale),
251
- " \xB7 ",
252
- phaseLabel(currentPhase, tone, locale),
253
- " \xB7 ",
254
- progressPct,
255
- "%"
256
- ] })
617
+ /* @__PURE__ */ jsx("span", { className: `ajw-pill ajw-pill-${statusLabel}`, children: statusLabel })
257
618
  ] }),
258
- /* @__PURE__ */ jsx("div", { className: "ajw-actions", children: /* @__PURE__ */ jsx("button", { className: "ajw-btn", onClick: () => setMuted((m) => !m), title: "mute/unmute", children: muted ? "\u{1F507}" : "\u{1F50A}" }) })
619
+ /* @__PURE__ */ jsxs("div", { className: "ajw-sub", children: [
620
+ lastEventAt ? `last: ${formatRelative(lastEventAt, /* @__PURE__ */ new Date(), locale)}` : "waiting for events",
621
+ autoScroll && !stickToBottom ? " \u2022 scroll paused" : ""
622
+ ] })
259
623
  ] }),
260
- /* @__PURE__ */ jsxs("div", { className: "ajw-body", children: [
261
- /* @__PURE__ */ jsxs("div", { className: "ajw-progressRow", children: [
262
- /* @__PURE__ */ jsx("div", { className: "ajw-progressBar", "aria-label": "progress", children: /* @__PURE__ */ jsx("div", { style: { width: `${progressPct}%` } }) }),
263
- showXp ? /* @__PURE__ */ jsxs("div", { className: "ajw-xp", children: [
264
- "Lv.",
265
- level,
266
- " \xB7 XP ",
267
- xp
268
- ] }) : null
624
+ showControls && /* @__PURE__ */ jsxs("div", { className: "ajw-actions", children: [
625
+ shareUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
626
+ /* @__PURE__ */ jsx(
627
+ "button",
628
+ {
629
+ type: "button",
630
+ className: "ajw-btn",
631
+ onClick: () => copyToClipboard(shareUrl),
632
+ title: "Copy share URL",
633
+ children: "Copy link"
634
+ }
635
+ ),
636
+ /* @__PURE__ */ jsx("a", { className: "ajw-btn ajw-btn-link", href: shareUrl, target: "_blank", rel: "noreferrer", title: "Open share URL", children: "Open" })
269
637
  ] }),
270
- /* @__PURE__ */ jsx("div", { className: "ajw-events", children: filtered.map((e) => {
271
- if (e.type === "system.comment") {
272
- return /* @__PURE__ */ jsxs("div", { className: "ajw-persona", children: [
273
- /* @__PURE__ */ jsx("div", { className: "ajw-avatar", "aria-hidden": true, children: avatarForPack(run.pack) }),
274
- /* @__PURE__ */ jsx("div", { className: "ajw-bubble", children: e.message })
275
- ] }, e.id);
638
+ autoScroll && !stickToBottom && /* @__PURE__ */ jsx("button", { type: "button", className: "ajw-btn", onClick: scrollToBottom, title: "Jump to latest", children: "Jump to latest" }),
639
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ajw-btn", onClick: downloadJsonl, title: "Download events as JSONL", children: "Download log" }),
640
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ajw-btn", onClick: () => setMuted((m) => !m), title: "Toggle mute", children: muted ? "Unmute" : "Mute" }),
641
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ajw-btn", onClick: () => setSoundEnabled((s) => !s), title: "Toggle sound", children: soundEnabled ? "Sound on" : "Sound off" })
642
+ ] }),
643
+ showToolbar && /* @__PURE__ */ jsxs("div", { className: "ajw-toolbar", children: [
644
+ /* @__PURE__ */ jsx(
645
+ "input",
646
+ {
647
+ className: "ajw-search",
648
+ value: query,
649
+ onChange: (e) => setQuery(e.target.value),
650
+ placeholder: locale === "ja" ? "\u691C\u7D22\uFF08\u30E1\u30C3\u30BB\u30FC\u30B8 / \u30C4\u30FC\u30EB / \u7A2E\u5225\uFF09" : "Search (message / tool / type)"
276
651
  }
277
- const cls = e.type === "warning" ? "ajw-event ajw-eventWarning" : e.type === "error" || e.type === "run.failed" ? "ajw-event ajw-eventError" : e.type === "run.created" ? "ajw-event ajw-eventSystem" : "ajw-event";
278
- return /* @__PURE__ */ jsxs("div", { className: cls, children: [
279
- /* @__PURE__ */ jsx("div", { className: "ajw-eventMsg", children: e.message }),
280
- /* @__PURE__ */ jsxs("div", { className: "ajw-eventMeta", children: [
281
- /* @__PURE__ */ jsxs("span", { className: "ajw-mono", children: [
282
- e.type,
283
- e.phase ? ` \xB7 ${e.phase}` : "",
284
- e.tool?.name ? ` \xB7 tool=${e.tool.name}` : ""
285
- ] }),
286
- /* @__PURE__ */ jsx("span", { children: formatTs(e.ts, locale) }),
287
- typeof e.progress === "number" ? /* @__PURE__ */ jsxs("span", { children: [
288
- "progress=",
289
- Math.round(e.progress * 100),
290
- "%"
291
- ] }) : null
652
+ ),
653
+ /* @__PURE__ */ jsx("div", { className: "ajw-filters", role: "tablist", "aria-label": "Event filters", children: [
654
+ ["all", locale === "ja" ? "\u3059\u3079\u3066" : "All"],
655
+ ["important", locale === "ja" ? "\u91CD\u8981" : "Important"],
656
+ ["tools", locale === "ja" ? "\u30C4\u30FC\u30EB" : "Tools"],
657
+ ["artifacts", locale === "ja" ? "\u6210\u679C\u7269" : "Artifacts"]
658
+ ].map(([key, label]) => /* @__PURE__ */ jsx(
659
+ "button",
660
+ {
661
+ type: "button",
662
+ className: `ajw-chip ${filter === key ? "ajw-chip-active" : ""}`,
663
+ onClick: () => setFilter(key),
664
+ role: "tab",
665
+ "aria-selected": filter === key,
666
+ children: label
667
+ },
668
+ key
669
+ )) }),
670
+ /* @__PURE__ */ jsxs("div", { className: "ajw-count", title: "Visible / Total", children: [
671
+ visibleEvents.length,
672
+ "/",
673
+ events.length
674
+ ] })
675
+ ] }),
676
+ showXp && /* @__PURE__ */ jsxs("div", { className: "ajw-progress", children: [
677
+ /* @__PURE__ */ jsx("div", { className: "ajw-bar", "aria-label": "Progress", children: /* @__PURE__ */ jsx("div", { className: "ajw-bar-fill", style: { width: `${progressPct}%` } }) }),
678
+ /* @__PURE__ */ jsxs("div", { className: "ajw-xp", children: [
679
+ "Lv ",
680
+ level.level,
681
+ " \u2022 XP ",
682
+ xp,
683
+ " \u2022 ",
684
+ progressLabel
685
+ ] })
686
+ ] }),
687
+ showPersona && comment && /* @__PURE__ */ jsxs("div", { className: "ajw-persona", children: [
688
+ /* @__PURE__ */ jsx("div", { className: "ajw-avatar", "aria-hidden": "true", children: persona }),
689
+ /* @__PURE__ */ jsx("div", { className: "ajw-bubble", children: comment })
690
+ ] }),
691
+ /* @__PURE__ */ jsxs(
692
+ "div",
693
+ {
694
+ className: "ajw-events",
695
+ ref: listRef,
696
+ onScroll,
697
+ style: { maxHeight: typeof listMaxHeight === "number" ? `${listMaxHeight}px` : listMaxHeight },
698
+ children: [
699
+ loading && /* @__PURE__ */ jsxs("div", { className: "ajw-skeleton", children: [
700
+ /* @__PURE__ */ jsx("div", { className: "ajw-skeleton-line" }),
701
+ /* @__PURE__ */ jsx("div", { className: "ajw-skeleton-line" }),
702
+ /* @__PURE__ */ jsx("div", { className: "ajw-skeleton-line" })
703
+ ] }),
704
+ !loading && loadError && /* @__PURE__ */ jsxs("div", { className: "ajw-error", children: [
705
+ /* @__PURE__ */ jsx("div", { className: "ajw-error-title", children: "Failed to load" }),
706
+ /* @__PURE__ */ jsx("div", { className: "ajw-error-detail", children: loadError }),
707
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ajw-btn", onClick: () => load(), children: "Retry" })
292
708
  ] }),
293
- e.detail ? /* @__PURE__ */ jsx("div", { className: "ajw-eventMeta", style: { whiteSpace: "pre-wrap" }, children: e.detail }) : null
294
- ] }, e.id);
295
- }) })
296
- ] })
297
- ] }) });
709
+ !loading && !loadError && visibleEvents.map((e) => /* @__PURE__ */ jsxs("div", { className: `ajw-ev ajw-ev-${e.type.replace(".", "-")}`, children: [
710
+ /* @__PURE__ */ jsxs("div", { className: "ajw-ev-top", children: [
711
+ /* @__PURE__ */ jsx("div", { className: "ajw-ev-type", children: humanizeEventType(e.type) }),
712
+ /* @__PURE__ */ jsxs("div", { className: "ajw-ev-right", children: [
713
+ /* @__PURE__ */ jsx("div", { className: "ajw-ev-ts", children: e.ts?.slice(11, 19) ?? "" }),
714
+ /* @__PURE__ */ jsx(
715
+ "button",
716
+ {
717
+ type: "button",
718
+ className: "ajw-iconbtn",
719
+ title: locale === "ja" ? "\u30A4\u30D9\u30F3\u30C8\u3092\u30B3\u30D4\u30FC" : "Copy event",
720
+ onClick: () => copyToClipboard(JSON.stringify(e)),
721
+ children: "\u29C9"
722
+ }
723
+ )
724
+ ] })
725
+ ] }),
726
+ /* @__PURE__ */ jsx("div", { className: "ajw-ev-msg", children: e.message }),
727
+ e.tool && /* @__PURE__ */ jsxs("div", { className: "ajw-ev-tool", children: [
728
+ /* @__PURE__ */ jsx("div", { className: "ajw-ev-tool-name", children: e.tool.name }),
729
+ e.tool.input_summary && /* @__PURE__ */ jsxs("div", { className: "ajw-ev-tool-io", children: [
730
+ "in: ",
731
+ e.tool.input_summary
732
+ ] }),
733
+ e.tool.output_summary && /* @__PURE__ */ jsxs("div", { className: "ajw-ev-tool-io", children: [
734
+ "out: ",
735
+ e.tool.output_summary
736
+ ] }),
737
+ typeof e.tool.latency_ms === "number" && /* @__PURE__ */ jsxs("div", { className: "ajw-ev-tool-io", children: [
738
+ e.tool.latency_ms,
739
+ "ms"
740
+ ] })
741
+ ] }),
742
+ e.artifacts && e.artifacts.length > 0 && /* @__PURE__ */ jsx("div", { className: "ajw-ev-artifacts", children: e.artifacts.map((a, i) => /* @__PURE__ */ jsxs("div", { className: "ajw-ev-artifact", children: [
743
+ /* @__PURE__ */ jsx("span", { className: "ajw-ev-artifact-label", children: a.label }),
744
+ a.kind === "url" ? /* @__PURE__ */ jsx("a", { href: a.value, target: "_blank", rel: "noreferrer", children: a.value }) : /* @__PURE__ */ jsx("code", { children: a.value })
745
+ ] }, i)) }),
746
+ e.detail && /* @__PURE__ */ jsx("div", { className: "ajw-ev-detail", children: e.detail })
747
+ ] }, e.id))
748
+ ]
749
+ }
750
+ ),
751
+ autoScroll && !stickToBottom && /* @__PURE__ */ jsx("button", { type: "button", className: "ajw-float", onClick: scrollToBottom, title: "Jump to latest", children: "\u2193 Latest" })
752
+ ] });
298
753
  }
299
754
  export {
300
755
  AgentJoyRun