@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 +42 -0
- package/dist/index.css +171 -0
- package/dist/index.d.mts +90 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +328 -0
- package/dist/index.mjs +301 -0
- package/dist/styles.css +137 -0
- package/package.json +48 -0
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
|
+
}
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
};
|
package/dist/styles.css
ADDED
|
@@ -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
|
+
}
|