返回 DeepSeek-Reasonix
AppRuntimeEffects.tsx
根目录 / desktop / frontend / src / app-runtime / AppRuntimeEffects.tsx
1 import { useEffect } from "react";
2 import {
3 app,
4 onEvent,
5 onReady,
6 onRemoteForwards,
7 onRemoteServer,
8 onRemoteStatus,
9 onRuntimeRebuilt,
10 } from "../lib/bridge";
11 import { generativeMusic, isGenerativeMusicEnabled } from "../lib/generative-music";
12 import { startTerminalEventBridge } from "../lib/terminalEvents";
13 import { trackAppSubscription } from "./appLifecycleProbe";
14 import { createSubscriptionScope } from "../lib/subscriptionScope";
15
16 export type RuntimeEventListener = Parameters<typeof onEvent>[0];
17 export type RuntimeReadyListener = Parameters<typeof onReady>[0];
18 export type RuntimeRebuiltListener = Parameters<typeof onRuntimeRebuilt>[0];
19 export type RemoteStatusListener = Parameters<typeof onRemoteStatus>[0];
20 export type RemoteForwardsListener = Parameters<typeof onRemoteForwards>[0];
21 export type RemoteServerListener = Parameters<typeof onRemoteServer>[0];
22
23 type AppRuntimeEffectsProps = {
24 running: boolean;
25 onEvent: RuntimeEventListener;
26 onReady: RuntimeReadyListener;
27 onRebuilt: RuntimeRebuiltListener;
28 onRemoteStatus: RemoteStatusListener;
29 onRemoteForwards: RemoteForwardsListener;
30 onRemoteServer: RemoteServerListener;
31 onInitialRemoteHosts: (hosts: Awaited<ReturnType<typeof app.RemoteHosts>>) => void;
32 onInitialRemoteStatuses: (statuses: Awaited<ReturnType<typeof app.RemoteConnectionStatuses>>) => void;
33 };
34
35 /** Owns app-wide bridge subscriptions; App regions never subscribe directly. */
36 export function AppRuntimeEffects(props: AppRuntimeEffectsProps) {
37 const { onEvent: eventListener, onReady: readyListener, onRebuilt, onRemoteStatus: statusListener,
38 onRemoteForwards: forwardsListener, onRemoteServer: serverListener,
39 onInitialRemoteHosts, onInitialRemoteStatuses, running } = props;
40 useEffect(startTerminalEventBridge, []);
41 useEffect(() => {
42 const scope = createSubscriptionScope(trackAppSubscription);
43 scope.listen(onEvent, (event) => {
44 eventListener(event);
45 if (event.kind === "text" || event.kind === "reasoning" || event.kind === "tool_dispatch") {
46 generativeMusic.playTokenNote();
47 }
48 });
49 scope.listen(onReady, readyListener);
50 scope.listen(onRuntimeRebuilt, onRebuilt);
51 scope.listen(onRemoteStatus, statusListener);
52 scope.listen(onRemoteForwards, forwardsListener);
53 scope.listen(onRemoteServer, serverListener);
54 return () => scope.dispose();
55 }, [eventListener, readyListener, onRebuilt, forwardsListener, serverListener, statusListener]);
56
57 useEffect(() => {
58 let disposed = false;
59 void app.RemoteHosts().then((hosts) => { if (!disposed) onInitialRemoteHosts(hosts); }).catch(() => {});
60 void app.RemoteConnectionStatuses().then((statuses) => { if (!disposed) onInitialRemoteStatuses(statuses); }).catch(() => {});
61 return () => { disposed = true; };
62 }, [onInitialRemoteHosts, onInitialRemoteStatuses]);
63
64 useEffect(() => {
65 if (running && isGenerativeMusicEnabled()) generativeMusic.start();
66 else generativeMusic.stop();
67 return () => generativeMusic.stop();
68 }, [running]);
69
70 return null;
71 }
72
72 lines Plain Text