Line data Source code
1 : import * as React from 'react';
2 :
3 : import { ConnectorConfigs, readConnectorConfigs } from '../connections/connector_configs.js';
4 : import { useLogger } from '../platform/logger/logger_provider.js';
5 : import { Logger } from '../platform/logger/logger.js';
6 : import { createTrace } from '../platform/logger/trace_context.js';
7 : import { AppFlavor, applyFlavorSettings } from './flavors.js';
8 : import { useAppFlavor } from './flavor_provider.js';
9 :
10 1 : const CONFIG_URL = new URL('../../static/config.json', import.meta.url);
11 :
12 : export interface AppSettings {
13 : pauseAfterAppSetup?: boolean;
14 : enableTableColumnPlots?: boolean;
15 : experimental_file_attachments?: boolean;
16 : /// Run the embedded HyperDB on its experimental wasm backend, applied by a restart of the app.
17 : /// A hyperdb-wasm without the backend starts on the default backend.
18 : experimental_wasm_backend?: boolean;
19 : narrowScreenLayout?: boolean;
20 : /// Lay out the app like the desktop app on macOS, with placeholders of the traffic lights, to check the layout in the browser
21 : macWindowLayout?: boolean;
22 : /// Show the labels of the buttons in a wide rail on the left
23 : navRailWide?: boolean;
24 : scriptDebugMode?: boolean;
25 : tableDebugMode?: boolean;
26 : formattingDebugMode?: boolean;
27 : keywordCase?: 'lower' | 'upper';
28 : /// Force the OAuth identity provider to re-prompt for login instead of silently reusing an
29 : /// existing browser session. Adds `prompt=login` to the authorization request.
30 : forceReLogin?: boolean;
31 : minLogLevel?: number;
32 : /// The id of the app flavor, the flavor query parameter of the url wins
33 : flavor?: string;
34 : /// The color scheme of the app, the one of the OS if unset
35 : colorScheme?: 'light' | 'dark';
36 : /// The sessions whose scripts agents may read, edit and run through the local agent server
37 : agentSessions?: string[];
38 : /// The port of the local agent server, the build's default if unset
39 : agentPort?: number;
40 : /// The harness that runs the requests of the agent, the default harness of the agent server if unset
41 : agentHarness?: string;
42 : /// The model of the harness's requests, one of the models of the harness, the harness's own default if unset
43 : agentModel?: string;
44 : /// The effort of the harness's requests, one of the efforts of the harness, the harness's own default if unset
45 : agentEffort?: string;
46 : }
47 :
48 : export interface AppConfig {
49 : settings?: AppSettings;
50 : connectors?: ConnectorConfigs;
51 : }
52 :
53 1 : function readAppConfig(object: Record<string, object>): AppConfig {
54 1 : if (object.connectors) {
55 1 : object.connectors = readConnectorConfigs(object.connectors);
56 : }
57 1 : return object as AppConfig;
58 1 : }
59 :
60 : export interface AppConfigResult {
61 : config: AppConfig;
62 : traceId: number;
63 : }
64 :
65 1 : export async function downloadAppConfig(logger: Logger, flavor: AppFlavor): Promise<AppConfigResult> {
66 1 : const trace = createTrace();
67 1 : const traced = logger.withTrace(trace);
68 1 : try {
69 1 : const resp = await fetch(CONFIG_URL as unknown as string);
70 1 : const body = await resp.json();
71 1 : const config = readAppConfig(body);
72 1 : config.settings = applyFlavorSettings(config.settings, flavor);
73 1 : traced.info("Loaded app config", {}, "app_config");
74 1 : return { config, traceId: trace.traceId };
75 1 : } catch (e: any) {
76 1 : console.error(e);
77 1 : throw e;
78 1 : }
79 1 : };
80 :
81 : type ReconfigureStateFn = (res: AppConfig | null) => (AppConfig | null);
82 1 : const RECONFIGURE_CTX = React.createContext<((config: ReconfigureStateFn) => void) | null>(null);
83 1 : const CONFIG_CTX = React.createContext<AppConfig | null>(null);
84 1 : const CONFIG_TRACE_ID_CTX = React.createContext<number | null>(null);
85 :
86 : type Props = {
87 : children: React.ReactElement;
88 : };
89 :
90 1 : export const AppConfigProvider: React.FC<Props> = (props: Props) => {
91 1 : const logger = useLogger();
92 1 : const flavor = useAppFlavor();
93 1 : const configPromise = React.useRef<Promise<AppConfigResult>>(null);
94 1 : const [config, setConfig] = React.useState<AppConfig | null>(null);
95 1 : const [traceId, setTraceId] = React.useState<number | null>(null);
96 :
97 1 : React.useEffect(() => {
98 1 : if (!configPromise.current) {
99 1 : configPromise.current = downloadAppConfig(logger, flavor);
100 1 : configPromise.current.then(result => {
101 1 : setConfig(result.config);
102 1 : setTraceId(result.traceId);
103 1 : }).catch(() => {
104 0 : setConfig(null);
105 0 : setTraceId(null);
106 1 : });
107 1 : }
108 1 : }, []);
109 :
110 1 : const reconfigure = React.useCallback((mapper: ReconfigureStateFn) => {
111 1 : logger.info(`Reconfiguring application`, {}, "app_config");
112 1 : setConfig(c => mapper(c));
113 1 : }, []);
114 :
115 1 : return (
116 1 : <CONFIG_CTX.Provider value={config}>
117 1 : <CONFIG_TRACE_ID_CTX.Provider value={traceId}>
118 1 : <RECONFIGURE_CTX.Provider value={reconfigure}>{props.children}</RECONFIGURE_CTX.Provider>
119 1 : </CONFIG_TRACE_ID_CTX.Provider>
120 1 : </CONFIG_CTX.Provider>
121 : );
122 1 : };
123 :
124 1 : export const useAppConfig = (): AppConfig | null => React.useContext(CONFIG_CTX);
125 : export const useAppConfigTraceId = (): number | null => React.useContext(CONFIG_TRACE_ID_CTX);
126 1 : export const useAppReconfigure = (): ((config: ReconfigureStateFn) => void) => React.useContext(RECONFIGURE_CTX)!;
|