@aixnn/aixautots 0.0.0-stage → 0.5.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 +239 -2
- package/dist/ScriptStudio-DaCqOF5H.d.ts +43 -0
- package/dist/agent-runner-V60B0is1.d.ts +57 -0
- package/dist/automation-queue-8HqRJV_K.d.ts +72 -0
- package/dist/chunk-536WVCTB.js +5744 -0
- package/dist/chunk-5BMFMDQR.js +18 -0
- package/dist/chunk-64Q7BBIQ.js +1347 -0
- package/dist/chunk-6YUAACW6.js +866 -0
- package/dist/chunk-AIUOLJZA.js +412 -0
- package/dist/chunk-CVV7FOMQ.js +175 -0
- package/dist/chunk-EHF2WQUR.js +2042 -0
- package/dist/chunk-EVHTAXQG.js +273 -0
- package/dist/chunk-FW23FLAF.js +56 -0
- package/dist/chunk-GKEPMDXO.js +410 -0
- package/dist/chunk-H7MHGYF3.js +341 -0
- package/dist/chunk-MRQ7VJFJ.js +1074 -0
- package/dist/chunk-NWFYTFMI.js +65 -0
- package/dist/chunk-OEZFYKL6.js +1 -0
- package/dist/chunk-PCYERBNZ.js +1564 -0
- package/dist/chunk-PUPVZRKZ.js +668 -0
- package/dist/chunk-SUKFJ73W.js +217 -0
- package/dist/config-Bm1hmXhD.d.ts +1380 -0
- package/dist/coords-LPR_MPyp.d.ts +22 -0
- package/dist/core/index.d.ts +135 -0
- package/dist/core/index.js +5 -0
- package/dist/device-normalize-BaThgnH7.d.ts +43 -0
- package/dist/device-session-KZVGLPOI.js +2 -0
- package/dist/i18n/index.d.ts +30 -0
- package/dist/i18n/index.js +1 -0
- package/dist/mirror-session-C_1vFkRV.d.ts +108 -0
- package/dist/nav/index.d.ts +19 -0
- package/dist/nav/index.js +1 -0
- package/dist/react/index.d.ts +487 -0
- package/dist/react/index.js +7 -0
- package/dist/script-debugger-Cw5jVRoz.d.ts +114 -0
- package/dist/task-agent-runner-eD9-SCJz.d.ts +160 -0
- package/dist/types-C2WXVcam.d.ts +1005 -0
- package/dist/ui/billing.d.ts +48 -0
- package/dist/ui/billing.js +425 -0
- package/dist/ui/device-create.d.ts +55 -0
- package/dist/ui/device-create.js +9 -0
- package/dist/ui/device-mirror.d.ts +42 -0
- package/dist/ui/device-mirror.js +10 -0
- package/dist/ui/devices.d.ts +223 -0
- package/dist/ui/devices.js +15 -0
- package/dist/ui/editor.d.ts +64 -0
- package/dist/ui/editor.js +344 -0
- package/dist/ui/index.d.ts +104 -0
- package/dist/ui/index.js +676 -0
- package/dist/ui/inspector.d.ts +5 -0
- package/dist/ui/inspector.js +1250 -0
- package/dist/ui/market.d.ts +10 -0
- package/dist/ui/market.js +60 -0
- package/dist/ui/mirror.d.ts +52 -0
- package/dist/ui/mirror.js +3 -0
- package/dist/ui/primitives/index.d.ts +101 -0
- package/dist/ui/primitives/index.js +1 -0
- package/dist/ui/script-studio.d.ts +85 -0
- package/dist/ui/script-studio.js +10 -0
- package/dist/ui/scripts.d.ts +18 -0
- package/dist/ui/scripts.js +210 -0
- package/dist/ui/tasks.d.ts +10 -0
- package/dist/ui/tasks.js +1039 -0
- package/dist/use-script-run-Diay7ibY.d.ts +50 -0
- package/package.json +76 -4
- package/styles.css +97 -0
- package/tailwind-preset.cjs +84 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { T as TaskAgentRunner, D as Device, a as TaskAgentDevice } from '../task-agent-runner-eD9-SCJz.js';
|
|
3
|
+
export { A as AgentTaskRunnerOptions, b as TaskAgentDeviceStatus, c as TaskAgentNodeSnapshot, d as TaskAgentPreflightResult, e as TaskAgentRunRequest, f as TaskAgentStreamEvent, g as TaskAgentTarget, h as createAgentTaskRunner, p as preflightTaskAgentDevice } from '../task-agent-runner-eD9-SCJz.js';
|
|
4
|
+
import { aL as Shortcut, h as ScriptParam } from '../types-C2WXVcam.js';
|
|
5
|
+
import '../device-normalize-BaThgnH7.js';
|
|
6
|
+
import '../config-Bm1hmXhD.js';
|
|
7
|
+
import '../agent-runner-V60B0is1.js';
|
|
8
|
+
|
|
9
|
+
interface DevicesPageProps {
|
|
10
|
+
/** Render the page header actions (Refresh + Add device) into a host slot.
|
|
11
|
+
* When omitted the page renders its own inline action row above the toolbar. */
|
|
12
|
+
renderHeaderActions?: (actions: React.ReactNode) => React.ReactNode;
|
|
13
|
+
/** Optional host transport for multi-device Claude/Codex tasks. */
|
|
14
|
+
taskAgentRunner?: TaskAgentRunner;
|
|
15
|
+
}
|
|
16
|
+
declare function DevicesPage({ taskAgentRunner: providedTaskAgentRunner }?: DevicesPageProps): react.JSX.Element;
|
|
17
|
+
|
|
18
|
+
interface DeviceCardProps {
|
|
19
|
+
device: Device;
|
|
20
|
+
isSelected: boolean;
|
|
21
|
+
onPreview: (id: string) => void;
|
|
22
|
+
onEdit?: (id: string) => void;
|
|
23
|
+
onDelete?: (id: string) => void;
|
|
24
|
+
powering?: boolean;
|
|
25
|
+
onPowerOn?: (id: string) => void;
|
|
26
|
+
checked?: boolean;
|
|
27
|
+
onCheck?: (id: string, checked: boolean) => void;
|
|
28
|
+
}
|
|
29
|
+
/** 紧凑截图卡片:顶部小尺寸截图缩略(单次抓取,非实时画面),
|
|
30
|
+
* 底部名称/平台/节点 + 备注/分组行;点图打开实时镜像,详情走外层卡片点击(drawer)。 */
|
|
31
|
+
declare function DeviceCard({ device, isSelected, onPreview, onEdit, onDelete, powering, onPowerOn, checked, onCheck, }: DeviceCardProps): react.JSX.Element;
|
|
32
|
+
|
|
33
|
+
interface Props$e {
|
|
34
|
+
deviceId: string | null;
|
|
35
|
+
onClose: () => void;
|
|
36
|
+
}
|
|
37
|
+
declare function DeviceDetailDrawer({ deviceId, onClose }: Props$e): react.JSX.Element;
|
|
38
|
+
|
|
39
|
+
interface Props$d {
|
|
40
|
+
/** null = 关闭。 */
|
|
41
|
+
deviceIds: string[] | null;
|
|
42
|
+
/** id → 显示名。 */
|
|
43
|
+
names?: Record<string, string>;
|
|
44
|
+
/** 底栏按键图标风格(全 iOS 选 ios,否则 android)。默认 android。 */
|
|
45
|
+
platform?: 'android' | 'ios';
|
|
46
|
+
onClose: () => void;
|
|
47
|
+
}
|
|
48
|
+
declare function GroupMirrorModal({ deviceIds, names, platform, onClose }: Props$d): react.JSX.Element | null;
|
|
49
|
+
|
|
50
|
+
interface DeviceRef$1 {
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
}
|
|
54
|
+
interface Props$c {
|
|
55
|
+
/** 勾选的设备;null = 关闭。 */
|
|
56
|
+
devices: DeviceRef$1[] | null;
|
|
57
|
+
onClose: () => void;
|
|
58
|
+
}
|
|
59
|
+
declare function RunScriptDialog({ devices, onClose }: Props$c): react.JSX.Element | null;
|
|
60
|
+
|
|
61
|
+
interface TaskAgentDialogProps {
|
|
62
|
+
/** Selected devices; null closes the dialog. */
|
|
63
|
+
devices: TaskAgentDevice[] | null;
|
|
64
|
+
/** Desktop/web host transport. The SDK never imports Tauri directly. */
|
|
65
|
+
runner?: TaskAgentRunner;
|
|
66
|
+
onClose: () => void;
|
|
67
|
+
}
|
|
68
|
+
declare function TaskAgentDialog({ devices, runner, onClose }: TaskAgentDialogProps): react.JSX.Element | null;
|
|
69
|
+
|
|
70
|
+
interface DeviceRef {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
platform?: string | null;
|
|
74
|
+
}
|
|
75
|
+
interface Props$b {
|
|
76
|
+
/** 勾选的设备;null = 关闭。 */
|
|
77
|
+
devices: DeviceRef[] | null;
|
|
78
|
+
onClose: () => void;
|
|
79
|
+
}
|
|
80
|
+
declare function BulkInstallAppsDialog({ devices, onClose }: Props$b): react.JSX.Element | null;
|
|
81
|
+
|
|
82
|
+
interface Props$a {
|
|
83
|
+
/** Target device(s). 单台传 1 个;多选传多个。 */
|
|
84
|
+
deviceIds: string[];
|
|
85
|
+
platform?: 'android' | 'ios' | 'any';
|
|
86
|
+
/** 横向(默认,设备详情抽屉用)或竖向图标轨(紧贴镜像左侧、与画面等高)。 */
|
|
87
|
+
orientation?: 'horizontal' | 'vertical';
|
|
88
|
+
className?: string;
|
|
89
|
+
}
|
|
90
|
+
/** 统一封装的设备快捷栏:拉 system+user 快捷,点一下在选中设备上跑(有参数则弹框)。
|
|
91
|
+
* 用户自有快捷可就地编辑/删除;"+" 新建。横向铺开或竖向贴边图标轨两种布局。 */
|
|
92
|
+
declare function ShortcutBar({ deviceIds, platform, orientation, className }: Props$a): react.JSX.Element;
|
|
93
|
+
|
|
94
|
+
interface Props$9 {
|
|
95
|
+
shortcut: Shortcut;
|
|
96
|
+
/** Targets — length drives single vs batch. */
|
|
97
|
+
deviceIds: string[];
|
|
98
|
+
onClose: () => void;
|
|
99
|
+
onRun: (values: Record<string, unknown>) => void;
|
|
100
|
+
}
|
|
101
|
+
/** Collect a shortcut's parameters (and confirm) before running it on N devices. */
|
|
102
|
+
declare function ShortcutRunDialog({ shortcut, deviceIds, onClose, onRun }: Props$9): react.JSX.Element;
|
|
103
|
+
|
|
104
|
+
interface Props$8 {
|
|
105
|
+
/** 传入则为编辑;否则新建。仅 user 快捷可编辑。 */
|
|
106
|
+
initial?: Shortcut;
|
|
107
|
+
onClose: () => void;
|
|
108
|
+
onSaved?: (s: Shortcut) => void;
|
|
109
|
+
onDeleted?: () => void;
|
|
110
|
+
}
|
|
111
|
+
/** 创建/编辑用户自定义快捷(函数脚本 + 参数签名)。 */
|
|
112
|
+
declare function ShortcutEditDialog({ initial, onClose, onSaved, onDeleted }: Props$8): react.JSX.Element;
|
|
113
|
+
|
|
114
|
+
/** 回收站视图:列出软删除设备,可恢复 / 彻底删除 / 清空全部。 */
|
|
115
|
+
declare function RecycleBinPanel(): react.JSX.Element;
|
|
116
|
+
|
|
117
|
+
interface Props$7 {
|
|
118
|
+
/** 目标设备(null = 关闭)。 */
|
|
119
|
+
device: {
|
|
120
|
+
id: string;
|
|
121
|
+
name: string;
|
|
122
|
+
} | null;
|
|
123
|
+
onClose: () => void;
|
|
124
|
+
/** 删除成功后的回调(如关闭详情抽屉)。 */
|
|
125
|
+
onDeleted?: () => void;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 删除设备二次确认:要求键入设备名(防误删),确认后软删进回收站。
|
|
129
|
+
* 用受控弹窗而非浏览器 confirm —— Tauri webview 下 `window.confirm` 常被吞。
|
|
130
|
+
*/
|
|
131
|
+
declare function DeleteDeviceDialog({ device, onClose, onDeleted }: Props$7): react.JSX.Element | null;
|
|
132
|
+
|
|
133
|
+
interface Props$6 {
|
|
134
|
+
device: Device | null;
|
|
135
|
+
onClose: () => void;
|
|
136
|
+
}
|
|
137
|
+
declare function DeviceEditDialog({ device, onClose }: Props$6): react.JSX.Element | null;
|
|
138
|
+
|
|
139
|
+
interface Props$5 {
|
|
140
|
+
deviceId: string;
|
|
141
|
+
}
|
|
142
|
+
declare function FileManager({ deviceId }: Props$5): react.JSX.Element;
|
|
143
|
+
|
|
144
|
+
interface Props$4 {
|
|
145
|
+
deviceId: string;
|
|
146
|
+
}
|
|
147
|
+
declare function FingerprintPanel({ deviceId }: Props$4): react.JSX.Element;
|
|
148
|
+
|
|
149
|
+
interface Props$3 {
|
|
150
|
+
deviceId: string;
|
|
151
|
+
}
|
|
152
|
+
declare function DeviceTerminal({ deviceId }: Props$3): react.JSX.Element;
|
|
153
|
+
|
|
154
|
+
interface DeviceConnectProps {
|
|
155
|
+
open: boolean;
|
|
156
|
+
onClose: () => void;
|
|
157
|
+
}
|
|
158
|
+
declare function DeviceConnect({ open, onClose }: DeviceConnectProps): react.JSX.Element;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 运行设备下拉:列出全部设备(在线优先),选中即同步到共享 devices view 的
|
|
162
|
+
* `selectDevice`,保证镜像等读全局选中设备的链路一致。
|
|
163
|
+
*/
|
|
164
|
+
declare function DeviceSelector({ disabled }: {
|
|
165
|
+
disabled?: boolean;
|
|
166
|
+
}): react.JSX.Element;
|
|
167
|
+
|
|
168
|
+
interface TagEditorProps {
|
|
169
|
+
tags: string[];
|
|
170
|
+
onChange: (next: string[]) => void;
|
|
171
|
+
}
|
|
172
|
+
/** Inline editor for a device's group tags: chips with a remove (×) plus an
|
|
173
|
+
* input that appends a tag on Enter. Emits the full next-tag list. */
|
|
174
|
+
declare function TagEditor({ tags, onChange }: TagEditorProps): react.JSX.Element;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 「刷新设备参数」按钮 + popover。刷新 = 重滚设备指纹(regenerateProfile)。虚拟机
|
|
178
|
+
* (provisioned)额外提供「还原设备」可选勾:勾上则在重滚后恢复出厂(reset,清 /data、
|
|
179
|
+
* 原镜像重建)。真机无还原选项——保存即经 LSPosed 实时推送。
|
|
180
|
+
*/
|
|
181
|
+
declare function RefreshParamsButton({ deviceId, provisioned, onDone, size, variant, }: {
|
|
182
|
+
deviceId: string;
|
|
183
|
+
provisioned: boolean;
|
|
184
|
+
onDone?: () => void;
|
|
185
|
+
size?: 'sm' | 'md' | 'icon';
|
|
186
|
+
variant?: 'primary' | 'secondary' | 'ghost';
|
|
187
|
+
}): react.JSX.Element;
|
|
188
|
+
|
|
189
|
+
interface Props$2 {
|
|
190
|
+
/** 当前参数值映射(key → 字符串值)。 */
|
|
191
|
+
params: Record<string, string>;
|
|
192
|
+
/** 单个键变更回调。 */
|
|
193
|
+
onChange: (key: string, value: string) => void;
|
|
194
|
+
/** 无参数时的提示文案。 */
|
|
195
|
+
emptyHint?: string;
|
|
196
|
+
className?: string;
|
|
197
|
+
}
|
|
198
|
+
/** flow.variables → 参数输入。键列只读(脚本定义),值列可编辑。 */
|
|
199
|
+
declare function ScriptParamForm({ params, onChange, emptyHint, className }: Props$2): react.JSX.Element;
|
|
200
|
+
/** 把任意 `variables`(值可能非字符串)归一成参数表单用的字符串映射。 */
|
|
201
|
+
declare function variablesToParams(variables: Record<string, unknown> | undefined): Record<string, string>;
|
|
202
|
+
|
|
203
|
+
interface Props$1 {
|
|
204
|
+
/** 非目标参数(已剔除 isTarget)。 */
|
|
205
|
+
params: ScriptParam[];
|
|
206
|
+
/** 单值映射(随机关闭时的值,或随机开启时的多行候选文本)。 */
|
|
207
|
+
values: Record<string, string>;
|
|
208
|
+
/** 哪些字段处于「随机取值」模式。 */
|
|
209
|
+
randomOn: Record<string, boolean>;
|
|
210
|
+
onValue: (key: string, value: string) => void;
|
|
211
|
+
onToggleRandom: (key: string, on: boolean) => void;
|
|
212
|
+
}
|
|
213
|
+
declare function SharedParamsForm({ params, values, randomOn, onValue, onToggleRandom }: Props$1): react.JSX.Element;
|
|
214
|
+
/** 按 schema 对一行随机字段做解析:随机字段从多行候选里取一条,其余取单值。 */
|
|
215
|
+
declare function resolveSharedRow(params: ScriptParam[], values: Record<string, string>, randomOn: Record<string, boolean>): Record<string, string>;
|
|
216
|
+
|
|
217
|
+
interface Props {
|
|
218
|
+
open: boolean;
|
|
219
|
+
onClose: () => void;
|
|
220
|
+
}
|
|
221
|
+
declare function NodeRegister({ open, onClose }: Props): react.JSX.Element;
|
|
222
|
+
|
|
223
|
+
export { BulkInstallAppsDialog, DeleteDeviceDialog, DeviceCard, DeviceConnect, DeviceDetailDrawer, DeviceEditDialog, DeviceSelector, DeviceTerminal, DevicesPage, FileManager, FingerprintPanel, GroupMirrorModal, NodeRegister, RecycleBinPanel, RefreshParamsButton, RunScriptDialog, ScriptParamForm, SharedParamsForm, ShortcutBar, ShortcutEditDialog, ShortcutRunDialog, TagEditor, TaskAgentDevice, TaskAgentDialog, TaskAgentRunner, resolveSharedRow, variablesToParams };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { BulkInstallAppsDialog, DeleteDeviceDialog, DeviceCard, DeviceConnect, DeviceDetailDrawer, DeviceEditDialog, DeviceSelector, DeviceTerminal, DevicesPage, FileManager, FingerprintPanel, GroupMirrorModal, NodeRegister, RecycleBinPanel, RefreshParamsButton, RunScriptDialog, ScriptParamForm, SharedParamsForm, ShortcutBar, ShortcutEditDialog, ShortcutRunDialog, TagEditor, TaskAgentDialog, resolveSharedRow, variablesToParams } from '../chunk-536WVCTB.js';
|
|
2
|
+
import '../chunk-64Q7BBIQ.js';
|
|
3
|
+
import '../chunk-FW23FLAF.js';
|
|
4
|
+
import '../chunk-AIUOLJZA.js';
|
|
5
|
+
import '../chunk-OEZFYKL6.js';
|
|
6
|
+
import '../chunk-CVV7FOMQ.js';
|
|
7
|
+
import '../chunk-MRQ7VJFJ.js';
|
|
8
|
+
import '../chunk-EVHTAXQG.js';
|
|
9
|
+
export { createAgentTaskRunner, preflightTaskAgentDevice } from '../chunk-EHF2WQUR.js';
|
|
10
|
+
import '../chunk-H7MHGYF3.js';
|
|
11
|
+
import '../chunk-PUPVZRKZ.js';
|
|
12
|
+
import '../chunk-SUKFJ73W.js';
|
|
13
|
+
import '../chunk-GKEPMDXO.js';
|
|
14
|
+
import '../chunk-PCYERBNZ.js';
|
|
15
|
+
import '../chunk-NWFYTFMI.js';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { S as ScriptStudioProps } from '../ScriptStudio-DaCqOF5H.js';
|
|
3
|
+
export { a as ScriptStudioSlotContext } from '../ScriptStudio-DaCqOF5H.js';
|
|
4
|
+
import { a as AgentRunner } from '../agent-runner-V60B0is1.js';
|
|
5
|
+
export { A as AgentKind, b as AgentSendRequest, c as AgentStreamEvent } from '../agent-runner-V60B0is1.js';
|
|
6
|
+
import { c as AixClient } from '../config-Bm1hmXhD.js';
|
|
7
|
+
import '../types-C2WXVcam.js';
|
|
8
|
+
|
|
9
|
+
interface ScriptEditorPageProps {
|
|
10
|
+
/** Route param: the script to open. Undefined ⇒ a fresh "New Script". */
|
|
11
|
+
scriptId?: string;
|
|
12
|
+
/** Host-injected local-agent transport — drives the built-in `AgentPanel`. */
|
|
13
|
+
agentRunner?: AgentRunner;
|
|
14
|
+
/** Fully override the right-column agent panel (takes precedence over `agentRunner`). */
|
|
15
|
+
renderAgentPanel?: ScriptStudioProps['renderAgentPanel'];
|
|
16
|
+
/** Host-injected device inspector (modal/overlay). */
|
|
17
|
+
renderInspector?: ScriptStudioProps['renderInspector'];
|
|
18
|
+
/** Extra header controls (e.g. a language toggle) — host chrome. */
|
|
19
|
+
headerExtra?: ScriptStudioProps['headerExtra'];
|
|
20
|
+
}
|
|
21
|
+
declare function ScriptEditorPage({ scriptId, agentRunner, renderAgentPanel, renderInspector, headerExtra, }: ScriptEditorPageProps): react.JSX.Element;
|
|
22
|
+
|
|
23
|
+
interface AgentPanelProps {
|
|
24
|
+
scriptId?: string;
|
|
25
|
+
deviceId: string | null;
|
|
26
|
+
/** Current editor source — handed to the agent each turn. */
|
|
27
|
+
code: string;
|
|
28
|
+
/** Replace the editor source with the agent's edited `script.ts`. */
|
|
29
|
+
onReplace: (code: string) => void;
|
|
30
|
+
/** Open the visual device inspector (element picker → inserts a locator). */
|
|
31
|
+
onOpenInspector: () => void;
|
|
32
|
+
/** Append a snippet to the editor source (e.g. a `device.launchApp(...)` line). */
|
|
33
|
+
onInsert: (snippet: string) => void;
|
|
34
|
+
/** Host-injected agent transport (Tauri-backed on desktop). */
|
|
35
|
+
runner: AgentRunner;
|
|
36
|
+
}
|
|
37
|
+
declare function AgentPanel({ scriptId, deviceId, code, onReplace, onOpenInspector, onInsert, runner, }: AgentPanelProps): react.JSX.Element;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 服务端 agent 的 `AgentRunner` 实现:claude/codex CLI 跑在**后端**(密钥/算力在后端),
|
|
41
|
+
* 前端经 HTTP 起停 + SSE 流式回传。与本机 Tauri 实现(`frontend/src/lib/agentRunner.ts`)
|
|
42
|
+
* 并列;`AgentPanel` 不感知差异。
|
|
43
|
+
*
|
|
44
|
+
* 复用 `AixClient` 的 baseUrl / 鉴权(user JWT)/ fetchFn —— 做成闭包工厂,调用方只需:
|
|
45
|
+
* const runner = createBackendAgentRunner(client)
|
|
46
|
+
* <ScriptEditorPage agentRunner={isTauri() ? tauriAgentRunner : runner} />
|
|
47
|
+
*
|
|
48
|
+
* 后端端点契约(待后端落地):
|
|
49
|
+
* POST {basePath}/start body=AgentSendRequest 首轮:起会话并跑
|
|
50
|
+
* POST {basePath}/send body=AgentSendRequest 续轮
|
|
51
|
+
* POST {basePath}/stop body={ scriptId } 停止
|
|
52
|
+
* GET {basePath}/defaults → { agent? }(可选)
|
|
53
|
+
* GET {basePath}/stream (text/event-stream) SSE:每帧 data: <AgentStreamEvent JSON>
|
|
54
|
+
* 事件 JSON 字段:script_id|scriptId / stream('stdout'|'stderr'|'done') / chunk? / script?
|
|
55
|
+
* 鉴权全部复用 client 的 user JWT(fetchFn + auth.headers 注入)。
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
interface BackendAgentRunnerOptions {
|
|
59
|
+
/** Agent 端点前缀,默认 `/api/agent`。 */
|
|
60
|
+
basePath?: string;
|
|
61
|
+
}
|
|
62
|
+
declare function createBackendAgentRunner(client: AixClient, opts?: BackendAgentRunnerOptions): AgentRunner;
|
|
63
|
+
|
|
64
|
+
export { AgentPanel, type AgentPanelProps, AgentRunner, type BackendAgentRunnerOptions, ScriptEditorPage, type ScriptEditorPageProps, createBackendAgentRunner };
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { ScriptStudio } from '../chunk-6YUAACW6.js';
|
|
2
|
+
import { useAixNav } from '../chunk-5BMFMDQR.js';
|
|
3
|
+
import { useAixT } from '../chunk-64Q7BBIQ.js';
|
|
4
|
+
import '../chunk-FW23FLAF.js';
|
|
5
|
+
import { Select, cn } from '../chunk-EVHTAXQG.js';
|
|
6
|
+
import '../chunk-EHF2WQUR.js';
|
|
7
|
+
import '../chunk-H7MHGYF3.js';
|
|
8
|
+
import '../chunk-PUPVZRKZ.js';
|
|
9
|
+
import { useClient } from '../chunk-SUKFJ73W.js';
|
|
10
|
+
import '../chunk-GKEPMDXO.js';
|
|
11
|
+
import '../chunk-PCYERBNZ.js';
|
|
12
|
+
import '../chunk-NWFYTFMI.js';
|
|
13
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
14
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
15
|
+
|
|
16
|
+
function AgentPanel({
|
|
17
|
+
scriptId,
|
|
18
|
+
deviceId,
|
|
19
|
+
code,
|
|
20
|
+
onReplace,
|
|
21
|
+
onOpenInspector,
|
|
22
|
+
onInsert,
|
|
23
|
+
runner
|
|
24
|
+
}) {
|
|
25
|
+
const t = useAixT();
|
|
26
|
+
const client = useClient();
|
|
27
|
+
const available = runner.available;
|
|
28
|
+
const [agent, setAgent] = useState("claude");
|
|
29
|
+
const [messages, setMessages] = useState([]);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
void runner.defaults?.().then((d) => {
|
|
32
|
+
if (d.agent) setAgent(d.agent);
|
|
33
|
+
});
|
|
34
|
+
}, [runner]);
|
|
35
|
+
const [input, setInput] = useState("");
|
|
36
|
+
const [running, setRunning] = useState(false);
|
|
37
|
+
const scrollRef = useRef(null);
|
|
38
|
+
const inputRef = useRef(null);
|
|
39
|
+
const liveRef = useRef("");
|
|
40
|
+
const [apps, setApps] = useState(null);
|
|
41
|
+
const [appsLoading, setAppsLoading] = useState(false);
|
|
42
|
+
const [appsErr, setAppsErr] = useState(null);
|
|
43
|
+
const loadApps = useCallback(async () => {
|
|
44
|
+
if (!deviceId) {
|
|
45
|
+
setAppsErr(t("agent.selectDeviceFirst"));
|
|
46
|
+
setApps([]);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
setAppsLoading(true);
|
|
50
|
+
setAppsErr(null);
|
|
51
|
+
try {
|
|
52
|
+
const list = await client.devices.op(deviceId, { op: "app_list" });
|
|
53
|
+
setApps(list ?? []);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
setAppsErr(String(e));
|
|
56
|
+
setApps([]);
|
|
57
|
+
} finally {
|
|
58
|
+
setAppsLoading(false);
|
|
59
|
+
}
|
|
60
|
+
}, [deviceId, client, t]);
|
|
61
|
+
const autosize = useCallback(() => {
|
|
62
|
+
const el = inputRef.current;
|
|
63
|
+
if (!el) return;
|
|
64
|
+
el.style.height = "auto";
|
|
65
|
+
el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
|
|
66
|
+
}, []);
|
|
67
|
+
useEffect(autosize, [input, autosize]);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight });
|
|
70
|
+
}, [messages]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!available) return;
|
|
73
|
+
const unsub = runner.subscribe((p) => {
|
|
74
|
+
if (p.scriptId !== (scriptId ?? "")) return;
|
|
75
|
+
if (p.stream === "stdout" && p.chunk) {
|
|
76
|
+
liveRef.current += p.chunk + "\n";
|
|
77
|
+
setMessages((m) => upsertAgent(m, liveRef.current));
|
|
78
|
+
} else if (p.stream === "done") {
|
|
79
|
+
setRunning(false);
|
|
80
|
+
liveRef.current = "";
|
|
81
|
+
if (typeof p.script === "string" && p.script.length > 0 && p.script !== code) {
|
|
82
|
+
onReplace(p.script);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return unsub;
|
|
87
|
+
}, [available, runner, scriptId]);
|
|
88
|
+
const send = useCallback(
|
|
89
|
+
async (text) => {
|
|
90
|
+
const msg = text.trim();
|
|
91
|
+
if (!msg || running || !available) return;
|
|
92
|
+
if (!deviceId) {
|
|
93
|
+
setMessages((m) => [...m, { role: "agent", text: t("agent.needDevice") }]);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
setMessages((m) => [...m, { role: "user", text: msg }, { role: "agent", text: "\u2026" }]);
|
|
97
|
+
const isFirst = messages.length === 0;
|
|
98
|
+
setInput("");
|
|
99
|
+
setRunning(true);
|
|
100
|
+
liveRef.current = "";
|
|
101
|
+
try {
|
|
102
|
+
await runner.send({ agent, scriptId: scriptId ?? "", code, deviceId, userMsg: msg, isFirst });
|
|
103
|
+
} catch (e) {
|
|
104
|
+
setRunning(false);
|
|
105
|
+
setMessages((m) => upsertAgent(m, t("agent.startFailed", { err: String(e) })));
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
[agent, code, deviceId, messages.length, running, scriptId, available, runner, t]
|
|
109
|
+
);
|
|
110
|
+
const stop = useCallback(async () => {
|
|
111
|
+
if (!available) return;
|
|
112
|
+
try {
|
|
113
|
+
await runner.stop(scriptId ?? "");
|
|
114
|
+
} finally {
|
|
115
|
+
setRunning(false);
|
|
116
|
+
}
|
|
117
|
+
}, [scriptId, available, runner]);
|
|
118
|
+
if (!available) {
|
|
119
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
|
|
120
|
+
/* @__PURE__ */ jsx("div", { className: "px-3 py-2 border-b border-border text-body-sm font-semibold", children: "Agent" }),
|
|
121
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground text-body-sm px-6 text-center", children: t("agent.unavailable") })
|
|
122
|
+
] });
|
|
123
|
+
}
|
|
124
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
|
|
125
|
+
/* @__PURE__ */ jsxs("div", { className: "px-3 py-2 border-b border-border flex items-center justify-between gap-2", children: [
|
|
126
|
+
/* @__PURE__ */ jsx("span", { className: "text-body-sm font-semibold", children: "Agent" }),
|
|
127
|
+
/* @__PURE__ */ jsx("div", { className: "w-28", children: /* @__PURE__ */ jsx(
|
|
128
|
+
Select,
|
|
129
|
+
{
|
|
130
|
+
value: agent,
|
|
131
|
+
onChange: (v) => setAgent(v),
|
|
132
|
+
options: [
|
|
133
|
+
{ value: "claude", label: "claude" },
|
|
134
|
+
{ value: "codex", label: "codex" }
|
|
135
|
+
],
|
|
136
|
+
placeholder: ""
|
|
137
|
+
}
|
|
138
|
+
) })
|
|
139
|
+
] }),
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "flex-1 overflow-auto px-3 py-2 space-y-2", children: [
|
|
141
|
+
messages.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground text-center pt-6", children: t("agent.emptyHint") }),
|
|
142
|
+
messages.map((m, i) => /* @__PURE__ */ jsx(
|
|
143
|
+
"div",
|
|
144
|
+
{
|
|
145
|
+
className: cn(
|
|
146
|
+
"rounded px-2 py-1.5 text-body-sm whitespace-pre-wrap break-words",
|
|
147
|
+
m.role === "user" ? "bg-primary/10 text-foreground ml-6" : "bg-secondary text-muted-foreground mr-2 font-mono text-code-sm"
|
|
148
|
+
),
|
|
149
|
+
children: m.text
|
|
150
|
+
},
|
|
151
|
+
i
|
|
152
|
+
))
|
|
153
|
+
] }),
|
|
154
|
+
apps !== null && /* @__PURE__ */ jsxs("div", { className: "px-3 py-1.5 border-t border-border max-h-40 overflow-auto space-y-0.5", children: [
|
|
155
|
+
appsErr && /* @__PURE__ */ jsx("div", { className: "text-xs text-destructive", children: appsErr }),
|
|
156
|
+
!appsErr && apps.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: t("agent.noApps") }),
|
|
157
|
+
apps.map((a) => /* @__PURE__ */ jsxs(
|
|
158
|
+
"button",
|
|
159
|
+
{
|
|
160
|
+
onClick: () => {
|
|
161
|
+
onInsert(`await device.launchApp(${JSON.stringify(a.package)});`);
|
|
162
|
+
setApps(null);
|
|
163
|
+
},
|
|
164
|
+
className: "flex w-full items-center justify-between gap-2 rounded px-2 py-0.5 text-left text-xs text-foreground hover:bg-secondary",
|
|
165
|
+
children: [
|
|
166
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: a.label || a.package }),
|
|
167
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-[10px] text-muted-foreground", children: a.package })
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
a.package
|
|
171
|
+
))
|
|
172
|
+
] }),
|
|
173
|
+
/* @__PURE__ */ jsxs("div", { className: "px-3 py-1.5 flex flex-wrap gap-1 border-t border-border", children: [
|
|
174
|
+
/* @__PURE__ */ jsx(
|
|
175
|
+
"button",
|
|
176
|
+
{
|
|
177
|
+
disabled: running,
|
|
178
|
+
onClick: onOpenInspector,
|
|
179
|
+
className: "rounded-full bg-secondary px-2 py-0.5 text-xs text-muted-foreground hover:text-foreground disabled:opacity-50",
|
|
180
|
+
children: t("agent.useInspector")
|
|
181
|
+
}
|
|
182
|
+
),
|
|
183
|
+
/* @__PURE__ */ jsx(
|
|
184
|
+
"button",
|
|
185
|
+
{
|
|
186
|
+
disabled: running || appsLoading,
|
|
187
|
+
onClick: () => apps !== null ? setApps(null) : void loadApps(),
|
|
188
|
+
className: "rounded-full bg-secondary px-2 py-0.5 text-xs text-muted-foreground hover:text-foreground disabled:opacity-50",
|
|
189
|
+
children: appsLoading ? t("agent.loading") : apps !== null ? t("agent.closeApps") : t("agent.pickApp")
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
] }),
|
|
193
|
+
/* @__PURE__ */ jsxs("div", { className: "p-2 border-t border-border flex gap-2 items-end", children: [
|
|
194
|
+
/* @__PURE__ */ jsx(
|
|
195
|
+
"textarea",
|
|
196
|
+
{
|
|
197
|
+
ref: inputRef,
|
|
198
|
+
rows: 1,
|
|
199
|
+
className: "flex-1 resize-none bg-secondary rounded px-2 py-1 text-body-sm outline-none disabled:opacity-50 max-h-40 overflow-y-auto",
|
|
200
|
+
placeholder: t("agent.placeholder"),
|
|
201
|
+
value: input,
|
|
202
|
+
disabled: running,
|
|
203
|
+
onChange: (e) => setInput(e.target.value),
|
|
204
|
+
onKeyDown: (e) => {
|
|
205
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
void send(input);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
),
|
|
212
|
+
running ? /* @__PURE__ */ jsx(
|
|
213
|
+
"button",
|
|
214
|
+
{
|
|
215
|
+
onClick: () => void stop(),
|
|
216
|
+
className: "rounded bg-destructive/15 text-destructive px-3 text-body-sm",
|
|
217
|
+
children: t("agent.stop")
|
|
218
|
+
}
|
|
219
|
+
) : /* @__PURE__ */ jsx(
|
|
220
|
+
"button",
|
|
221
|
+
{
|
|
222
|
+
onClick: () => void send(input),
|
|
223
|
+
disabled: !input.trim(),
|
|
224
|
+
className: "rounded bg-primary text-primary-foreground px-3 text-body-sm disabled:opacity-50",
|
|
225
|
+
children: t("agent.send")
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
] })
|
|
229
|
+
] });
|
|
230
|
+
}
|
|
231
|
+
function upsertAgent(messages, text) {
|
|
232
|
+
const next = [...messages];
|
|
233
|
+
for (let i = next.length - 1; i >= 0; i--) {
|
|
234
|
+
if (next[i]?.role === "agent") {
|
|
235
|
+
next[i] = { role: "agent", text };
|
|
236
|
+
return next;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
next.push({ role: "agent", text });
|
|
240
|
+
return next;
|
|
241
|
+
}
|
|
242
|
+
function ScriptEditorPage({
|
|
243
|
+
scriptId,
|
|
244
|
+
agentRunner,
|
|
245
|
+
renderAgentPanel,
|
|
246
|
+
renderInspector,
|
|
247
|
+
headerExtra
|
|
248
|
+
}) {
|
|
249
|
+
const { navigate } = useAixNav();
|
|
250
|
+
const agentSlot = renderAgentPanel ?? (agentRunner ? (ctx) => /* @__PURE__ */ jsx(
|
|
251
|
+
AgentPanel,
|
|
252
|
+
{
|
|
253
|
+
scriptId: ctx.scriptId,
|
|
254
|
+
deviceId: ctx.deviceId,
|
|
255
|
+
code: ctx.code,
|
|
256
|
+
onReplace: ctx.setCode,
|
|
257
|
+
onOpenInspector: ctx.openInspector,
|
|
258
|
+
onInsert: ctx.insertSnippet,
|
|
259
|
+
runner: agentRunner
|
|
260
|
+
}
|
|
261
|
+
) : void 0);
|
|
262
|
+
return /* @__PURE__ */ jsx(
|
|
263
|
+
ScriptStudio,
|
|
264
|
+
{
|
|
265
|
+
scriptId,
|
|
266
|
+
onScriptIdChange: (id) => navigate(`/editor/${encodeURIComponent(id)}`, { replace: true }),
|
|
267
|
+
renderAgentPanel: agentSlot,
|
|
268
|
+
renderInspector,
|
|
269
|
+
headerExtra
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/ui/editor/backend-agent-runner.ts
|
|
275
|
+
function createBackendAgentRunner(client, opts = {}) {
|
|
276
|
+
const base = (opts.basePath ?? "/api/agent").replace(/\/$/, "");
|
|
277
|
+
return {
|
|
278
|
+
available: true,
|
|
279
|
+
async defaults() {
|
|
280
|
+
try {
|
|
281
|
+
return await client.http.get(`${base}/defaults`) ?? {};
|
|
282
|
+
} catch {
|
|
283
|
+
return {};
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
async send(req) {
|
|
287
|
+
await client.http.post(`${base}/${req.isFirst ? "start" : "send"}`, req);
|
|
288
|
+
},
|
|
289
|
+
async stop(scriptId) {
|
|
290
|
+
await client.http.post(`${base}/stop`, { scriptId });
|
|
291
|
+
},
|
|
292
|
+
subscribe(on) {
|
|
293
|
+
const ctrl = new AbortController();
|
|
294
|
+
void streamSse(
|
|
295
|
+
client,
|
|
296
|
+
`${base}/stream`,
|
|
297
|
+
ctrl.signal,
|
|
298
|
+
(raw) => on({
|
|
299
|
+
scriptId: raw.script_id ?? raw.scriptId ?? "",
|
|
300
|
+
stream: raw.stream,
|
|
301
|
+
chunk: raw.chunk,
|
|
302
|
+
script: raw.script
|
|
303
|
+
})
|
|
304
|
+
);
|
|
305
|
+
return () => ctrl.abort();
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async function streamSse(client, path, signal, onEvent) {
|
|
310
|
+
const url = client.cfg.baseUrl.replace(/\/$/, "") + path;
|
|
311
|
+
let resp;
|
|
312
|
+
try {
|
|
313
|
+
const headers = { ...await client.cfg.auth.headers("caller"), Accept: "text/event-stream" };
|
|
314
|
+
resp = await client.cfg.fetchFn(url, { headers, signal });
|
|
315
|
+
} catch {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const body = resp.body;
|
|
319
|
+
if (!body || typeof body.getReader !== "function") return;
|
|
320
|
+
const reader = body.getReader();
|
|
321
|
+
const decoder = new TextDecoder();
|
|
322
|
+
let buf = "";
|
|
323
|
+
try {
|
|
324
|
+
for (; ; ) {
|
|
325
|
+
const { value, done } = await reader.read();
|
|
326
|
+
if (done) break;
|
|
327
|
+
buf += decoder.decode(value, { stream: true });
|
|
328
|
+
let sep;
|
|
329
|
+
while ((sep = buf.indexOf("\n\n")) !== -1) {
|
|
330
|
+
const frame = buf.slice(0, sep);
|
|
331
|
+
buf = buf.slice(sep + 2);
|
|
332
|
+
const data = frame.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("\n");
|
|
333
|
+
if (!data) continue;
|
|
334
|
+
try {
|
|
335
|
+
onEvent(JSON.parse(data));
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export { AgentPanel, ScriptEditorPage, createBackendAgentRunner };
|