feat(ws): 新增WebSocket实时通信支持与SSE独立服务
重构中间件结构,新增ws模块实现WebSocket流程执行实时推送 将SSE服务拆分为独立端口监听,默认8866 优化前端流式模式切换,支持WS/SSE协议选择 统一流式事件处理逻辑,完善错误处理与取消机制 更新Cargo.toml依赖,添加WebSocket相关库 调整代码组织结构,规范导入分组与注释
This commit is contained in:
@ -50,6 +50,15 @@ export const TestRunSidePanel: FC<TestRunSidePanelProps> = ({ visible, onCancel
|
||||
_setStreamMode(checked);
|
||||
localStorage.setItem('testrun-stream-mode', JSON.stringify(checked));
|
||||
};
|
||||
// 当启用流式时,选择 WS 或 SSE(默认 SSE)
|
||||
const [useWS, _setUseWS] = useState<boolean>(() => {
|
||||
const saved = localStorage.getItem('testrun-ws-mode');
|
||||
return saved ? JSON.parse(saved) : false;
|
||||
});
|
||||
const setUseWS = (checked: boolean) => {
|
||||
_setUseWS(checked);
|
||||
localStorage.setItem('testrun-ws-mode', JSON.stringify(checked));
|
||||
};
|
||||
|
||||
// 流式渲染:实时上下文与日志
|
||||
const [streamCtx, setStreamCtx] = useState<any | undefined>();
|
||||
@ -95,23 +104,43 @@ export const TestRunSidePanel: FC<TestRunSidePanelProps> = ({ visible, onCancel
|
||||
}
|
||||
|
||||
if (streamMode) {
|
||||
const { cancel, done } = customService.runStream(values, {
|
||||
onNode: (evt) => {
|
||||
if (evt.ctx) setStreamCtx((prev: any) => ({ ...(prev || {}), ...(evt.ctx || {}) }));
|
||||
if (evt.logs && evt.logs.length) setStreamLogs((prev: string[]) => [...prev, ...evt.logs!]);
|
||||
},
|
||||
onError: (evt) => {
|
||||
const msg = evt.message || I18n.t('Run failed');
|
||||
setErrors((prev) => [...(prev || []), msg]);
|
||||
},
|
||||
onDone: (evt) => {
|
||||
setResult({ ok: evt.ok, ctx: evt.ctx, logs: evt.logs });
|
||||
},
|
||||
onFatal: (err) => {
|
||||
setErrors((prev) => [...(prev || []), err.message || String(err)]);
|
||||
setRunning(false);
|
||||
},
|
||||
});
|
||||
const startStream = () => useWS
|
||||
? customService.runStreamWS(values, {
|
||||
onNode: (evt) => {
|
||||
if (evt.ctx) setStreamCtx((prev: any) => ({ ...(prev || {}), ...(evt.ctx || {}) }));
|
||||
if (evt.logs && evt.logs.length) setStreamLogs((prev: string[]) => [...prev, ...evt.logs!]);
|
||||
},
|
||||
onError: (evt) => {
|
||||
const msg = evt.message || I18n.t('Run failed');
|
||||
setErrors((prev) => [...(prev || []), msg]);
|
||||
},
|
||||
onDone: (evt) => {
|
||||
setResult({ ok: evt.ok, ctx: evt.ctx, logs: evt.logs });
|
||||
},
|
||||
onFatal: (err) => {
|
||||
setErrors((prev) => [...(prev || []), err.message || String(err)]);
|
||||
setRunning(false);
|
||||
},
|
||||
})
|
||||
: customService.runStream(values, {
|
||||
onNode: (evt) => {
|
||||
if (evt.ctx) setStreamCtx((prev: any) => ({ ...(prev || {}), ...(evt.ctx || {}) }));
|
||||
if (evt.logs && evt.logs.length) setStreamLogs((prev: string[]) => [...prev, ...evt.logs!]);
|
||||
},
|
||||
onError: (evt) => {
|
||||
const msg = evt.message || I18n.t('Run failed');
|
||||
setErrors((prev) => [...(prev || []), msg]);
|
||||
},
|
||||
onDone: (evt) => {
|
||||
setResult({ ok: evt.ok, ctx: evt.ctx, logs: evt.logs });
|
||||
},
|
||||
onFatal: (err) => {
|
||||
setErrors((prev) => [...(prev || []), err.message || String(err)]);
|
||||
setRunning(false);
|
||||
},
|
||||
});
|
||||
|
||||
const { cancel, done } = startStream();
|
||||
|
||||
cancelRef.current = cancel;
|
||||
|
||||
@ -212,18 +241,32 @@ export const TestRunSidePanel: FC<TestRunSidePanelProps> = ({ visible, onCancel
|
||||
<div className={styles['testrun-panel-form']}>
|
||||
<div className={styles['testrun-panel-input']}>
|
||||
<div className={styles.title}>{I18n.t('Input Form')}</div>
|
||||
<div>{I18n.t('JSON Mode')}</div>
|
||||
<Switch
|
||||
checked={inputJSONMode}
|
||||
onChange={(checked: boolean) => setInputJSONMode(checked)}
|
||||
size="small"
|
||||
/>
|
||||
<div>{I18n.t('Streaming Mode')}</div>
|
||||
<Switch
|
||||
checked={streamMode}
|
||||
onChange={(checked: boolean) => setStreamMode(checked)}
|
||||
size="small"
|
||||
/>
|
||||
<div className={styles.toggle}>
|
||||
<div>{I18n.t('JSON Mode')}</div>
|
||||
<Switch
|
||||
checked={inputJSONMode}
|
||||
onChange={(checked: boolean) => setInputJSONMode(checked)}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.toggle}>
|
||||
<div>{I18n.t('Streaming Mode')}</div>
|
||||
<Switch
|
||||
checked={streamMode}
|
||||
onChange={(checked: boolean) => setStreamMode(checked)}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
{streamMode && (
|
||||
<div className={styles.toggle}>
|
||||
<div>WS</div>
|
||||
<Switch
|
||||
checked={useWS}
|
||||
onChange={(checked: boolean) => setUseWS(checked)}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{renderStatus}
|
||||
{errors?.map((e) => (
|
||||
|
||||
Reference in New Issue
Block a user