@vnl-works/agentjoy-react 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @vnl-works/agentjoy-react
2
+
3
+ AgentJoy の「待ち時間が楽しくなる」埋め込みWidget(React)です。
4
+ FastAPI backend(`backend/`)と組み合わせると、Runの進行をライブ表示できます。
5
+
6
+ ## インストール(例)
7
+
8
+ ```bash
9
+ npm i @vnl-works/agentjoy-react
10
+ # or pnpm add @vnl-works/agentjoy-react
11
+ ```
12
+
13
+ ## 使い方
14
+
15
+ ```tsx
16
+ import React from "react";
17
+ import { AgentJoyRun } from "@vnl-works/agentjoy-react";
18
+ import "@vnl-works/agentjoy-react/styles.css";
19
+
20
+ export function MyPage() {
21
+ return (
22
+ <AgentJoyRun
23
+ runId="run_..."
24
+ token="pub_..." // publish token か share token
25
+ apiBaseUrl="http://127.0.0.1:8000"
26
+ sound={true}
27
+ />
28
+ );
29
+ }
30
+ ```
31
+
32
+ ## Props(抜粋)
33
+ - `runId` / `token`: 必須
34
+ - `apiBaseUrl`: バックエンドのURL(同一オリジンなら省略可)
35
+ - `sound`: 進捗に合わせた簡易SE(デフォルトfalse)
36
+ - `mute`: 強制ミュート(brandのmute_defaultより優先)
37
+ - `showPersona`: `system.comment` を表示(人格パック)
38
+ - `showXp`: XP/レベル表示
39
+
40
+ ## イベント仕様
41
+ backend は `POST /v1/runs/{runId}/events` へ AgentJoy event schema を受け取ります。
42
+ OpenAPI は backend の `/docs` を参照してください。
package/dist/index.css ADDED
@@ -0,0 +1,171 @@
1
+ /* src/styles.css */
2
+ .ajw-root {
3
+ --ajw-primary: var(--aj-primary, #4F46E5);
4
+ --ajw-border: #E5E7EB;
5
+ --ajw-text: #111827;
6
+ --ajw-muted: #6B7280;
7
+ --ajw-bg: #ffffff;
8
+ --ajw-card: #ffffff;
9
+ font-family:
10
+ ui-sans-serif,
11
+ system-ui,
12
+ -apple-system,
13
+ Segoe UI,
14
+ Roboto,
15
+ Helvetica,
16
+ Arial,
17
+ "Apple Color Emoji",
18
+ "Segoe UI Emoji";
19
+ color: var(--ajw-text);
20
+ }
21
+ .ajw-card {
22
+ border: 1px solid var(--ajw-border);
23
+ border-radius: 14px;
24
+ background: var(--ajw-card);
25
+ overflow: hidden;
26
+ }
27
+ .ajw-header {
28
+ display: flex;
29
+ gap: 10px;
30
+ align-items: center;
31
+ padding: 12px 14px;
32
+ border-bottom: 1px solid var(--ajw-border);
33
+ background: #fafafa;
34
+ }
35
+ .ajw-logo {
36
+ width: 28px;
37
+ height: 28px;
38
+ border-radius: 8px;
39
+ background: var(--ajw-primary);
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ color: white;
44
+ font-weight: 700;
45
+ overflow: hidden;
46
+ flex: none;
47
+ }
48
+ .ajw-title {
49
+ font-weight: 700;
50
+ font-size: 14px;
51
+ line-height: 1.1;
52
+ }
53
+ .ajw-sub {
54
+ font-size: 12px;
55
+ color: var(--ajw-muted);
56
+ margin-top: 2px;
57
+ }
58
+ .ajw-actions {
59
+ margin-left: auto;
60
+ display: flex;
61
+ gap: 8px;
62
+ align-items: center;
63
+ }
64
+ .ajw-btn {
65
+ border: 1px solid var(--ajw-border);
66
+ border-radius: 10px;
67
+ background: white;
68
+ padding: 6px 10px;
69
+ font-size: 12px;
70
+ cursor: pointer;
71
+ }
72
+ .ajw-btnPrimary {
73
+ border-color: var(--ajw-primary);
74
+ background: var(--ajw-primary);
75
+ color: white;
76
+ }
77
+ .ajw-body {
78
+ padding: 12px 14px;
79
+ }
80
+ .ajw-progressRow {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 10px;
84
+ }
85
+ .ajw-progressBar {
86
+ height: 8px;
87
+ background: #F3F4F6;
88
+ border-radius: 999px;
89
+ overflow: hidden;
90
+ flex: 1;
91
+ }
92
+ .ajw-progressBar > div {
93
+ height: 100%;
94
+ width: 0%;
95
+ background: var(--ajw-primary);
96
+ }
97
+ .ajw-xp {
98
+ font-size: 12px;
99
+ color: var(--ajw-muted);
100
+ white-space: nowrap;
101
+ }
102
+ .ajw-events {
103
+ margin-top: 12px;
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: 8px;
107
+ max-height: 360px;
108
+ overflow: auto;
109
+ padding-right: 4px;
110
+ }
111
+ .ajw-event {
112
+ border-left: 3px solid var(--ajw-border);
113
+ background: #FAFAFA;
114
+ border-radius: 10px;
115
+ padding: 10px 10px 10px 12px;
116
+ }
117
+ .ajw-eventWarning {
118
+ border-left-color: rgba(180, 83, 9, 0.8);
119
+ }
120
+ .ajw-eventError {
121
+ border-left-color: rgba(185, 28, 28, 0.8);
122
+ }
123
+ .ajw-eventSystem {
124
+ border-left-color: rgba(79, 70, 229, 0.7);
125
+ }
126
+ .ajw-eventMsg {
127
+ font-size: 13px;
128
+ font-weight: 600;
129
+ }
130
+ .ajw-eventMeta {
131
+ font-size: 12px;
132
+ color: var(--ajw-muted);
133
+ margin-top: 4px;
134
+ display: flex;
135
+ gap: 10px;
136
+ flex-wrap: wrap;
137
+ }
138
+ .ajw-mono {
139
+ font-family:
140
+ ui-monospace,
141
+ SFMono-Regular,
142
+ Menlo,
143
+ Monaco,
144
+ Consolas,
145
+ "Liberation Mono",
146
+ "Courier New",
147
+ monospace;
148
+ }
149
+ .ajw-persona {
150
+ display: flex;
151
+ gap: 8px;
152
+ align-items: flex-start;
153
+ }
154
+ .ajw-avatar {
155
+ width: 26px;
156
+ height: 26px;
157
+ border-radius: 10px;
158
+ background: rgba(79, 70, 229, 0.12);
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ flex: none;
163
+ }
164
+ .ajw-bubble {
165
+ background: white;
166
+ border: 1px solid var(--ajw-border);
167
+ border-radius: 12px;
168
+ padding: 9px 10px;
169
+ font-size: 13px;
170
+ font-weight: 600;
171
+ }
@@ -0,0 +1,90 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type AgentJoyPhase = "prepare" | "research" | "execute" | "format" | "verify" | "finalize";
4
+ type AgentJoyEventType = "run.created" | "phase.started" | "phase.progress" | "tool.called" | "tool.completed" | "artifact.ready" | "warning" | "error" | "run.completed" | "run.failed" | "system.comment";
5
+ type WorkspaceBrand = {
6
+ product_name: string;
7
+ logo_url?: string | null;
8
+ primary_color: string;
9
+ tone: "polite" | "formal" | "casual";
10
+ mute_default: boolean;
11
+ show_xp: boolean;
12
+ show_persona: boolean;
13
+ };
14
+ type ToolInfo = {
15
+ name: string;
16
+ input_summary?: string;
17
+ output_summary?: string;
18
+ latency_ms?: number;
19
+ };
20
+ type Artifact = {
21
+ kind: "url" | "file" | "text";
22
+ label: string;
23
+ value: string;
24
+ };
25
+ type AgentJoyEvent = {
26
+ id: number;
27
+ run_id: string;
28
+ ts: string;
29
+ type: AgentJoyEventType;
30
+ phase?: AgentJoyPhase | null;
31
+ message: string;
32
+ detail?: string | null;
33
+ progress?: number | null;
34
+ severity?: "info" | "warning" | "error";
35
+ actor?: "agent" | "tool" | "system" | "user";
36
+ tool?: ToolInfo | null;
37
+ artifacts?: Artifact[] | null;
38
+ meta?: Record<string, unknown> | null;
39
+ tags?: string[] | null;
40
+ };
41
+ type RunPublic = {
42
+ id: string;
43
+ workspace_id: string;
44
+ status: "running" | "completed" | "failed";
45
+ created_at: string;
46
+ started_at?: string | null;
47
+ ended_at?: string | null;
48
+ last_event_at?: string | null;
49
+ title?: string | null;
50
+ external_user_id?: string | null;
51
+ mode: "professional" | "playful";
52
+ pack: string;
53
+ share_token?: string | null;
54
+ share_expires_at?: string | null;
55
+ metadata?: Record<string, unknown> | null;
56
+ };
57
+ type RunDetailResponse = {
58
+ run: RunPublic;
59
+ events: AgentJoyEvent[];
60
+ workspace_brand: WorkspaceBrand;
61
+ };
62
+
63
+ type AgentJoyRunProps = {
64
+ runId: string;
65
+ /** publish token (server side) or share token (viewer) */
66
+ token: string;
67
+ /** default: "" (same origin) */
68
+ apiBaseUrl?: string;
69
+ /** default: "ja" */
70
+ locale?: "ja" | "en";
71
+ /** number of events rendered (default 200) */
72
+ limit?: number;
73
+ /** sound effects (default false) */
74
+ sound?: boolean;
75
+ /** force mute state (overrides brand mute_default). if omitted, uses brand default */
76
+ mute?: boolean;
77
+ /** override brand (optional) */
78
+ brandOverride?: Partial<WorkspaceBrand>;
79
+ /** show XP/level (default: brand.show_xp) */
80
+ showXp?: boolean;
81
+ /** show persona comment events (default: brand.show_persona) */
82
+ showPersona?: boolean;
83
+ /** called when run status becomes completed */
84
+ onComplete?: (run: RunPublic) => void;
85
+ /** called when run status becomes failed */
86
+ onError?: (run: RunPublic) => void;
87
+ };
88
+ declare function AgentJoyRun(props: AgentJoyRunProps): react_jsx_runtime.JSX.Element;
89
+
90
+ export { type AgentJoyEvent, type AgentJoyEventType, type AgentJoyPhase, AgentJoyRun, type AgentJoyRunProps, type Artifact, type RunDetailResponse, type RunPublic, type ToolInfo, type WorkspaceBrand };
@@ -0,0 +1,90 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type AgentJoyPhase = "prepare" | "research" | "execute" | "format" | "verify" | "finalize";
4
+ type AgentJoyEventType = "run.created" | "phase.started" | "phase.progress" | "tool.called" | "tool.completed" | "artifact.ready" | "warning" | "error" | "run.completed" | "run.failed" | "system.comment";
5
+ type WorkspaceBrand = {
6
+ product_name: string;
7
+ logo_url?: string | null;
8
+ primary_color: string;
9
+ tone: "polite" | "formal" | "casual";
10
+ mute_default: boolean;
11
+ show_xp: boolean;
12
+ show_persona: boolean;
13
+ };
14
+ type ToolInfo = {
15
+ name: string;
16
+ input_summary?: string;
17
+ output_summary?: string;
18
+ latency_ms?: number;
19
+ };
20
+ type Artifact = {
21
+ kind: "url" | "file" | "text";
22
+ label: string;
23
+ value: string;
24
+ };
25
+ type AgentJoyEvent = {
26
+ id: number;
27
+ run_id: string;
28
+ ts: string;
29
+ type: AgentJoyEventType;
30
+ phase?: AgentJoyPhase | null;
31
+ message: string;
32
+ detail?: string | null;
33
+ progress?: number | null;
34
+ severity?: "info" | "warning" | "error";
35
+ actor?: "agent" | "tool" | "system" | "user";
36
+ tool?: ToolInfo | null;
37
+ artifacts?: Artifact[] | null;
38
+ meta?: Record<string, unknown> | null;
39
+ tags?: string[] | null;
40
+ };
41
+ type RunPublic = {
42
+ id: string;
43
+ workspace_id: string;
44
+ status: "running" | "completed" | "failed";
45
+ created_at: string;
46
+ started_at?: string | null;
47
+ ended_at?: string | null;
48
+ last_event_at?: string | null;
49
+ title?: string | null;
50
+ external_user_id?: string | null;
51
+ mode: "professional" | "playful";
52
+ pack: string;
53
+ share_token?: string | null;
54
+ share_expires_at?: string | null;
55
+ metadata?: Record<string, unknown> | null;
56
+ };
57
+ type RunDetailResponse = {
58
+ run: RunPublic;
59
+ events: AgentJoyEvent[];
60
+ workspace_brand: WorkspaceBrand;
61
+ };
62
+
63
+ type AgentJoyRunProps = {
64
+ runId: string;
65
+ /** publish token (server side) or share token (viewer) */
66
+ token: string;
67
+ /** default: "" (same origin) */
68
+ apiBaseUrl?: string;
69
+ /** default: "ja" */
70
+ locale?: "ja" | "en";
71
+ /** number of events rendered (default 200) */
72
+ limit?: number;
73
+ /** sound effects (default false) */
74
+ sound?: boolean;
75
+ /** force mute state (overrides brand mute_default). if omitted, uses brand default */
76
+ mute?: boolean;
77
+ /** override brand (optional) */
78
+ brandOverride?: Partial<WorkspaceBrand>;
79
+ /** show XP/level (default: brand.show_xp) */
80
+ showXp?: boolean;
81
+ /** show persona comment events (default: brand.show_persona) */
82
+ showPersona?: boolean;
83
+ /** called when run status becomes completed */
84
+ onComplete?: (run: RunPublic) => void;
85
+ /** called when run status becomes failed */
86
+ onError?: (run: RunPublic) => void;
87
+ };
88
+ declare function AgentJoyRun(props: AgentJoyRunProps): react_jsx_runtime.JSX.Element;
89
+
90
+ export { type AgentJoyEvent, type AgentJoyEventType, type AgentJoyPhase, AgentJoyRun, type AgentJoyRunProps, type Artifact, type RunDetailResponse, type RunPublic, type ToolInfo, type WorkspaceBrand };
package/dist/index.js ADDED
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AgentJoyRun: () => AgentJoyRun
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/AgentJoyRun.tsx
28
+ var import_react = require("react");
29
+
30
+ // src/utils.ts
31
+ function phaseLabel(phase, tone, locale = "ja") {
32
+ const ja = {
33
+ prepare: "\u6E96\u5099",
34
+ research: "\u8ABF\u67FB",
35
+ execute: "\u5B9F\u884C",
36
+ format: "\u6574\u5F62",
37
+ verify: "\u691C\u8A3C",
38
+ finalize: "\u4ED5\u4E0A\u3052"
39
+ };
40
+ const en = {
41
+ prepare: "Prepare",
42
+ research: "Research",
43
+ execute: "Execute",
44
+ format: "Format",
45
+ verify: "Verify",
46
+ finalize: "Finalize"
47
+ };
48
+ const base = locale === "ja" ? ja[phase] : en[phase];
49
+ return base;
50
+ }
51
+ function statusLabel(status, tone, locale = "ja") {
52
+ if (locale === "en") {
53
+ if (status === "running") return "Running";
54
+ if (status === "completed") return "Completed";
55
+ return "Failed";
56
+ }
57
+ const polite = { running: "\u5B9F\u884C\u4E2D", completed: "\u5B8C\u4E86", failed: "\u5931\u6557" };
58
+ const formal = { running: "\u5B9F\u884C\u4E2D", completed: "\u5B8C\u4E86", failed: "\u5931\u6557" };
59
+ const casual = { running: "\u5B9F\u884C\u4E2D", completed: "\u5B8C\u4E86", failed: "\u5931\u6557" };
60
+ const map = tone === "formal" ? formal : tone === "casual" ? casual : polite;
61
+ return map[status];
62
+ }
63
+ function formatTs(ts, locale = "ja") {
64
+ try {
65
+ return new Date(ts).toLocaleString(locale === "ja" ? "ja-JP" : "en-US");
66
+ } catch {
67
+ return ts;
68
+ }
69
+ }
70
+ function avatarForPack(packId) {
71
+ const m = {
72
+ default: "\u{1F9ED}",
73
+ buddy: "\u{1F91D}",
74
+ senpai: "\u{1F393}",
75
+ robot: "\u{1F916}"
76
+ };
77
+ return m[packId] || "\u{1F3AD}";
78
+ }
79
+ function clamp01(x) {
80
+ if (typeof x !== "number" || Number.isNaN(x)) return null;
81
+ if (x < 0) return 0;
82
+ if (x > 1) return 1;
83
+ return x;
84
+ }
85
+
86
+ // src/AgentJoyRun.tsx
87
+ var import_jsx_runtime = require("react/jsx-runtime");
88
+ function xpForEvent(e) {
89
+ switch (e.type) {
90
+ case "phase.started":
91
+ return 18;
92
+ case "phase.progress":
93
+ return 6;
94
+ case "tool.called":
95
+ return 10;
96
+ case "tool.completed":
97
+ return 12;
98
+ case "warning":
99
+ return 2;
100
+ case "error":
101
+ case "run.failed":
102
+ return 1;
103
+ case "run.completed":
104
+ return 25;
105
+ case "system.comment":
106
+ return 0;
107
+ default:
108
+ return 4;
109
+ }
110
+ }
111
+ function useBeep() {
112
+ const ctxRef = (0, import_react.useRef)(null);
113
+ function ensure() {
114
+ if (!ctxRef.current) {
115
+ const AC = window.AudioContext || window.webkitAudioContext;
116
+ if (AC) ctxRef.current = new AC();
117
+ }
118
+ return ctxRef.current;
119
+ }
120
+ function tone(freq, ms) {
121
+ const ctx = ensure();
122
+ if (!ctx) return;
123
+ const o = ctx.createOscillator();
124
+ const g = ctx.createGain();
125
+ o.type = "sine";
126
+ o.frequency.value = freq;
127
+ g.gain.value = 0.03;
128
+ o.connect(g);
129
+ g.connect(ctx.destination);
130
+ o.start();
131
+ setTimeout(() => {
132
+ o.stop();
133
+ o.disconnect();
134
+ g.disconnect();
135
+ }, ms);
136
+ }
137
+ function success() {
138
+ tone(740, 90);
139
+ setTimeout(() => tone(880, 120), 110);
140
+ }
141
+ function error() {
142
+ tone(220, 200);
143
+ }
144
+ function tick() {
145
+ tone(520, 55);
146
+ }
147
+ return { tick, success, error };
148
+ }
149
+ function AgentJoyRun(props) {
150
+ const apiBaseUrl = props.apiBaseUrl ?? "";
151
+ const locale = props.locale ?? "ja";
152
+ const limit = props.limit ?? 200;
153
+ const { tick, success, error } = useBeep();
154
+ const [run, setRun] = (0, import_react.useState)(null);
155
+ const [events, setEvents] = (0, import_react.useState)([]);
156
+ const [brand, setBrand] = (0, import_react.useState)(null);
157
+ const [muted, setMuted] = (0, import_react.useState)(props.mute ?? true);
158
+ const esRef = (0, import_react.useRef)(null);
159
+ (0, import_react.useEffect)(() => {
160
+ let cancelled = false;
161
+ async function load() {
162
+ const url = `${apiBaseUrl}/v1/runs/${encodeURIComponent(props.runId)}?token=${encodeURIComponent(props.token)}&limit=2000`;
163
+ const res = await fetch(url);
164
+ if (!res.ok) throw new Error(await res.text());
165
+ const data = await res.json();
166
+ if (cancelled) return;
167
+ setRun(data.run);
168
+ setEvents(data.events || []);
169
+ const mergedBrand = { ...data.workspace_brand, ...props.brandOverride || {} };
170
+ setBrand(mergedBrand);
171
+ setMuted(props.mute ?? !!mergedBrand.mute_default);
172
+ }
173
+ load().catch(() => {
174
+ });
175
+ return () => {
176
+ cancelled = true;
177
+ };
178
+ }, [apiBaseUrl, props.runId, props.token]);
179
+ (0, import_react.useEffect)(() => {
180
+ const url = `${apiBaseUrl}/v1/runs/${encodeURIComponent(props.runId)}/stream?token=${encodeURIComponent(props.token)}`;
181
+ const es = new EventSource(url);
182
+ esRef.current = es;
183
+ es.onmessage = (msg) => {
184
+ try {
185
+ const e = JSON.parse(msg.data);
186
+ setEvents((prev) => [...prev, e]);
187
+ if (e.type === "run.completed") {
188
+ setRun((r) => r ? { ...r, status: "completed", ended_at: e.ts } : r);
189
+ }
190
+ if (e.type === "run.failed") {
191
+ setRun((r) => r ? { ...r, status: "failed", ended_at: e.ts } : r);
192
+ }
193
+ if (props.sound && !muted) {
194
+ if (e.type === "phase.started") tick();
195
+ if (e.type === "run.completed") success();
196
+ if (e.type === "run.failed" || e.type === "error") error();
197
+ }
198
+ } catch {
199
+ }
200
+ };
201
+ es.onerror = () => {
202
+ };
203
+ return () => {
204
+ es.close();
205
+ esRef.current = null;
206
+ };
207
+ }, [apiBaseUrl, props.runId, props.token, props.sound, muted]);
208
+ (0, import_react.useEffect)(() => {
209
+ if (!run) return;
210
+ if (run.status === "completed") props.onComplete?.(run);
211
+ if (run.status === "failed") props.onError?.(run);
212
+ }, [run?.status]);
213
+ const resolvedBrand = (0, import_react.useMemo)(() => {
214
+ if (!brand) return null;
215
+ return { ...brand, ...props.brandOverride || {} };
216
+ }, [brand, props.brandOverride]);
217
+ const showXp = props.showXp ?? resolvedBrand?.show_xp ?? true;
218
+ const showPersona = props.showPersona ?? resolvedBrand?.show_persona ?? true;
219
+ const filtered = (0, import_react.useMemo)(() => {
220
+ const arr = showPersona ? events : events.filter((e) => e.type !== "system.comment");
221
+ return arr.slice(-limit);
222
+ }, [events, limit, showPersona]);
223
+ const currentPhase = (0, import_react.useMemo)(() => {
224
+ for (let i = events.length - 1; i >= 0; i--) {
225
+ const p = events[i].phase;
226
+ if (p) return p;
227
+ }
228
+ return "prepare";
229
+ }, [events]);
230
+ const lastProgress = (0, import_react.useMemo)(() => {
231
+ for (let i = events.length - 1; i >= 0; i--) {
232
+ const p = clamp01(events[i].progress ?? null);
233
+ if (typeof p === "number") return p;
234
+ }
235
+ return null;
236
+ }, [events]);
237
+ const progressPct = (0, import_react.useMemo)(() => {
238
+ if (typeof lastProgress === "number") return Math.round(lastProgress * 100);
239
+ const order = ["prepare", "research", "execute", "format", "verify", "finalize"];
240
+ const idx = Math.max(0, order.indexOf(currentPhase));
241
+ return Math.round((idx + 0.4) / order.length * 100);
242
+ }, [currentPhase, lastProgress]);
243
+ const xp = (0, import_react.useMemo)(() => events.reduce((acc, e) => acc + xpForEvent(e), 0), [events]);
244
+ const level = Math.floor(xp / 250) + 1;
245
+ const title = resolvedBrand?.product_name || "AgentJoy";
246
+ const tone = resolvedBrand?.tone || "polite";
247
+ const status = run?.status || "running";
248
+ const rootStyle = {
249
+ // @ts-ignore CSS variable typing
250
+ ["--aj-primary"]: resolvedBrand?.primary_color || "#4F46E5"
251
+ };
252
+ if (!run) {
253
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-root", style: rootStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-card", children: [
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-header", children: [
255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-logo", children: "AJ" }),
256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-title", children: title }),
258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-sub", children: "loading\u2026" })
259
+ ] })
260
+ ] }),
261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-event ajw-eventSystem", children: [
262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-eventMsg", children: "Loading run\u2026" }),
263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-eventMeta", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ajw-mono", children: [
264
+ "runId=",
265
+ props.runId
266
+ ] }) })
267
+ ] }) })
268
+ ] }) });
269
+ }
270
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-root", style: rootStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-card", children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-header", children: [
272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-logo", children: resolvedBrand?.logo_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: resolvedBrand.logo_url, style: { width: 28, height: 28, objectFit: "cover" } }) : "AJ" }),
273
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
274
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-title", children: title }),
275
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-sub", children: [
276
+ statusLabel(status, tone, locale),
277
+ " \xB7 ",
278
+ phaseLabel(currentPhase, tone, locale),
279
+ " \xB7 ",
280
+ progressPct,
281
+ "%"
282
+ ] })
283
+ ] }),
284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "ajw-btn", onClick: () => setMuted((m) => !m), title: "mute/unmute", children: muted ? "\u{1F507}" : "\u{1F50A}" }) })
285
+ ] }),
286
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-body", children: [
287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-progressRow", children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-progressBar", "aria-label": "progress", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: `${progressPct}%` } }) }),
289
+ showXp ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-xp", children: [
290
+ "Lv.",
291
+ level,
292
+ " \xB7 XP ",
293
+ xp
294
+ ] }) : null
295
+ ] }),
296
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-events", children: filtered.map((e) => {
297
+ if (e.type === "system.comment") {
298
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-persona", children: [
299
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-avatar", "aria-hidden": true, children: avatarForPack(run.pack) }),
300
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-bubble", children: e.message })
301
+ ] }, e.id);
302
+ }
303
+ 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";
304
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cls, children: [
305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-eventMsg", children: e.message }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ajw-eventMeta", children: [
307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ajw-mono", children: [
308
+ e.type,
309
+ e.phase ? ` \xB7 ${e.phase}` : "",
310
+ e.tool?.name ? ` \xB7 tool=${e.tool.name}` : ""
311
+ ] }),
312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatTs(e.ts, locale) }),
313
+ typeof e.progress === "number" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
314
+ "progress=",
315
+ Math.round(e.progress * 100),
316
+ "%"
317
+ ] }) : null
318
+ ] }),
319
+ e.detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ajw-eventMeta", style: { whiteSpace: "pre-wrap" }, children: e.detail }) : null
320
+ ] }, e.id);
321
+ }) })
322
+ ] })
323
+ ] }) });
324
+ }
325
+ // Annotate the CommonJS export names for ESM import in node:
326
+ 0 && (module.exports = {
327
+ AgentJoyRun
328
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,301 @@
1
+ // src/AgentJoyRun.tsx
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+
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
+ function avatarForPack(packId) {
45
+ const m = {
46
+ default: "\u{1F9ED}",
47
+ buddy: "\u{1F91D}",
48
+ senpai: "\u{1F393}",
49
+ robot: "\u{1F916}"
50
+ };
51
+ return m[packId] || "\u{1F3AD}";
52
+ }
53
+ function clamp01(x) {
54
+ if (typeof x !== "number" || Number.isNaN(x)) return null;
55
+ if (x < 0) return 0;
56
+ if (x > 1) return 1;
57
+ return x;
58
+ }
59
+
60
+ // 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;
83
+ }
84
+ }
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);
120
+ }
121
+ return { tick, success, error };
122
+ }
123
+ 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();
128
+ const [run, setRun] = useState(null);
129
+ const [events, setEvents] = useState([]);
130
+ const [brand, setBrand] = useState(null);
131
+ const [muted, setMuted] = useState(props.mute ?? true);
132
+ const esRef = useRef(null);
133
+ 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);
146
+ }
147
+ load().catch(() => {
148
+ });
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) => {
158
+ 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);
163
+ }
164
+ if (e.type === "run.failed") {
165
+ setRun((r) => r ? { ...r, status: "failed", ended_at: e.ts } : r);
166
+ }
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();
171
+ }
172
+ } catch {
173
+ }
174
+ };
175
+ es.onerror = () => {
176
+ };
177
+ return () => {
178
+ es.close();
179
+ esRef.current = null;
180
+ };
181
+ }, [apiBaseUrl, props.runId, props.token, props.sound, muted]);
182
+ useEffect(() => {
183
+ 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;
201
+ }
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;
208
+ }
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
+ ] }) })
242
+ ] }) });
243
+ }
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: [
248
+ /* @__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
+ ] })
257
+ ] }),
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}" }) })
259
+ ] }),
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
269
+ ] }),
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);
276
+ }
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
292
+ ] }),
293
+ e.detail ? /* @__PURE__ */ jsx("div", { className: "ajw-eventMeta", style: { whiteSpace: "pre-wrap" }, children: e.detail }) : null
294
+ ] }, e.id);
295
+ }) })
296
+ ] })
297
+ ] }) });
298
+ }
299
+ export {
300
+ AgentJoyRun
301
+ };
@@ -0,0 +1,137 @@
1
+ .ajw-root {
2
+ --ajw-primary: var(--aj-primary, #4F46E5);
3
+ --ajw-border: #E5E7EB;
4
+ --ajw-text: #111827;
5
+ --ajw-muted: #6B7280;
6
+ --ajw-bg: #ffffff;
7
+ --ajw-card: #ffffff;
8
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
9
+ color: var(--ajw-text);
10
+ }
11
+
12
+ .ajw-card {
13
+ border: 1px solid var(--ajw-border);
14
+ border-radius: 14px;
15
+ background: var(--ajw-card);
16
+ overflow: hidden;
17
+ }
18
+
19
+ .ajw-header {
20
+ display: flex;
21
+ gap: 10px;
22
+ align-items: center;
23
+ padding: 12px 14px;
24
+ border-bottom: 1px solid var(--ajw-border);
25
+ background: #fafafa;
26
+ }
27
+
28
+ .ajw-logo {
29
+ width: 28px;
30
+ height: 28px;
31
+ border-radius: 8px;
32
+ background: var(--ajw-primary);
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ color: white;
37
+ font-weight: 700;
38
+ overflow: hidden;
39
+ flex: none;
40
+ }
41
+
42
+ .ajw-title {
43
+ font-weight: 700;
44
+ font-size: 14px;
45
+ line-height: 1.1;
46
+ }
47
+ .ajw-sub {
48
+ font-size: 12px;
49
+ color: var(--ajw-muted);
50
+ margin-top: 2px;
51
+ }
52
+
53
+ .ajw-actions { margin-left: auto; display:flex; gap: 8px; align-items:center; }
54
+ .ajw-btn {
55
+ border: 1px solid var(--ajw-border);
56
+ border-radius: 10px;
57
+ background: white;
58
+ padding: 6px 10px;
59
+ font-size: 12px;
60
+ cursor: pointer;
61
+ }
62
+ .ajw-btnPrimary {
63
+ border-color: var(--ajw-primary);
64
+ background: var(--ajw-primary);
65
+ color: white;
66
+ }
67
+
68
+ .ajw-body { padding: 12px 14px; }
69
+
70
+ .ajw-progressRow { display:flex; align-items:center; gap: 10px; }
71
+
72
+ .ajw-progressBar {
73
+ height: 8px;
74
+ background: #F3F4F6;
75
+ border-radius: 999px;
76
+ overflow: hidden;
77
+ flex: 1;
78
+ }
79
+ .ajw-progressBar > div {
80
+ height: 100%;
81
+ width: 0%;
82
+ background: var(--ajw-primary);
83
+ }
84
+
85
+ .ajw-xp {
86
+ font-size: 12px;
87
+ color: var(--ajw-muted);
88
+ white-space: nowrap;
89
+ }
90
+
91
+ .ajw-events {
92
+ margin-top: 12px;
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 8px;
96
+ max-height: 360px;
97
+ overflow: auto;
98
+ padding-right: 4px;
99
+ }
100
+
101
+ .ajw-event {
102
+ border-left: 3px solid var(--ajw-border);
103
+ background: #FAFAFA;
104
+ border-radius: 10px;
105
+ padding: 10px 10px 10px 12px;
106
+ }
107
+ .ajw-eventWarning { border-left-color: rgba(180,83,9,0.8); }
108
+ .ajw-eventError { border-left-color: rgba(185,28,28,0.8); }
109
+ .ajw-eventSystem { border-left-color: rgba(79,70,229,0.7); }
110
+
111
+ .ajw-eventMsg { font-size: 13px; font-weight: 600; }
112
+ .ajw-eventMeta { font-size: 12px; color: var(--ajw-muted); margin-top: 4px; display:flex; gap:10px; flex-wrap: wrap; }
113
+ .ajw-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
114
+
115
+ .ajw-persona {
116
+ display: flex;
117
+ gap: 8px;
118
+ align-items: flex-start;
119
+ }
120
+ .ajw-avatar {
121
+ width: 26px;
122
+ height: 26px;
123
+ border-radius: 10px;
124
+ background: rgba(79,70,229,0.12);
125
+ display:flex;
126
+ align-items:center;
127
+ justify-content:center;
128
+ flex: none;
129
+ }
130
+ .ajw-bubble {
131
+ background: white;
132
+ border: 1px solid var(--ajw-border);
133
+ border-radius: 12px;
134
+ padding: 9px 10px;
135
+ font-size: 13px;
136
+ font-weight: 600;
137
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@vnl-works/agentjoy-react",
3
+ "version": "0.1.2",
4
+ "description": "AgentJoy embedded widget SDK (React)",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup src/index.ts --dts --format cjs,esm --external react --external react-dom && node -e \"require('fs').copyFileSync('src/styles.css','dist/styles.css')\"",
14
+ "dev": "tsup src/index.ts --dts --format cjs,esm --watch --external react --external react-dom",
15
+ "typecheck": "tsc -p tsconfig.json --noEmit",
16
+ "prepack": "npm run build"
17
+ },
18
+ "peerDependencies": {
19
+ "react": ">=18",
20
+ "react-dom": ">=18"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^18.3.0",
24
+ "@types/react-dom": "^18.3.0",
25
+ "tsup": "^8.0.0",
26
+ "typescript": "^5.4.0"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/vnl-works/agentjoy.git"
35
+ },
36
+ "homepage": "https://github.com/vnl-works/agentjoy#readme",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "import": "./dist/index.mjs",
41
+ "require": "./dist/index.js"
42
+ },
43
+ "./styles.css": "./dist/styles.css"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/vnl-works/agentjoy/issues"
47
+ }
48
+ }