@iblai/iblai-js 2.3.3 → 2.3.5
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.
|
@@ -440,15 +440,15 @@ const TAURI_COMMANDS = {
|
|
|
440
440
|
// Contract for the Cowork manager, which installs and sets up GhostOS
|
|
441
441
|
// (https://github.com/ghostwright/ghost-os) on-device via the Tauri host. This
|
|
442
442
|
// mirrors the Local Models (Ollama) contract above: the web layer ships the
|
|
443
|
-
// presentational card + types, while the Tauri host
|
|
444
|
-
//
|
|
443
|
+
// presentational card + types, while the Tauri host implements the native
|
|
444
|
+
// install/status commands and owns the driver process.
|
|
445
445
|
// ============================================================================
|
|
446
|
-
/**
|
|
447
|
-
const
|
|
446
|
+
/** Upstream project the Cowork driver comes from. */
|
|
447
|
+
const CUA_DRIVER_REPO_URL = 'https://github.com/trycua/cua';
|
|
448
448
|
/**
|
|
449
449
|
* Initial state for the Cowork install flow.
|
|
450
450
|
*/
|
|
451
|
-
const
|
|
451
|
+
const initialCuaDriverInstallState = {
|
|
452
452
|
status: 'idle',
|
|
453
453
|
progress: 0,
|
|
454
454
|
message: '',
|
|
@@ -456,24 +456,27 @@ const initialGhostOsInstallState = {
|
|
|
456
456
|
lastUpdated: new Date().toISOString(),
|
|
457
457
|
};
|
|
458
458
|
/**
|
|
459
|
-
* Tauri event names emitted by the host during
|
|
459
|
+
* Tauri event names emitted by the host during Cua Driver install.
|
|
460
460
|
*/
|
|
461
|
-
const
|
|
462
|
-
INSTALL_PROGRESS: '
|
|
463
|
-
INSTALLATION_LOG: '
|
|
464
|
-
STATUS: '
|
|
461
|
+
const CUA_DRIVER_TAURI_EVENTS = {
|
|
462
|
+
INSTALL_PROGRESS: 'cua-driver:install-progress',
|
|
463
|
+
INSTALLATION_LOG: 'cua-driver:installation-log',
|
|
464
|
+
STATUS: 'cua-driver:status',
|
|
465
465
|
};
|
|
466
466
|
/**
|
|
467
|
-
* Tauri command names the host implements for
|
|
468
|
-
*
|
|
467
|
+
* Tauri command names the host implements for the Cua Driver. The web layer
|
|
468
|
+
* invokes these; the native side downloads the pinned per-platform binary and
|
|
469
|
+
* owns the MCP process.
|
|
469
470
|
*/
|
|
470
|
-
const
|
|
471
|
-
/**
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
|
|
471
|
+
const CUA_DRIVER_TAURI_COMMANDS = {
|
|
472
|
+
/** Download + install the pinned Cua Driver binary. */
|
|
473
|
+
INSTALL: 'install_cua_driver',
|
|
474
|
+
/** Report current {@link CuaDriverStatus}. */
|
|
475
|
+
CHECK_STATUS: 'check_cua_driver_status',
|
|
476
|
+
/** Whether Cowork can run on this desktop session, and why not if it can't. */
|
|
477
|
+
SUPPORT: 'cua_driver_support',
|
|
478
|
+
/** Stop the driver's MCP process. */
|
|
479
|
+
STOP: 'cua_driver_stop',
|
|
477
480
|
};
|
|
478
481
|
/**
|
|
479
482
|
* Tauri command strings exposed by `tauri-plugin-macos-permissions`
|
|
@@ -560,16 +563,26 @@ function useTauri() {
|
|
|
560
563
|
};
|
|
561
564
|
}
|
|
562
565
|
|
|
563
|
-
|
|
566
|
+
// Evaluated per call, not once at module load. A module-level constant is
|
|
567
|
+
// captured before any test can influence it, which left every server-side guard
|
|
568
|
+
// below permanently unreachable — and therefore unverifiable — in a jsdom runner.
|
|
569
|
+
const isServer = () => typeof window === 'undefined';
|
|
564
570
|
/**
|
|
565
571
|
* A simplified useLocalStorage hook for persisting state to localStorage
|
|
566
572
|
* with SSR safety.
|
|
567
573
|
*/
|
|
568
574
|
function useLocalStorage(key, initialValue) {
|
|
575
|
+
// The initial value is captured, not tracked. Depending on its identity meant
|
|
576
|
+
// an inline literal — `useLocalStorage('k', { n: 0 })`, the ordinary React
|
|
577
|
+
// idiom — gave `getInitialValue` a new identity every render, which re-ran the
|
|
578
|
+
// key-change effect below, which called `setStoredValue` with a fresh object,
|
|
579
|
+
// which re-rendered… an infinite loop that ends in an out-of-memory crash.
|
|
580
|
+
// Existing callers only escaped it by happening to pass module constants.
|
|
581
|
+
const initialRef = useRef(initialValue);
|
|
569
582
|
// Get the initial value
|
|
570
583
|
const getInitialValue = useCallback(() => {
|
|
571
|
-
const initial =
|
|
572
|
-
if (
|
|
584
|
+
const initial = initialRef.current instanceof Function ? initialRef.current() : initialRef.current;
|
|
585
|
+
if (isServer()) {
|
|
573
586
|
return initial;
|
|
574
587
|
}
|
|
575
588
|
try {
|
|
@@ -583,42 +596,59 @@ function useLocalStorage(key, initialValue) {
|
|
|
583
596
|
console.warn(`Error reading localStorage key "${key}":`, error);
|
|
584
597
|
return initial;
|
|
585
598
|
}
|
|
586
|
-
}, [key
|
|
599
|
+
}, [key]);
|
|
587
600
|
const [storedValue, setStoredValue] = useState(getInitialValue);
|
|
588
601
|
// Update stored value when key changes
|
|
589
602
|
useEffect(() => {
|
|
590
603
|
setStoredValue(getInitialValue());
|
|
591
604
|
}, [key, getInitialValue]);
|
|
592
|
-
//
|
|
605
|
+
// Latest value, readable synchronously. Two writes in the same tick would
|
|
606
|
+
// otherwise both resolve their updater against the render's snapshot, so the
|
|
607
|
+
// second silently reverts the first (a status change followed by a log append
|
|
608
|
+
// losing the status, for instance).
|
|
609
|
+
const storedRef = useRef(storedValue);
|
|
610
|
+
storedRef.current = storedValue;
|
|
611
|
+
// Persist to localStorage.
|
|
612
|
+
//
|
|
613
|
+
// Depends on `key` ALONE, deliberately. This used to close over `storedValue`,
|
|
614
|
+
// which gave the setter a new identity on every value change — and anything
|
|
615
|
+
// built on it (a `useCallback` logger, say) churned too. An effect listing
|
|
616
|
+
// those in its dependencies then re-ran on every state change; where such an
|
|
617
|
+
// effect subscribes asynchronously, its cleanup runs before the subscription
|
|
618
|
+
// resolves and unregisters nothing, so each pass leaks another live listener
|
|
619
|
+
// and one event fans out into two, four, eight… until the webview locks up.
|
|
593
620
|
const setValue = useCallback((value) => {
|
|
594
|
-
if (
|
|
621
|
+
if (isServer()) {
|
|
595
622
|
console.warn(`Tried setting localStorage key "${key}" even though environment is not a client`);
|
|
596
623
|
return;
|
|
597
624
|
}
|
|
598
625
|
try {
|
|
599
|
-
const valueToStore = value instanceof Function ? value(
|
|
626
|
+
const valueToStore = value instanceof Function ? value(storedRef.current) : value;
|
|
627
|
+
storedRef.current = valueToStore;
|
|
600
628
|
setStoredValue(valueToStore);
|
|
601
629
|
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
602
630
|
}
|
|
603
631
|
catch (error) {
|
|
604
632
|
console.warn(`Error setting localStorage key "${key}":`, error);
|
|
605
633
|
}
|
|
606
|
-
}, [key
|
|
634
|
+
}, [key]);
|
|
607
635
|
// Remove from localStorage
|
|
608
636
|
const removeValue = useCallback(() => {
|
|
609
|
-
if (
|
|
637
|
+
if (isServer()) {
|
|
610
638
|
console.warn(`Tried removing localStorage key "${key}" even though environment is not a client`);
|
|
611
639
|
return;
|
|
612
640
|
}
|
|
613
641
|
try {
|
|
614
|
-
const initial =
|
|
642
|
+
const initial = initialRef.current instanceof Function ? initialRef.current() : initialRef.current;
|
|
615
643
|
window.localStorage.removeItem(key);
|
|
616
644
|
setStoredValue(initial);
|
|
617
645
|
}
|
|
618
646
|
catch (error) {
|
|
619
647
|
console.warn(`Error removing localStorage key "${key}":`, error);
|
|
620
648
|
}
|
|
621
|
-
|
|
649
|
+
// `[key]` alone, for the same reason as getInitialValue above — otherwise an
|
|
650
|
+
// inline initial value churns this callback's identity on every render.
|
|
651
|
+
}, [key]);
|
|
622
652
|
return [storedValue, setValue, removeValue];
|
|
623
653
|
}
|
|
624
654
|
|
|
@@ -1070,79 +1100,148 @@ function useModelDownload() {
|
|
|
1070
1100
|
};
|
|
1071
1101
|
}
|
|
1072
1102
|
|
|
1073
|
-
const LOCAL_STORAGE_KEY = '
|
|
1103
|
+
const LOCAL_STORAGE_KEY = 'cua_driver_install_state';
|
|
1074
1104
|
/**
|
|
1075
|
-
* Manage installing
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1105
|
+
* Manage installing the Cua Driver (https://github.com/trycua/cua) via the Tauri
|
|
1106
|
+
* host. This is the self-contained backing hook for the Cowork card and
|
|
1107
|
+
* deliberately mirrors `useModelDownload`:
|
|
1078
1108
|
*
|
|
1079
|
-
* - checks
|
|
1080
|
-
* - installs
|
|
1109
|
+
* - checks driver install status AND whether this desktop session is supported
|
|
1110
|
+
* - downloads/installs the pinned driver with real-time progress + logs
|
|
1081
1111
|
* - persists state across app restarts
|
|
1082
1112
|
*
|
|
1083
1113
|
* Outside the desktop (Tauri) app every action is a no-op and `isAvailable` is
|
|
1084
|
-
* false, so the card hides itself.
|
|
1114
|
+
* false, so the card hides itself. Inside it, `isSupported` is what gates the
|
|
1115
|
+
* Cowork toggle — the driver has no proven behaviour on every Linux session.
|
|
1116
|
+
*
|
|
1117
|
+
* **Consent is the host application's job, not this hook's.** Enabling Cowork
|
|
1118
|
+
* hands an LLM synthetic input, screen capture, and — through the driver's
|
|
1119
|
+
* `--grant existing-profile` — the user's logged-in browser sessions. The driver
|
|
1120
|
+
* renders no consent UI of its own, and neither does the `InsideButtons` toggle
|
|
1121
|
+
* in this package. A host must explain that and get consent before calling
|
|
1122
|
+
* `requestDriverPermissions()`.
|
|
1085
1123
|
*/
|
|
1086
|
-
function
|
|
1124
|
+
function useCuaDriver() {
|
|
1087
1125
|
const { isAvailable, invoke, listen } = useTauri();
|
|
1088
|
-
const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY,
|
|
1126
|
+
const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY, initialCuaDriverInstallState);
|
|
1089
1127
|
const [status, setStatus] = useState(null);
|
|
1090
|
-
// macOS
|
|
1091
|
-
//
|
|
1128
|
+
// The two macOS grants the driver needs: Accessibility to post synthetic input,
|
|
1129
|
+
// Screen Recording because get_window_state always captures alongside the tree.
|
|
1130
|
+
//
|
|
1131
|
+
// `null` is a third state and a meaningful one: the commands exist only on
|
|
1132
|
+
// macOS, so elsewhere the invoke throws and the answer is "not applicable"
|
|
1133
|
+
// rather than "denied". Callers must branch on `=== false`, never on falsy —
|
|
1134
|
+
// treating null as denied would make Cowork unusable on Linux and Windows.
|
|
1092
1135
|
const [accessibilityPermission, setAccessibilityPermission] = useState(null);
|
|
1093
|
-
const
|
|
1136
|
+
const [screenRecordingPermission, setScreenRecordingPermission] = useState(null);
|
|
1094
1137
|
const hasCheckedStatus = useRef(false);
|
|
1095
1138
|
/**
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
* doesn't suppress the other. Stays null where unsupported.
|
|
1139
|
+
* Read one permission. Checking never prompts (`AXIsProcessTrusted` /
|
|
1140
|
+
* `CGPreflightScreenCaptureAccess`), which is what makes it safe to do on mount.
|
|
1099
1141
|
*/
|
|
1100
|
-
const
|
|
1101
|
-
if (!isAvailable)
|
|
1102
|
-
return;
|
|
1142
|
+
const checkPermission = useCallback(async (command) => {
|
|
1103
1143
|
try {
|
|
1104
|
-
|
|
1105
|
-
setAccessibilityPermission(granted);
|
|
1144
|
+
return await invoke(command);
|
|
1106
1145
|
}
|
|
1107
1146
|
catch (error) {
|
|
1108
|
-
// Plugin not registered or non-macOS host —
|
|
1109
|
-
console.warn(
|
|
1110
|
-
|
|
1147
|
+
// Plugin not registered or non-macOS host — not applicable, not denied.
|
|
1148
|
+
console.warn(`[useCuaDriver] ${command} unavailable:`, error);
|
|
1149
|
+
return null;
|
|
1111
1150
|
}
|
|
1112
|
-
}, [
|
|
1151
|
+
}, [invoke]);
|
|
1113
1152
|
/**
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1153
|
+
* Refresh both permissions. Independent of the driver status check so a missing
|
|
1154
|
+
* host command for one doesn't suppress the other.
|
|
1116
1155
|
*/
|
|
1117
|
-
const
|
|
1156
|
+
const refreshPermissions = useCallback(async () => {
|
|
1118
1157
|
if (!isAvailable)
|
|
1119
1158
|
return;
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1159
|
+
const [accessibility, screenRecording] = await Promise.all([
|
|
1160
|
+
checkPermission(MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY),
|
|
1161
|
+
checkPermission(MACOS_PERMISSIONS_COMMANDS.CHECK_SCREEN_RECORDING),
|
|
1162
|
+
]);
|
|
1163
|
+
setAccessibilityPermission(accessibility);
|
|
1164
|
+
setScreenRecordingPermission(screenRecording);
|
|
1165
|
+
}, [isAvailable, checkPermission]);
|
|
1166
|
+
/**
|
|
1167
|
+
* Ask for whichever grants are missing, then report what is actually held.
|
|
1168
|
+
*
|
|
1169
|
+
* **Prompts the user, so never call this from an effect** — it belongs to an
|
|
1170
|
+
* explicit action (switching Cowork on), not to mounting a component. The OS
|
|
1171
|
+
* prompt is not a consent step: it asks for a macOS grant, not for permission
|
|
1172
|
+
* to read the screen and act as the user. Explain that first (see the hook
|
|
1173
|
+
* docblock) — this package does not.
|
|
1174
|
+
*
|
|
1175
|
+
* Returns the post-request values rather than leaving the caller to read state:
|
|
1176
|
+
* a `setState` in this tick is not visible to the caller that awaited us, and
|
|
1177
|
+
* that caller has to decide right now whether to proceed.
|
|
1178
|
+
*
|
|
1179
|
+
* Note the values can still be `false` immediately after a successful grant —
|
|
1180
|
+
* `CGRequestScreenCaptureAccess` returns before the grant takes effect, which
|
|
1181
|
+
* on macOS usually needs an app restart. Copy shown to the user must say so.
|
|
1182
|
+
*/
|
|
1183
|
+
const requestDriverPermissions = useCallback(async () => {
|
|
1184
|
+
if (!isAvailable)
|
|
1185
|
+
return { accessibility: null, screenRecording: null };
|
|
1186
|
+
const pairs = [
|
|
1187
|
+
[
|
|
1188
|
+
MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY,
|
|
1189
|
+
MACOS_PERMISSIONS_COMMANDS.REQUEST_ACCESSIBILITY,
|
|
1190
|
+
],
|
|
1191
|
+
[
|
|
1192
|
+
MACOS_PERMISSIONS_COMMANDS.CHECK_SCREEN_RECORDING,
|
|
1193
|
+
MACOS_PERMISSIONS_COMMANDS.REQUEST_SCREEN_RECORDING,
|
|
1194
|
+
],
|
|
1195
|
+
];
|
|
1196
|
+
for (const [check, request] of pairs) {
|
|
1197
|
+
// Only prompt for what is actually missing — re-requesting a held grant
|
|
1198
|
+
// just reopens System Settings for no reason.
|
|
1199
|
+
if ((await checkPermission(check)) !== false)
|
|
1200
|
+
continue;
|
|
1201
|
+
try {
|
|
1202
|
+
await invoke(request);
|
|
1203
|
+
}
|
|
1204
|
+
catch (error) {
|
|
1205
|
+
console.error(`[useCuaDriver] ${request} failed:`, error);
|
|
1206
|
+
}
|
|
1125
1207
|
}
|
|
1126
|
-
await
|
|
1127
|
-
|
|
1128
|
-
|
|
1208
|
+
const [accessibility, screenRecording] = await Promise.all([
|
|
1209
|
+
checkPermission(MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY),
|
|
1210
|
+
checkPermission(MACOS_PERMISSIONS_COMMANDS.CHECK_SCREEN_RECORDING),
|
|
1211
|
+
]);
|
|
1212
|
+
setAccessibilityPermission(accessibility);
|
|
1213
|
+
setScreenRecordingPermission(screenRecording);
|
|
1214
|
+
return { accessibility, screenRecording };
|
|
1215
|
+
}, [isAvailable, invoke, checkPermission]);
|
|
1216
|
+
/**
|
|
1217
|
+
* Append a log line, optionally in the SAME update as other state changes.
|
|
1218
|
+
*
|
|
1219
|
+
* The `patch` argument is not a convenience: `setState` here is backed by
|
|
1220
|
+
* `useLocalStorage`, whose write + re-read means a log appended straight after
|
|
1221
|
+
* a status change can be computed from the pre-change snapshot and silently
|
|
1222
|
+
* revert it. That is how a failed install used to end up displayed as
|
|
1223
|
+
* "completed" with the error only visible down in the log pane. Anything that
|
|
1224
|
+
* sets status AND logs must do both in one update.
|
|
1225
|
+
*/
|
|
1226
|
+
const addLog = useCallback((log, patch) => {
|
|
1129
1227
|
setState((prev) => ({
|
|
1130
1228
|
...prev,
|
|
1229
|
+
...patch,
|
|
1131
1230
|
logs: [...prev.logs.slice(-99), log],
|
|
1132
1231
|
lastUpdated: new Date().toISOString(),
|
|
1133
1232
|
}));
|
|
1134
1233
|
}, [setState]);
|
|
1135
1234
|
/**
|
|
1136
|
-
* Query
|
|
1235
|
+
* Query driver install status + session support from the host.
|
|
1137
1236
|
*/
|
|
1138
1237
|
const checkStatus = useCallback(async () => {
|
|
1139
1238
|
if (!isAvailable)
|
|
1140
1239
|
return;
|
|
1141
|
-
// Refresh
|
|
1142
|
-
|
|
1240
|
+
// Refresh both permissions alongside install status (independent, no prompt).
|
|
1241
|
+
refreshPermissions();
|
|
1143
1242
|
try {
|
|
1144
1243
|
setState((prev) => ({ ...prev, status: 'checking' }));
|
|
1145
|
-
const result = await invoke(
|
|
1244
|
+
const result = await invoke(CUA_DRIVER_TAURI_COMMANDS.CHECK_STATUS);
|
|
1146
1245
|
setStatus(result);
|
|
1147
1246
|
setState((prev) => ({
|
|
1148
1247
|
...prev,
|
|
@@ -1152,13 +1251,13 @@ function useGhostOs() {
|
|
|
1152
1251
|
}));
|
|
1153
1252
|
}
|
|
1154
1253
|
catch (error) {
|
|
1155
|
-
console.error('[
|
|
1254
|
+
console.error('[useCuaDriver] Failed to check status:', error);
|
|
1156
1255
|
setState((prev) => ({ ...prev, status: 'idle' }));
|
|
1157
1256
|
}
|
|
1158
|
-
}, [isAvailable, invoke, setState,
|
|
1257
|
+
}, [isAvailable, invoke, setState, refreshPermissions]);
|
|
1159
1258
|
/**
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1259
|
+
* Download + install the pinned driver. The host emits progress/log events
|
|
1260
|
+
* along the way, and refuses up front on an unsupported desktop session.
|
|
1162
1261
|
*/
|
|
1163
1262
|
const install = useCallback(async () => {
|
|
1164
1263
|
if (!isAvailable)
|
|
@@ -1177,64 +1276,73 @@ function useGhostOs() {
|
|
|
1177
1276
|
level: 'info',
|
|
1178
1277
|
message: 'Starting setup…',
|
|
1179
1278
|
});
|
|
1180
|
-
const result = await invoke(
|
|
1279
|
+
const result = await invoke(CUA_DRIVER_TAURI_COMMANDS.INSTALL);
|
|
1181
1280
|
addLog({
|
|
1182
1281
|
timestamp: new Date().toISOString(),
|
|
1183
1282
|
level: 'info',
|
|
1184
1283
|
message: typeof result === 'string' && result ? result : 'Setup complete',
|
|
1185
|
-
}
|
|
1186
|
-
setState((prev) => ({
|
|
1187
|
-
...prev,
|
|
1284
|
+
}, {
|
|
1188
1285
|
status: 'completed',
|
|
1189
1286
|
progress: 100,
|
|
1190
1287
|
message: 'Your assistant is ready',
|
|
1191
|
-
|
|
1192
|
-
}));
|
|
1288
|
+
});
|
|
1193
1289
|
await checkStatus();
|
|
1194
1290
|
}
|
|
1195
1291
|
catch (error) {
|
|
1196
1292
|
const message = error instanceof Error ? error.message : String(error);
|
|
1197
|
-
setState((prev) => ({
|
|
1198
|
-
...prev,
|
|
1199
|
-
status: 'error',
|
|
1200
|
-
error: message,
|
|
1201
|
-
message: 'Setup didn’t finish',
|
|
1202
|
-
lastUpdated: new Date().toISOString(),
|
|
1203
|
-
}));
|
|
1204
1293
|
addLog({
|
|
1205
1294
|
timestamp: new Date().toISOString(),
|
|
1206
1295
|
level: 'error',
|
|
1207
1296
|
message: `Install failed: ${message}`,
|
|
1208
|
-
});
|
|
1297
|
+
}, { status: 'error', error: message, message: 'Setup didn’t finish' });
|
|
1209
1298
|
}
|
|
1210
1299
|
}, [isAvailable, invoke, addLog, checkStatus, setState]);
|
|
1211
1300
|
/**
|
|
1212
|
-
* Stop the
|
|
1301
|
+
* Stop the driver's MCP process. The host announces the exit, which makes the
|
|
1302
|
+
* chat layer drop its cached MCP session and reconnect on the next turn.
|
|
1213
1303
|
*/
|
|
1214
1304
|
const stop = useCallback(async () => {
|
|
1215
1305
|
if (!isAvailable)
|
|
1216
1306
|
return;
|
|
1217
1307
|
try {
|
|
1218
|
-
await invoke(
|
|
1308
|
+
await invoke(CUA_DRIVER_TAURI_COMMANDS.STOP);
|
|
1219
1309
|
await checkStatus();
|
|
1220
1310
|
}
|
|
1221
1311
|
catch (error) {
|
|
1222
|
-
console.error('[
|
|
1312
|
+
console.error('[useCuaDriver] Failed to stop the driver:', error);
|
|
1223
1313
|
}
|
|
1224
1314
|
}, [isAvailable, invoke, checkStatus]);
|
|
1225
1315
|
/**
|
|
1226
1316
|
* Reset the install state (for retrying after an error).
|
|
1227
1317
|
*/
|
|
1228
1318
|
const resetState = useCallback(() => {
|
|
1229
|
-
setState(
|
|
1319
|
+
setState(initialCuaDriverInstallState);
|
|
1230
1320
|
}, [setState]);
|
|
1321
|
+
// Latest handlers, read at event time. Keeping them OUT of the effect's
|
|
1322
|
+
// dependencies is the point: anything derived from `setState` can change
|
|
1323
|
+
// identity as state moves, and re-subscribing per state change is what turned
|
|
1324
|
+
// a handful of install events into a runaway cascade.
|
|
1325
|
+
const handlersRef = useRef({ addLog, setState });
|
|
1326
|
+
handlersRef.current = { addLog, setState };
|
|
1231
1327
|
// Wire up host event listeners (progress / logs / status).
|
|
1232
1328
|
useEffect(() => {
|
|
1233
1329
|
if (!isAvailable)
|
|
1234
1330
|
return;
|
|
1331
|
+
// `listen()` is async, so a teardown can land before the subscription
|
|
1332
|
+
// resolves. Registering the handle unconditionally would leave a live
|
|
1333
|
+
// listener behind with nothing holding its unsubscribe — the leak that made
|
|
1334
|
+
// one install log event fan out and freeze the webview for good.
|
|
1335
|
+
let cancelled = false;
|
|
1336
|
+
const handles = [];
|
|
1337
|
+
const track = (unlisten) => {
|
|
1338
|
+
if (cancelled)
|
|
1339
|
+
unlisten();
|
|
1340
|
+
else
|
|
1341
|
+
handles.push(unlisten);
|
|
1342
|
+
};
|
|
1235
1343
|
const setupListeners = async () => {
|
|
1236
1344
|
try {
|
|
1237
|
-
|
|
1345
|
+
track(await listen(CUA_DRIVER_TAURI_EVENTS.INSTALL_PROGRESS, (payload) => {
|
|
1238
1346
|
const nextStatus = payload.status === 'completed'
|
|
1239
1347
|
? 'completed'
|
|
1240
1348
|
: payload.status === 'cancelled'
|
|
@@ -1242,30 +1350,28 @@ function useGhostOs() {
|
|
|
1242
1350
|
: payload.status === 'error'
|
|
1243
1351
|
? 'error'
|
|
1244
1352
|
: 'installing';
|
|
1245
|
-
setState((prev) => ({
|
|
1353
|
+
handlersRef.current.setState((prev) => ({
|
|
1246
1354
|
...prev,
|
|
1247
1355
|
status: nextStatus,
|
|
1248
1356
|
progress: payload.percentage,
|
|
1249
1357
|
message: payload.message,
|
|
1250
1358
|
lastUpdated: new Date().toISOString(),
|
|
1251
1359
|
}));
|
|
1252
|
-
});
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
unlistenRefs.current.push(unlistenLogs);
|
|
1256
|
-
const unlistenStatus = await listen(GHOST_OS_TAURI_EVENTS.STATUS, setStatus);
|
|
1257
|
-
unlistenRefs.current.push(unlistenStatus);
|
|
1360
|
+
}));
|
|
1361
|
+
track(await listen(CUA_DRIVER_TAURI_EVENTS.INSTALLATION_LOG, (log) => handlersRef.current.addLog(log)));
|
|
1362
|
+
track(await listen(CUA_DRIVER_TAURI_EVENTS.STATUS, setStatus));
|
|
1258
1363
|
}
|
|
1259
1364
|
catch (error) {
|
|
1260
|
-
console.error('[
|
|
1365
|
+
console.error('[useCuaDriver] Failed to setup event listeners:', error);
|
|
1261
1366
|
}
|
|
1262
1367
|
};
|
|
1263
1368
|
setupListeners();
|
|
1264
1369
|
return () => {
|
|
1265
|
-
|
|
1266
|
-
|
|
1370
|
+
cancelled = true;
|
|
1371
|
+
handles.splice(0).forEach((unlisten) => unlisten());
|
|
1267
1372
|
};
|
|
1268
|
-
|
|
1373
|
+
// Only re-subscribe if the host itself changes — never on state.
|
|
1374
|
+
}, [isAvailable, listen]);
|
|
1269
1375
|
// Check status once on mount (in the desktop app).
|
|
1270
1376
|
useEffect(() => {
|
|
1271
1377
|
if (!isAvailable || hasCheckedStatus.current)
|
|
@@ -1275,16 +1381,28 @@ function useGhostOs() {
|
|
|
1275
1381
|
}, [isAvailable, checkStatus]);
|
|
1276
1382
|
// Only expose the feature as available inside the desktop (Tauri) app.
|
|
1277
1383
|
const finalIsAvailable = isAvailable && isTauriApp();
|
|
1384
|
+
// Deliberately `=== true`, not `?? true`: until the first status check lands
|
|
1385
|
+
// we do not know whether this desktop session is one the driver can drive, and
|
|
1386
|
+
// offering Cowork optimistically would show the toggle to (say) a KDE user for
|
|
1387
|
+
// a beat before snatching it away. One IPC round-trip on mount is cheaper than
|
|
1388
|
+
// that flicker, and errs towards not promising what we cannot deliver.
|
|
1389
|
+
const isSupported = (status === null || status === void 0 ? void 0 : status.supported) === true;
|
|
1390
|
+
const unsupportedReason = (status === null || status === void 0 ? void 0 : status.supported) === false ? status.reason : undefined;
|
|
1278
1391
|
return {
|
|
1279
1392
|
isAvailable: finalIsAvailable,
|
|
1393
|
+
/** Session support — gate the Cowork toggle on this, not on `isAvailable`. */
|
|
1394
|
+
isSupported,
|
|
1395
|
+
/** Machine-readable code the UI maps to a translated explanation. */
|
|
1396
|
+
unsupportedReason,
|
|
1280
1397
|
state,
|
|
1281
1398
|
status,
|
|
1282
1399
|
accessibilityPermission,
|
|
1400
|
+
screenRecordingPermission,
|
|
1283
1401
|
install,
|
|
1284
1402
|
stop,
|
|
1285
1403
|
checkStatus,
|
|
1286
1404
|
resetState,
|
|
1287
|
-
|
|
1405
|
+
requestDriverPermissions,
|
|
1288
1406
|
};
|
|
1289
1407
|
}
|
|
1290
1408
|
|
|
@@ -61217,7 +61335,11 @@ var chatInputFormInsideButtons$3 = {
|
|
|
61217
61335
|
cowork: "Cowork",
|
|
61218
61336
|
coworkNeedsBackend: "Enable a large local model or sign in to a workspace to use Cowork.",
|
|
61219
61337
|
coworkModelTooSmall: "Your local model is too small for Cowork. Pick a model of at least 14GB in Local Models.",
|
|
61220
|
-
moreOptions: "More options"
|
|
61338
|
+
moreOptions: "More options",
|
|
61339
|
+
coworkUnsupportedKde: "Cowork isn’t supported on KDE Plasma yet.",
|
|
61340
|
+
coworkUnsupportedGnomeHelper: "Cowork on GNOME needs the WinRects helper installed, then a session restart.",
|
|
61341
|
+
coworkUnsupportedOs: "Cowork isn’t supported on this operating system.",
|
|
61342
|
+
coworkUnsupportedSession: "Cowork isn’t supported on this desktop session."
|
|
61221
61343
|
};
|
|
61222
61344
|
var chatInputFormMemoryMenu$3 = {
|
|
61223
61345
|
title: "Your Memory",
|
|
@@ -67545,7 +67667,11 @@ var chatInputFormInsideButtons$2 = {
|
|
|
67545
67667
|
cowork: "Cowork",
|
|
67546
67668
|
coworkNeedsBackend: "Activez un grand modèle local ou connectez-vous à un espace de travail pour utiliser Cowork.",
|
|
67547
67669
|
coworkModelTooSmall: "Votre modèle local est trop petit pour Cowork. Choisissez un modèle d’au moins 14 Go dans Modèles Locaux.",
|
|
67548
|
-
moreOptions: "Plus d'options"
|
|
67670
|
+
moreOptions: "Plus d'options",
|
|
67671
|
+
coworkUnsupportedKde: "Cowork n’est pas encore pris en charge sur KDE Plasma.",
|
|
67672
|
+
coworkUnsupportedGnomeHelper: "Cowork sur GNOME nécessite l’extension WinRects, puis un redémarrage de la session.",
|
|
67673
|
+
coworkUnsupportedOs: "Cowork n’est pas pris en charge sur ce système d’exploitation.",
|
|
67674
|
+
coworkUnsupportedSession: "Cowork n’est pas pris en charge sur cette session de bureau."
|
|
67549
67675
|
};
|
|
67550
67676
|
var chatInputFormMemoryMenu$2 = {
|
|
67551
67677
|
title: "Votre mémoire",
|
|
@@ -73761,7 +73887,11 @@ var chatInputFormInsideButtons$1 = {
|
|
|
73761
73887
|
cowork: "Cowork",
|
|
73762
73888
|
coworkNeedsBackend: "Activa un modelo local grande o inicia sesión en un espacio de trabajo para usar Cowork.",
|
|
73763
73889
|
coworkModelTooSmall: "Tu modelo local es demasiado pequeño para Cowork. Elige un modelo de al menos 14 GB en Modelos Locales.",
|
|
73764
|
-
moreOptions: "Más opciones"
|
|
73890
|
+
moreOptions: "Más opciones",
|
|
73891
|
+
coworkUnsupportedKde: "Cowork aún no es compatible con KDE Plasma.",
|
|
73892
|
+
coworkUnsupportedGnomeHelper: "Cowork en GNOME necesita la extensión WinRects instalada y reiniciar la sesión.",
|
|
73893
|
+
coworkUnsupportedOs: "Cowork no es compatible con este sistema operativo.",
|
|
73894
|
+
coworkUnsupportedSession: "Cowork no es compatible con esta sesión de escritorio."
|
|
73765
73895
|
};
|
|
73766
73896
|
var chatInputFormMemoryMenu$1 = {
|
|
73767
73897
|
title: "Tu memoria",
|
|
@@ -79977,7 +80107,11 @@ var chatInputFormInsideButtons = {
|
|
|
79977
80107
|
cowork: "Cowork",
|
|
79978
80108
|
coworkNeedsBackend: "启用大型本地模型或登录工作区即可使用 Cowork。",
|
|
79979
80109
|
coworkModelTooSmall: "您的本地模型太小,无法用于 Cowork。请在“本地模型”中选择至少 14GB 的模型。",
|
|
79980
|
-
moreOptions: "更多选项"
|
|
80110
|
+
moreOptions: "更多选项",
|
|
80111
|
+
coworkUnsupportedKde: "Cowork 尚不支持 KDE Plasma。",
|
|
80112
|
+
coworkUnsupportedGnomeHelper: "在 GNOME 上使用 Cowork 需要安装 WinRects 扩展,然后重启会话。",
|
|
80113
|
+
coworkUnsupportedOs: "Cowork 不支持此操作系统。",
|
|
80114
|
+
coworkUnsupportedSession: "Cowork 不支持此桌面会话。"
|
|
79981
80115
|
};
|
|
79982
80116
|
var chatInputFormMemoryMenu = {
|
|
79983
80117
|
title: "您的记忆",
|
|
@@ -170006,7 +170140,7 @@ var joinTextblockForward = () => ({ state, dispatch }) => {
|
|
|
170006
170140
|
};
|
|
170007
170141
|
|
|
170008
170142
|
// src/utilities/isMacOS.ts
|
|
170009
|
-
function isMacOS
|
|
170143
|
+
function isMacOS() {
|
|
170010
170144
|
return typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
|
|
170011
170145
|
}
|
|
170012
170146
|
|
|
@@ -170032,7 +170166,7 @@ function normalizeKeyName(name) {
|
|
|
170032
170166
|
} else if (/^s(hift)?$/i.test(mod)) {
|
|
170033
170167
|
shift = true;
|
|
170034
170168
|
} else if (/^mod$/i.test(mod)) {
|
|
170035
|
-
if (isiOS() || isMacOS
|
|
170169
|
+
if (isiOS() || isMacOS()) {
|
|
170036
170170
|
meta = true;
|
|
170037
170171
|
} else {
|
|
170038
170172
|
ctrl = true;
|
|
@@ -173009,7 +173143,7 @@ var Keymap = Extension.create({
|
|
|
173009
173143
|
"Ctrl-a": () => this.editor.commands.selectTextblockStart(),
|
|
173010
173144
|
"Ctrl-e": () => this.editor.commands.selectTextblockEnd()
|
|
173011
173145
|
};
|
|
173012
|
-
if (isiOS() || isMacOS
|
|
173146
|
+
if (isiOS() || isMacOS()) {
|
|
173013
173147
|
return macKeymap;
|
|
173014
173148
|
}
|
|
173015
173149
|
return pcKeymap;
|
|
@@ -230077,27 +230211,40 @@ function MemoryButton({ mentorId, tenantKey, username }) {
|
|
|
230077
230211
|
} }))] }) }), jsx(PopoverContent, { className: "w-96 rounded-lg border border-gray-200 bg-white p-0 shadow-xl", children: jsx(MemoryMenu, { onClose: () => setIsOpen(false), mentorId: mentorId, tenantKey: tenantKey, username: username }) })] }));
|
|
230078
230212
|
}
|
|
230079
230213
|
|
|
230080
|
-
// Cowork is macOS-only in prod; the env flag bypasses the OS check so the
|
|
230081
|
-
// toggle can be exercised on Linux/Windows desktop builds during testing.
|
|
230082
|
-
const isMacOS = () => {
|
|
230083
|
-
if (typeof navigator === 'undefined')
|
|
230084
|
-
return false;
|
|
230085
|
-
return /mac/i.test(navigator.userAgent || '');
|
|
230086
|
-
};
|
|
230087
|
-
const allowNonMacOSCowork = () => process.env.NEXT_PUBLIC_ALLOW_NON_MACOS_COMPUTER_USE_TOGGLE === 'true';
|
|
230088
230214
|
// 14GB floor per spec; modelSupportsCowork gates size > gb, so a model of
|
|
230089
230215
|
// exactly 14GB is also off (no 14.0GB model in the catalog). Only the local-model
|
|
230090
230216
|
// backend needs this; the remote AI has no size requirement.
|
|
230091
230217
|
const COWORK_MIN_MODEL_GB = 14;
|
|
230092
230218
|
const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode, artifactsEnabled, containerWidth, disabled = false, onOpenPromptGallery, embedMode = false, promptsIsEnabled = false, memoryEnabled = false, mentorId, tenantKey, username, }) => {
|
|
230093
230219
|
const t = useT();
|
|
230094
|
-
// Cowork = the Tauri
|
|
230220
|
+
// Cowork = the Tauri cua-driver assistant. The driver runs the tools on-device;
|
|
230095
230221
|
// the "brain" is EITHER a large local model (Local Models / Ollama) OR the
|
|
230096
|
-
// remote AI (DM OpenAI-compatible endpoint), whichever is set up.
|
|
230222
|
+
// remote AI (DM OpenAI-compatible endpoint), whichever is set up. useCuaDriver
|
|
230097
230223
|
// handles install/stop; the localStorage pref (isCoworkEnabled) is read
|
|
230098
230224
|
// by the chat hook to route the turn. Reads the pref on mount.
|
|
230099
230225
|
// Local state is `coworkOn` so it doesn't shadow the imported setCoworkEnabled.
|
|
230100
|
-
|
|
230226
|
+
//
|
|
230227
|
+
// NOTE — this toggle is deliberately UNGATED: it explains nothing and requests
|
|
230228
|
+
// no OS permission. Enabling Cowork gives an LLM synthetic input, screen
|
|
230229
|
+
// capture, and the user's logged-in browser sessions (the driver is spawned
|
|
230230
|
+
// with `--grant existing-profile`). A host that wants a consent step and the
|
|
230231
|
+
// macOS grants must build them and call `useCuaDriver().requestDriverPermissions()`
|
|
230232
|
+
// itself — the OS app's fork of this component does exactly that. See the
|
|
230233
|
+
// useCuaDriver docblock.
|
|
230234
|
+
// The host reports a machine-readable code; never render it raw.
|
|
230235
|
+
const unsupportedCoworkReason = (reason) => {
|
|
230236
|
+
switch (reason) {
|
|
230237
|
+
case 'kde_unproven':
|
|
230238
|
+
return t('chatInputFormInsideButtons.coworkUnsupportedKde');
|
|
230239
|
+
case 'gnome_helper_missing':
|
|
230240
|
+
return t('chatInputFormInsideButtons.coworkUnsupportedGnomeHelper');
|
|
230241
|
+
case 'unsupported_os':
|
|
230242
|
+
return t('chatInputFormInsideButtons.coworkUnsupportedOs');
|
|
230243
|
+
default:
|
|
230244
|
+
return t('chatInputFormInsideButtons.coworkUnsupportedSession');
|
|
230245
|
+
}
|
|
230246
|
+
};
|
|
230247
|
+
const ghostOs = useCuaDriver();
|
|
230101
230248
|
const [coworkOn, setCoworkOn] = useState(isCoworkEnabled);
|
|
230102
230249
|
const toggleCowork = () => {
|
|
230103
230250
|
const next = !coworkOn;
|
|
@@ -230108,8 +230255,7 @@ const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode,
|
|
|
230108
230255
|
// `disabled` when the user lacks the mentor `#chat` permission — see
|
|
230109
230256
|
// chat-input-form's isChatDisabledByRbac.)
|
|
230110
230257
|
if (next) {
|
|
230111
|
-
const localReady = isLocalLLMEnabled() &&
|
|
230112
|
-
modelSupportsCowork(getLocalLLMModel(), COWORK_MIN_MODEL_GB);
|
|
230258
|
+
const localReady = isLocalLLMEnabled() && modelSupportsCowork(getLocalLLMModel(), COWORK_MIN_MODEL_GB);
|
|
230113
230259
|
if (!localReady && !hasRemoteAiConfig()) {
|
|
230114
230260
|
toast.warning(isLocalLLMEnabled()
|
|
230115
230261
|
? t('chatInputFormInsideButtons.coworkModelTooSmall')
|
|
@@ -230173,7 +230319,15 @@ const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode,
|
|
|
230173
230319
|
icon: jsx(Monitor, { className: "h-4 w-4" }),
|
|
230174
230320
|
isActive: coworkOn,
|
|
230175
230321
|
action: toggleCowork,
|
|
230176
|
-
|
|
230322
|
+
// Cowork used to be macOS-only because GhostOS was. The Cua Driver runs
|
|
230323
|
+
// on Windows, macOS and Linux — but not on every Linux session, so an
|
|
230324
|
+
// unsupported one renders the pill DISABLED with the reason rather than
|
|
230325
|
+
// hiding it. The chatbox is Cowork's only surface: hide it and a KDE user
|
|
230326
|
+
// is left with no way to find out why the feature is missing.
|
|
230327
|
+
isEnabled: ghostOs.isAvailable,
|
|
230328
|
+
disabledReason: ghostOs.isSupported
|
|
230329
|
+
? undefined
|
|
230330
|
+
: unsupportedCoworkReason(ghostOs.unsupportedReason),
|
|
230177
230331
|
},
|
|
230178
230332
|
].filter((item) => item.isEnabled);
|
|
230179
230333
|
// Get visible inside buttons based on screen size
|
|
@@ -230211,7 +230365,7 @@ const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode,
|
|
|
230211
230365
|
if (button.name === 'Memory') {
|
|
230212
230366
|
return (jsx(MemoryButton, { mentorId: mentorId, tenantKey: tenantKey, username: username }, button.name));
|
|
230213
230367
|
}
|
|
230214
|
-
return (jsx("div", { className: "relative", children: jsxs(Button$1, { variant: "ghost", size: "sm", type: "button", disabled: disabled, className: `flex h-8 items-center gap-1.5 rounded-lg px-2 text-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50 ${button.isActive
|
|
230368
|
+
return (jsx("div", { className: "relative", children: jsxs(Button$1, { variant: "ghost", size: "sm", type: "button", disabled: disabled || !!button.disabledReason, title: button.disabledReason, className: `flex h-8 items-center gap-1.5 rounded-lg px-2 text-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50 ${button.isActive
|
|
230215
230369
|
? 'border border-[#D0E0FF] bg-[#F5F8FF] text-[#38A1E5]'
|
|
230216
230370
|
: 'text-gray-600 hover:border hover:border-[#D0E0FF] hover:bg-[#F5F8FF]'}`, onClick: (e) => {
|
|
230217
230371
|
e.preventDefault();
|
|
@@ -234967,5 +235121,5 @@ var trainOrDeleteModal = /*#__PURE__*/Object.freeze({
|
|
|
234967
235121
|
TrainOrDeleteModal: TrainOrDeleteModal
|
|
234968
235122
|
});
|
|
234969
235123
|
|
|
234970
|
-
export { ACCESS_COURSE_LABEL, AccessTimeHeatmap, AccessiblePaginate, AddMentorToProjectModal, AddSkillDialog, Admin, AdvancedTab, AgentConfigPrompts, AgentSkills, AlertsTab, AnalyticsAuditLogStats, AnalyticsCourseDetail, AnalyticsCourses, AnalyticsFinancialStats, AnalyticsLayout, AnalyticsMonetizationStats, AnalyticsOverview, AnalyticsProgramDetail, AnalyticsPrograms, AnalyticsReportDownload, AnalyticsReports, AnalyticsSettingsProvider, AnalyticsTopicsStats, AnalyticsTranscriptsStats, AnalyticsUsersStats, AppleRestrictionModal, ApplyWizard, BUY_NOW_LABEL, BillingTab, CATALOGS, CREATE_AGENT_LABELS, CategorizedDropdownMenu, ChartCardWrapper, ChartFiltersProvider, ChartLoading, ChatContext, ChatPrivacyToggle, ChatProvider, CompanyDialog, ConnectorManagementDialog, CopyButtonIcon, CourseAccessGuard, CourseCardSkeleton, CourseContentLoading, CourseOutline, CourseOutlineContext, CourseOutlineDrawer, CreateAgentForm, CreateAgentModal, CreateProjectModal, CreateWorkflowModal, CredentialBox, CredentialMiniBoxSkeleton, CredentialsList, CreditBalance, CustomDateRangePicker, CustomTooltip, DEFAULT_COWORK_REQUIRED_SIZE_GB, DEFAULT_LOCALE, DefaultEmptyBox, DeleteProjectModal, DeleteWorkflowModal, DiscoverContentCard, DiscoverFacetsFilter, DiscoverFilterDrawer, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, ENROLL_NOW_COURSE_STARTING_SOON_LABEL, ENROLL_NOW_LABEL, EditAlertDialog, EducationBox, EducationDialog, EducationTab, EdxIframeContext, EmptyStats, ExperienceBox, ExperienceDialog, ExperienceTab, FacetFilterContext, Footer,
|
|
235124
|
+
export { ACCESS_COURSE_LABEL, AccessTimeHeatmap, AccessiblePaginate, AddMentorToProjectModal, AddSkillDialog, Admin, AdvancedTab, AgentConfigPrompts, AgentSkills, AlertsTab, AnalyticsAuditLogStats, AnalyticsCourseDetail, AnalyticsCourses, AnalyticsFinancialStats, AnalyticsLayout, AnalyticsMonetizationStats, AnalyticsOverview, AnalyticsProgramDetail, AnalyticsPrograms, AnalyticsReportDownload, AnalyticsReports, AnalyticsSettingsProvider, AnalyticsTopicsStats, AnalyticsTranscriptsStats, AnalyticsUsersStats, AppleRestrictionModal, ApplyWizard, BUY_NOW_LABEL, BillingTab, CATALOGS, CREATE_AGENT_LABELS, CUA_DRIVER_REPO_URL, CUA_DRIVER_TAURI_COMMANDS, CUA_DRIVER_TAURI_EVENTS, CategorizedDropdownMenu, ChartCardWrapper, ChartFiltersProvider, ChartLoading, ChatContext, ChatPrivacyToggle, ChatProvider, CompanyDialog, ConnectorManagementDialog, CopyButtonIcon, CourseAccessGuard, CourseCardSkeleton, CourseContentLoading, CourseOutline, CourseOutlineContext, CourseOutlineDrawer, CreateAgentForm, CreateAgentModal, CreateProjectModal, CreateWorkflowModal, CredentialBox, CredentialMiniBoxSkeleton, CredentialsList, CreditBalance, CustomDateRangePicker, CustomTooltip, DEFAULT_COWORK_REQUIRED_SIZE_GB, DEFAULT_LOCALE, DefaultEmptyBox, DeleteProjectModal, DeleteWorkflowModal, DiscoverContentCard, DiscoverFacetsFilter, DiscoverFilterDrawer, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, ENROLL_NOW_COURSE_STARTING_SOON_LABEL, ENROLL_NOW_LABEL, EditAlertDialog, EducationBox, EducationDialog, EducationTab, EdxIframeContext, EmptyStats, ExperienceBox, ExperienceDialog, ExperienceTab, FacetFilterContext, Footer, GradebookTab, GroupsFilterDropdown, INVITATION_ONLY_LABEL, InstitutionDialog, IntegrationsTab, InviteUserContent, InviteUserDialog, InvitedUsersDialog, LOCAL_MODELS, Loader, LocalLLMTab, LocalModelsContent, LoginButton, MACOS_PERMISSIONS_COMMANDS, Markdown, MinimumMentorAlert, MonetizationTab, NotificationDisplay, NotificationDropdown, ONBOARDING_SECTORS, OnboardingShell, OnboardingWizard, PaywallModal, PlacementExam, PlacementExamsDropdown, PlatformNavbar, PlatformNavbarSearch, Profile, ProfileTimeChart, ProgressDots, ProjectActionButtons, ProjectFilesModal, ProjectInstructionsModal, ProjectLandingPage, ProjectMentorsList, ProjectsPage, REQUEST_ACCESS_COURSE_STARTING_SOON_LABEL, REQUEST_ACCESS_LABEL, RenameProjectModal, ResumeBox, ResumeTab, RichTextEditor, SUPPORTED_LOCALES, SandboxConfig, SearchableMultiSelect, SendNotificationDialog, SignupButton, SkeletonActivityStatBox, SkeletonAddSkillsLoading, SkeletonCreatePathwaySearchList, SkeletonDiscoverFilterBox, SkeletonEducationBox, SkeletonMultiplier, SkeletonPathwayBox, SkeletonProfileInfoCard, SkeletonSkillBox, SkillBox, SkillDetailModal, SkillLeaderboardChart, SkillsBox, SkillsList, SlashSkillPicker, Spinner, StatCard, StepHeader, TAURI_COMMANDS, TAURI_EVENTS, TenantSwitcher, TimeFilter, TimeTrackingProvider, TimedExam, ToolDialogs, TopBanner, UpgradePackageModal, UserAvatar, Version, WebContainersI18nProvider, WorkflowSidebar, addBookmarksTab, availablePlacementExams, findLastResumeBlock, findSequentialParent, flattenVerticalBlocks, getCatalog, getFirstAvailableUnit, getLocalLLMModel, getLocalLLMToolSupport, getNextUnitIframe, getOrg, getParentBlockById, getParentsInfosFromSublessonId, getPreviousUnitIframe, getRandomCourseImage, getSectorById, getTenant, getUnitToIframe, getUserId, getUserName, inBrowserPrint, inIframe, initialCuaDriverInstallState, initialModelDownloadState, isCoworkEnabled, isLocalLLMEnabled, isPlatformNavbarCreditBalanceVisible, isSlashCommandToken, isTauriApp, isValidLocale, components as markdownComponents, modelSupportsCowork, onboardingPrimaryButtonClass, onboardingSecondaryButtonClass, resolveCreateAgentLabels, sanitizeCss, setCoworkEnabled, setLocalLLMEnabled, setLocalLLMModel, setLocalLLMToolSupport, smallestCoworkModel, useAnalyticsSettings, useAuditLog, useCatalogSearch, useChartFilters, useChatPrivacy, useChatState, useCourseDetail, useCourseMetadata, useCourses, useCuaDriver, useDiscover, useEdxIframe, useFinancial, useIframeMessageHandler, useLocalStorage, useModelDownload, useMonetization, useOverview, useProfileActivityStats, useProfileCredentials, useProfilePathways, useProfilePrograms, useProfileSkills, useProfileTimeSpent, usePrograms, useReports, useSlashSkillPicker, useT, useTauri, useTimeTracking, useTopics, useTranscripts, useUserCourses, useUserMetadata, useUsers, useWebContainersI18n, useWebContainersLocale };
|
|
234971
235125
|
//# sourceMappingURL=index.esm.js.map
|