@cniot/mdd-editor 0.3.1 → 0.3.3
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 +9 -0
- package/build/index.cjs.js +34 -18
- package/build/index.es.js +250 -24
- package/build/style.css +1 -1
- package/package.json +1 -1
- package/src/ai/LocalAIDrawer.jsx +204 -4
- package/src/ai/bridgeClient.js +67 -15
package/src/ai/LocalAIDrawer.jsx
CHANGED
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
import { buildPageIR, getPageCode } from './pageIR';
|
|
14
14
|
import { EVENT_KEY } from '$src/common/const';
|
|
15
15
|
|
|
16
|
+
const MIN_BRIDGE_VERSION = '0.1.4';
|
|
17
|
+
const START_COMMAND = `npm i -g @cniot/mdd-ai-bridge
|
|
18
|
+
mdd-ai-bridge`;
|
|
19
|
+
|
|
16
20
|
const parseSchema = (value) => {
|
|
17
21
|
if (!value) return null;
|
|
18
22
|
if (typeof value === 'string') {
|
|
@@ -34,14 +38,134 @@ const applySchemaToEditor = (schema, schemaJson) => {
|
|
|
34
38
|
schema.emit(EVENT_KEY.SCHEMA_UPDATE_FORCE, schemaJson);
|
|
35
39
|
};
|
|
36
40
|
|
|
41
|
+
const compareVersion = (a = '', b = '') => {
|
|
42
|
+
const left = String(a).split('.').map((item) => Number(item) || 0);
|
|
43
|
+
const right = String(b).split('.').map((item) => Number(item) || 0);
|
|
44
|
+
const max = Math.max(left.length, right.length);
|
|
45
|
+
for (let i = 0; i < max; i += 1) {
|
|
46
|
+
if ((left[i] || 0) > (right[i] || 0)) return 1;
|
|
47
|
+
if ((left[i] || 0) < (right[i] || 0)) return -1;
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isBridgeVersionReady = (version) => Boolean(version) && compareVersion(version, MIN_BRIDGE_VERSION) >= 0;
|
|
53
|
+
|
|
54
|
+
const copyText = async (text) => {
|
|
55
|
+
if (typeof navigator !== 'undefined' && navigator?.clipboard?.writeText) {
|
|
56
|
+
await navigator.clipboard.writeText(text);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const textarea = document.createElement('textarea');
|
|
60
|
+
textarea.value = text;
|
|
61
|
+
textarea.setAttribute('readonly', 'readonly');
|
|
62
|
+
textarea.style.position = 'fixed';
|
|
63
|
+
textarea.style.left = '-9999px';
|
|
64
|
+
document.body.appendChild(textarea);
|
|
65
|
+
textarea.select();
|
|
66
|
+
document.execCommand('copy');
|
|
67
|
+
document.body.removeChild(textarea);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const getJsonErrorDetail = (error) => {
|
|
71
|
+
const jsonError = error?.data?.jsonError;
|
|
72
|
+
if (jsonError) {
|
|
73
|
+
return {
|
|
74
|
+
title: `${jsonError.file || 'JSON'} 解析失败${
|
|
75
|
+
jsonError.line ? `:第 ${jsonError.line} 行${jsonError.column ? `,第 ${jsonError.column} 列` : ''}` : ''
|
|
76
|
+
}`,
|
|
77
|
+
message: jsonError.message || error.message || '',
|
|
78
|
+
snippet: jsonError.snippet || '',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
title: '同步失败',
|
|
83
|
+
message: error?.message || '同步本地 AI 修改失败',
|
|
84
|
+
snippet: '',
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getChangeTypeText = (type) => {
|
|
89
|
+
switch (type) {
|
|
90
|
+
case 'added':
|
|
91
|
+
return '新增';
|
|
92
|
+
case 'removed':
|
|
93
|
+
return '删除';
|
|
94
|
+
case 'type':
|
|
95
|
+
return '类型变化';
|
|
96
|
+
case 'changed':
|
|
97
|
+
return '修改';
|
|
98
|
+
default:
|
|
99
|
+
return type || '修改';
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
function PullDiffDetail({ diffSummary }) {
|
|
104
|
+
if (!diffSummary?.hasBaseline) return null;
|
|
105
|
+
const schemaChanges = diffSummary.schema?.changes || diffSummary.schema?.paths || [];
|
|
106
|
+
const diffText = diffSummary.diffText || '';
|
|
107
|
+
const hasDetail = schemaChanges.length > 0 || diffSummary.script?.changed || diffSummary.style?.changed || diffText;
|
|
108
|
+
if (!hasDetail) return null;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<details className="mdd-local-ai-diff">
|
|
112
|
+
<summary>查看具体改动与 diff</summary>
|
|
113
|
+
<div className="mdd-local-ai-diff-content">
|
|
114
|
+
{schemaChanges.length > 0 ? (
|
|
115
|
+
<div className="mdd-local-ai-diff-section">
|
|
116
|
+
<div className="mdd-local-ai-diff-title">Schema 改动位置</div>
|
|
117
|
+
<ul className="mdd-local-ai-diff-list">
|
|
118
|
+
{schemaChanges.map((item, index) => {
|
|
119
|
+
const path = typeof item === 'string' ? item : item.path;
|
|
120
|
+
const type = typeof item === 'string' ? 'changed' : item.type;
|
|
121
|
+
return (
|
|
122
|
+
<li key={`${path}-${index}`}>
|
|
123
|
+
<code>{path}</code>
|
|
124
|
+
<span>{getChangeTypeText(type)}</span>
|
|
125
|
+
</li>
|
|
126
|
+
);
|
|
127
|
+
})}
|
|
128
|
+
</ul>
|
|
129
|
+
</div>
|
|
130
|
+
) : null}
|
|
131
|
+
|
|
132
|
+
{diffSummary.script?.changed ? (
|
|
133
|
+
<div className="mdd-local-ai-diff-section">
|
|
134
|
+
<div className="mdd-local-ai-diff-title">Script 改动</div>
|
|
135
|
+
<div>{diffSummary.script.message}</div>
|
|
136
|
+
{diffSummary.script.addedFunctions?.length ? (
|
|
137
|
+
<div>新增:{diffSummary.script.addedFunctions.join('、')}</div>
|
|
138
|
+
) : null}
|
|
139
|
+
{diffSummary.script.removedFunctions?.length ? (
|
|
140
|
+
<div>移除:{diffSummary.script.removedFunctions.join('、')}</div>
|
|
141
|
+
) : null}
|
|
142
|
+
</div>
|
|
143
|
+
) : null}
|
|
144
|
+
|
|
145
|
+
{diffSummary.style?.changed ? (
|
|
146
|
+
<div className="mdd-local-ai-diff-section">
|
|
147
|
+
<div className="mdd-local-ai-diff-title">Style 改动</div>
|
|
148
|
+
<div>{diffSummary.style.message}</div>
|
|
149
|
+
</div>
|
|
150
|
+
) : null}
|
|
151
|
+
|
|
152
|
+
{diffText ? <pre className="mdd-local-ai-diff-pre">{diffText}</pre> : null}
|
|
153
|
+
</div>
|
|
154
|
+
</details>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
37
158
|
export default function LocalAIDrawer(props) {
|
|
38
159
|
const { schema, scriptInfo, pageMeta = {}, bridgeConfig, onApply } = props;
|
|
39
160
|
const config = normalizeBridgeConfig(bridgeConfig);
|
|
40
161
|
const [baseURL, setBaseURL] = React.useState(config.baseURL || DEFAULT_BRIDGE_URL);
|
|
41
162
|
const [status, setStatus] = React.useState('未连接');
|
|
42
163
|
const [workspacePath, setWorkspacePath] = React.useState('');
|
|
164
|
+
const [bridgeInfo, setBridgeInfo] = React.useState(null);
|
|
43
165
|
const [loadingAction, setLoadingAction] = React.useState('');
|
|
44
166
|
const [lastSummary, setLastSummary] = React.useState('');
|
|
167
|
+
const [pullError, setPullError] = React.useState(null);
|
|
168
|
+
const [lastDiffSummary, setLastDiffSummary] = React.useState(null);
|
|
45
169
|
|
|
46
170
|
const schemaJson = schema?.getAllJSON?.() || {};
|
|
47
171
|
const pageCode = getPageCode(schemaJson, pageMeta);
|
|
@@ -69,13 +193,54 @@ export default function LocalAIDrawer(props) {
|
|
|
69
193
|
};
|
|
70
194
|
}, [pageCode, pageMeta, schema, scriptInfo]);
|
|
71
195
|
|
|
196
|
+
const expectedWorkspacePath = React.useMemo(() => {
|
|
197
|
+
if (workspacePath) return workspacePath;
|
|
198
|
+
const root = bridgeInfo?.workspaceRoot || '~/.mdd-ai-workspace';
|
|
199
|
+
return `${root.replace(/\/$/, '')}/pages/${pageCode}`;
|
|
200
|
+
}, [bridgeInfo, pageCode, workspacePath]);
|
|
201
|
+
|
|
202
|
+
const aiPrompt = React.useMemo(
|
|
203
|
+
() =>
|
|
204
|
+
`请帮我修改这个 MDD 页面:
|
|
205
|
+
|
|
206
|
+
工作区:${expectedWorkspacePath}
|
|
207
|
+
页面 Code:${pageCode}
|
|
208
|
+
|
|
209
|
+
请先阅读同目录下的 AGENTS.md 和 PROMPT.md,再按需求修改:
|
|
210
|
+
- mdd.schema.json:页面 schema
|
|
211
|
+
- mdd.script.jsx:页面脚本
|
|
212
|
+
- mdd.style.less:页面样式
|
|
213
|
+
|
|
214
|
+
注意:
|
|
215
|
+
1. 不要修改 page.ir.json / page.meta.json 作为最终结果。
|
|
216
|
+
2. JSON 必须保持合法,尽量只改和需求相关的位置。
|
|
217
|
+
3. MDD hook / engine API 不确定时,先读 context 目录里的 api-cheatsheet.md、engine-runtime.md、hooks-cookbook.md。
|
|
218
|
+
|
|
219
|
+
我的需求是:`,
|
|
220
|
+
[expectedWorkspacePath, pageCode],
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const handleCopy = React.useCallback(async (text, successText) => {
|
|
224
|
+
try {
|
|
225
|
+
await copyText(text);
|
|
226
|
+
Message.success(successText);
|
|
227
|
+
} catch (e) {
|
|
228
|
+
Message.error('复制失败,请手动选择复制');
|
|
229
|
+
}
|
|
230
|
+
}, []);
|
|
231
|
+
|
|
72
232
|
const checkHealth = React.useCallback(async () => {
|
|
73
233
|
setLoadingAction('health');
|
|
74
234
|
try {
|
|
75
235
|
const res = await health(baseURL);
|
|
76
|
-
|
|
77
|
-
if (res?.
|
|
78
|
-
|
|
236
|
+
setBridgeInfo(res || null);
|
|
237
|
+
if (res?.ok && isBridgeVersionReady(res.version)) {
|
|
238
|
+
setStatus(`已连接(Bridge v${res.version})`);
|
|
239
|
+
} else if (res?.ok) {
|
|
240
|
+
setStatus(`Bridge 版本偏旧${res?.version ? `(v${res.version})` : ''}`);
|
|
241
|
+
setLastSummary(`建议升级本地 bridge 到 ${MIN_BRIDGE_VERSION} 或更高版本:npm i -g @cniot/mdd-ai-bridge@latest`);
|
|
242
|
+
} else {
|
|
243
|
+
setStatus('连接异常');
|
|
79
244
|
}
|
|
80
245
|
} catch (e) {
|
|
81
246
|
setStatus('未连接');
|
|
@@ -87,9 +252,15 @@ export default function LocalAIDrawer(props) {
|
|
|
87
252
|
|
|
88
253
|
const handlePush = async () => {
|
|
89
254
|
setLoadingAction('push');
|
|
255
|
+
setPullError(null);
|
|
256
|
+
setLastDiffSummary(null);
|
|
90
257
|
try {
|
|
91
258
|
const res = await pushPage(baseURL, pageCode, getPayload());
|
|
92
259
|
setWorkspacePath(res?.dir || '');
|
|
260
|
+
setBridgeInfo((prev) => ({
|
|
261
|
+
...(prev || {}),
|
|
262
|
+
workspaceRoot: res?.context?.workspaceRoot || prev?.workspaceRoot,
|
|
263
|
+
}));
|
|
93
264
|
setStatus('已同步到本地');
|
|
94
265
|
setLastSummary(`已写入本地工作区: ${res?.dir || pageCode}`);
|
|
95
266
|
Message.success('已发送到本地 AI 工作区');
|
|
@@ -102,6 +273,7 @@ export default function LocalAIDrawer(props) {
|
|
|
102
273
|
|
|
103
274
|
const handlePull = async () => {
|
|
104
275
|
setLoadingAction('pull');
|
|
276
|
+
setPullError(null);
|
|
105
277
|
try {
|
|
106
278
|
const res = await pullPage(baseURL, pageCode);
|
|
107
279
|
const nextSchema = parseSchema(res?.schemaInfo);
|
|
@@ -117,9 +289,13 @@ export default function LocalAIDrawer(props) {
|
|
|
117
289
|
raw: res,
|
|
118
290
|
});
|
|
119
291
|
setLastSummary(res?.summary || '已从本地工作区同步修改');
|
|
292
|
+
setLastDiffSummary(res?.diffSummary || null);
|
|
120
293
|
Message.success('已同步本地 AI 修改');
|
|
121
294
|
} catch (e) {
|
|
122
|
-
|
|
295
|
+
const detail = getJsonErrorDetail(e);
|
|
296
|
+
setPullError(detail);
|
|
297
|
+
setLastDiffSummary(null);
|
|
298
|
+
Message.error(detail.title || '同步本地 AI 修改失败');
|
|
123
299
|
} finally {
|
|
124
300
|
setLoadingAction('');
|
|
125
301
|
}
|
|
@@ -130,6 +306,7 @@ export default function LocalAIDrawer(props) {
|
|
|
130
306
|
try {
|
|
131
307
|
const res = await getPageStatus(baseURL, pageCode);
|
|
132
308
|
setWorkspacePath(res?.dir || '');
|
|
309
|
+
setLastDiffSummary(null);
|
|
133
310
|
setLastSummary(res?.exists ? `本地工作区已存在: ${res.dir}` : '本地还没有这个页面的工作区');
|
|
134
311
|
Message.success(res?.exists ? '本地工作区已存在' : '本地工作区不存在');
|
|
135
312
|
} catch (e) {
|
|
@@ -178,6 +355,7 @@ export default function LocalAIDrawer(props) {
|
|
|
178
355
|
</div>
|
|
179
356
|
<div className="mdd-local-ai-meta">
|
|
180
357
|
<div>状态:{status}</div>
|
|
358
|
+
{bridgeInfo?.version ? <div>Bridge 版本:{bridgeInfo.version}</div> : null}
|
|
181
359
|
<div>页面:{pageCode}</div>
|
|
182
360
|
{workspacePath ? <div>目录:{workspacePath}</div> : null}
|
|
183
361
|
</div>
|
|
@@ -198,8 +376,30 @@ export default function LocalAIDrawer(props) {
|
|
|
198
376
|
</CnButton>
|
|
199
377
|
</div>
|
|
200
378
|
|
|
379
|
+
<div className="mdd-local-ai-copy-actions">
|
|
380
|
+
<CnButton size="small" onClick={() => handleCopy(START_COMMAND, '已复制启动命令')}>
|
|
381
|
+
复制启动命令
|
|
382
|
+
</CnButton>
|
|
383
|
+
<CnButton size="small" onClick={() => handleCopy(expectedWorkspacePath, '已复制工作区路径')}>
|
|
384
|
+
复制工作区路径
|
|
385
|
+
</CnButton>
|
|
386
|
+
<CnButton size="small" onClick={() => handleCopy(aiPrompt, '已复制 AI 提示词')}>
|
|
387
|
+
复制 AI 提示词
|
|
388
|
+
</CnButton>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
201
391
|
{lastSummary ? <div className="mdd-local-ai-summary">{lastSummary}</div> : null}
|
|
202
392
|
|
|
393
|
+
<PullDiffDetail diffSummary={lastDiffSummary} />
|
|
394
|
+
|
|
395
|
+
{pullError ? (
|
|
396
|
+
<div className="mdd-local-ai-error">
|
|
397
|
+
<div className="mdd-local-ai-error-title">{pullError.title}</div>
|
|
398
|
+
<div className="mdd-local-ai-error-message">{pullError.message}</div>
|
|
399
|
+
{pullError.snippet ? <pre className="mdd-local-ai-error-snippet">{pullError.snippet}</pre> : null}
|
|
400
|
+
</div>
|
|
401
|
+
) : null}
|
|
402
|
+
|
|
203
403
|
<CnCard className="mdd-local-ai-card">
|
|
204
404
|
<div className="mdd-local-ai-title">使用方式</div>
|
|
205
405
|
<div className="mdd-local-ai-tips">
|
package/src/ai/bridgeClient.js
CHANGED
|
@@ -2,9 +2,19 @@ export const DEFAULT_BRIDGE_URL = 'http://127.0.0.1:17678';
|
|
|
2
2
|
|
|
3
3
|
const trimEndSlash = (value = '') => value.replace(/\/+$/, '');
|
|
4
4
|
const RELAY_CHANNEL = 'mdd-ai-bridge';
|
|
5
|
+
const relayCacheMap = new Map();
|
|
5
6
|
|
|
6
7
|
const isLoopbackURL = (value = '') => /^http:\/\/(127(?:\.\d{1,3}){3}|localhost)(?::\d+)?/i.test(value);
|
|
7
8
|
|
|
9
|
+
export class BridgeRequestError extends Error {
|
|
10
|
+
constructor(message, options = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'BridgeRequestError';
|
|
13
|
+
this.status = options.status || 0;
|
|
14
|
+
this.data = options.data || null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
8
18
|
export function shouldUseBridgeRelay(baseURL) {
|
|
9
19
|
return typeof window !== 'undefined' && !window.isSecureContext && isLoopbackURL(baseURL);
|
|
10
20
|
}
|
|
@@ -35,14 +45,22 @@ async function request(baseURL, path, options = {}) {
|
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
if (!res.ok) {
|
|
38
|
-
throw new
|
|
48
|
+
throw new BridgeRequestError(data?.message || `请求本地 AI Bridge 失败: ${res.status}`, {
|
|
49
|
+
status: res.status,
|
|
50
|
+
data,
|
|
51
|
+
});
|
|
39
52
|
}
|
|
40
53
|
return data;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
|
-
function
|
|
44
|
-
const
|
|
45
|
-
const
|
|
56
|
+
function getRelayCache(baseURL) {
|
|
57
|
+
const relayOrigin = trimEndSlash(baseURL);
|
|
58
|
+
const relayURL = `${relayOrigin}/relay`;
|
|
59
|
+
const cached = relayCacheMap.get(relayOrigin);
|
|
60
|
+
if (cached?.window && !cached.window.closed) {
|
|
61
|
+
return cached;
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
const relayWindow = window.open(
|
|
47
65
|
relayURL,
|
|
48
66
|
'mdd-ai-bridge-relay',
|
|
@@ -53,9 +71,23 @@ function requestViaRelay(baseURL, path, options = {}) {
|
|
|
53
71
|
throw new Error('浏览器拦截了本地 Bridge 窗口,请允许弹窗后重试');
|
|
54
72
|
}
|
|
55
73
|
|
|
74
|
+
const next = {
|
|
75
|
+
origin: relayOrigin,
|
|
76
|
+
window: relayWindow,
|
|
77
|
+
ready: false,
|
|
78
|
+
};
|
|
79
|
+
relayCacheMap.set(relayOrigin, next);
|
|
80
|
+
return next;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function requestViaRelay(baseURL, path, options = {}) {
|
|
84
|
+
const relayCache = getRelayCache(baseURL);
|
|
85
|
+
const requestId = createRequestId();
|
|
86
|
+
|
|
56
87
|
return new Promise((resolve, reject) => {
|
|
57
88
|
let settled = false;
|
|
58
|
-
let
|
|
89
|
+
let fallbackPosted = false;
|
|
90
|
+
let readyPosted = false;
|
|
59
91
|
|
|
60
92
|
const cleanup = () => {
|
|
61
93
|
window.removeEventListener('message', handleMessage);
|
|
@@ -63,8 +95,19 @@ function requestViaRelay(baseURL, path, options = {}) {
|
|
|
63
95
|
clearTimeout(fallbackTimer);
|
|
64
96
|
};
|
|
65
97
|
|
|
66
|
-
const postRequest = () => {
|
|
67
|
-
|
|
98
|
+
const postRequest = (source = 'ready') => {
|
|
99
|
+
if (source === 'ready') {
|
|
100
|
+
if (readyPosted) return;
|
|
101
|
+
readyPosted = true;
|
|
102
|
+
} else {
|
|
103
|
+
if (fallbackPosted) return;
|
|
104
|
+
fallbackPosted = true;
|
|
105
|
+
}
|
|
106
|
+
if (!relayCache.window || relayCache.window.closed) {
|
|
107
|
+
finish(reject, new Error('本地 Bridge relay 窗口已关闭,请重试'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
relayCache.window.postMessage(
|
|
68
111
|
{
|
|
69
112
|
channel: RELAY_CHANNEL,
|
|
70
113
|
type: 'request',
|
|
@@ -72,7 +115,7 @@ function requestViaRelay(baseURL, path, options = {}) {
|
|
|
72
115
|
path,
|
|
73
116
|
options,
|
|
74
117
|
},
|
|
75
|
-
|
|
118
|
+
relayCache.origin,
|
|
76
119
|
);
|
|
77
120
|
};
|
|
78
121
|
|
|
@@ -84,14 +127,14 @@ function requestViaRelay(baseURL, path, options = {}) {
|
|
|
84
127
|
};
|
|
85
128
|
|
|
86
129
|
const handleMessage = (event) => {
|
|
87
|
-
if (event.origin !==
|
|
88
|
-
if (event.source !==
|
|
130
|
+
if (event.origin !== relayCache.origin) return;
|
|
131
|
+
if (event.source !== relayCache.window) return;
|
|
89
132
|
const message = event.data || {};
|
|
90
133
|
if (message.channel !== RELAY_CHANNEL) return;
|
|
91
134
|
|
|
92
|
-
if (message.type === 'ready'
|
|
93
|
-
ready = true;
|
|
94
|
-
postRequest();
|
|
135
|
+
if (message.type === 'ready') {
|
|
136
|
+
relayCache.ready = true;
|
|
137
|
+
postRequest('ready');
|
|
95
138
|
return;
|
|
96
139
|
}
|
|
97
140
|
|
|
@@ -99,7 +142,13 @@ function requestViaRelay(baseURL, path, options = {}) {
|
|
|
99
142
|
if (!message.ok) {
|
|
100
143
|
finish(
|
|
101
144
|
reject,
|
|
102
|
-
new
|
|
145
|
+
new BridgeRequestError(
|
|
146
|
+
message.data?.message || `请求本地 AI Bridge 失败: ${message.status || 0}`,
|
|
147
|
+
{
|
|
148
|
+
status: message.status || 0,
|
|
149
|
+
data: message.data,
|
|
150
|
+
},
|
|
151
|
+
),
|
|
103
152
|
);
|
|
104
153
|
return;
|
|
105
154
|
}
|
|
@@ -110,10 +159,13 @@ function requestViaRelay(baseURL, path, options = {}) {
|
|
|
110
159
|
finish(reject, new Error('本地 AI Bridge relay 响应超时,请确认服务已启动'));
|
|
111
160
|
}, 30000);
|
|
112
161
|
const fallbackTimer = setTimeout(() => {
|
|
113
|
-
if (!ready) postRequest();
|
|
162
|
+
if (!relayCache.ready) postRequest('fallback');
|
|
114
163
|
}, 500);
|
|
115
164
|
|
|
116
165
|
window.addEventListener('message', handleMessage);
|
|
166
|
+
if (relayCache.ready) {
|
|
167
|
+
setTimeout(() => postRequest('ready'), 0);
|
|
168
|
+
}
|
|
117
169
|
});
|
|
118
170
|
}
|
|
119
171
|
|