@ottocode/web-sdk 0.1.289 → 0.1.290
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/dist/assets/provider-logos.d.ts +1 -1
- package/dist/assets/provider-logos.d.ts.map +1 -1
- package/dist/components/chat/ChatInput.d.ts.map +1 -1
- package/dist/components/chat/ChatInputKeyHandler.d.ts +6 -0
- package/dist/components/chat/ChatInputKeyHandler.d.ts.map +1 -1
- package/dist/components/chat/ConfigModal.d.ts.map +1 -1
- package/dist/components/chat/DictationInstallPrompt.d.ts +8 -0
- package/dist/components/chat/DictationInstallPrompt.d.ts.map +1 -0
- package/dist/components/chat/LiveWaveform.d.ts +32 -0
- package/dist/components/chat/LiveWaveform.d.ts.map +1 -0
- package/dist/components/chat/ReasoningTabs.d.ts +13 -0
- package/dist/components/chat/ReasoningTabs.d.ts.map +1 -0
- package/dist/components/chat/ShortcutsModal.d.ts.map +1 -1
- package/dist/components/chat/SkillMentionPopup.d.ts +12 -0
- package/dist/components/chat/SkillMentionPopup.d.ts.map +1 -0
- package/dist/components/git/GitFileTree.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +9419 -7263
- package/dist/components/index.js.map +51 -41
- package/dist/components/messages/UserMessageGroup.d.ts.map +1 -1
- package/dist/components/onboarding/OnboardingModal.d.ts.map +1 -1
- package/dist/components/onboarding/steps/ProviderSetupStep.d.ts +10 -0
- package/dist/components/onboarding/steps/ProviderSetupStep.d.ts.map +1 -1
- package/dist/components/settings/DictationSettings.d.ts +6 -0
- package/dist/components/settings/DictationSettings.d.ts.map +1 -0
- package/dist/components/settings/SettingsSidebar.d.ts.map +1 -1
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +3 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +713 -210
- package/dist/hooks/index.js.map +20 -17
- package/dist/hooks/useAuthStatus.d.ts +5 -0
- package/dist/hooks/useAuthStatus.d.ts.map +1 -1
- package/dist/hooks/useConfig.d.ts +24 -0
- package/dist/hooks/useConfig.d.ts.map +1 -1
- package/dist/hooks/useDictationModels.d.ts +23 -0
- package/dist/hooks/useDictationModels.d.ts.map +1 -0
- package/dist/hooks/useOttoRouterPayments.d.ts +2 -0
- package/dist/hooks/useOttoRouterPayments.d.ts.map +1 -0
- package/dist/hooks/usePreferences.d.ts +2 -3
- package/dist/hooks/usePreferences.d.ts.map +1 -1
- package/dist/hooks/useSkillMention.d.ts +16 -0
- package/dist/hooks/useSkillMention.d.ts.map +1 -0
- package/dist/hooks/useTheme.d.ts +1 -1
- package/dist/hooks/useTheme.d.ts.map +1 -1
- package/dist/hooks/useVoiceInput.d.ts +24 -0
- package/dist/hooks/useVoiceInput.d.ts.map +1 -0
- package/dist/index.js +9440 -7297
- package/dist/index.js.map +53 -43
- package/dist/lib/api-client/auth.d.ts +10 -0
- package/dist/lib/api-client/auth.d.ts.map +1 -1
- package/dist/lib/api-client/config.d.ts +18 -0
- package/dist/lib/api-client/config.d.ts.map +1 -1
- package/dist/lib/api-client/dictation.d.ts +74 -0
- package/dist/lib/api-client/dictation.d.ts.map +1 -0
- package/dist/lib/api-client/index.d.ts +35 -0
- package/dist/lib/api-client/index.d.ts.map +1 -1
- package/dist/lib/api-client/utils.d.ts +1 -0
- package/dist/lib/api-client/utils.d.ts.map +1 -1
- package/dist/lib/config.d.ts +6 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +186 -29
- package/dist/lib/index.js.map +10 -9
- package/dist/lib/skillMentions.d.ts +8 -0
- package/dist/lib/skillMentions.d.ts.map +1 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.d.ts.map +1 -1
- package/dist/stores/index.js +42 -2
- package/dist/stores/index.js.map +6 -5
- package/dist/stores/skillsStore.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/hooks/useSetuPayments.d.ts +0 -2
- package/dist/hooks/useSetuPayments.d.ts.map +0 -1
package/dist/hooks/index.js
CHANGED
|
@@ -21,20 +21,83 @@ import {
|
|
|
21
21
|
import { client } from "@ottocode/api";
|
|
22
22
|
|
|
23
23
|
// src/lib/config.ts
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
var RUNTIME_API_BASE_URL_STORAGE_KEY = "otto-api-base-url";
|
|
25
|
+
function normalizeApiBaseUrl(value) {
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
if (!trimmed) {
|
|
28
|
+
throw new Error("Enter an otto tunnel URL.");
|
|
29
|
+
}
|
|
30
|
+
let url;
|
|
31
|
+
try {
|
|
32
|
+
url = new URL(trimmed);
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error("Enter a valid URL, including http:// or https://.");
|
|
35
|
+
}
|
|
36
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
37
|
+
throw new Error("Tunnel URL must start with http:// or https://.");
|
|
38
|
+
}
|
|
39
|
+
url.hash = "";
|
|
40
|
+
url.pathname = url.pathname.replace(/\/+$/, "") || "";
|
|
41
|
+
return url.toString().replace(/\/+$/, "");
|
|
42
|
+
}
|
|
43
|
+
function getStoredApiBaseUrl() {
|
|
44
|
+
if (typeof window === "undefined")
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
return window.localStorage.getItem(RUNTIME_API_BASE_URL_STORAGE_KEY) ?? undefined;
|
|
48
|
+
} catch {
|
|
49
|
+
return;
|
|
28
50
|
}
|
|
51
|
+
}
|
|
52
|
+
function getConfiguredRuntimeApiBaseUrl() {
|
|
53
|
+
if (typeof window === "undefined")
|
|
54
|
+
return;
|
|
55
|
+
const params = new URLSearchParams(window.location.search);
|
|
56
|
+
const urlParam = params.get("url");
|
|
57
|
+
if (urlParam)
|
|
58
|
+
return normalizeApiBaseUrl(urlParam);
|
|
59
|
+
const win = window;
|
|
60
|
+
if (win.OTTO_SERVER_URL)
|
|
61
|
+
return normalizeApiBaseUrl(win.OTTO_SERVER_URL);
|
|
62
|
+
if (win.__OTTO_API_URL__)
|
|
63
|
+
return normalizeApiBaseUrl(win.__OTTO_API_URL__);
|
|
64
|
+
const storedUrl = getStoredApiBaseUrl();
|
|
65
|
+
if (storedUrl)
|
|
66
|
+
return normalizeApiBaseUrl(storedUrl);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
function hasConfiguredRuntimeApiBaseUrl() {
|
|
70
|
+
return Boolean(getConfiguredRuntimeApiBaseUrl());
|
|
71
|
+
}
|
|
72
|
+
function setRuntimeApiBaseUrl(value) {
|
|
73
|
+
const baseUrl = normalizeApiBaseUrl(value);
|
|
29
74
|
if (typeof window !== "undefined") {
|
|
30
75
|
const win = window;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return win.__OTTO_API_URL__;
|
|
36
|
-
}
|
|
76
|
+
win.OTTO_SERVER_URL = baseUrl;
|
|
77
|
+
try {
|
|
78
|
+
window.localStorage.setItem(RUNTIME_API_BASE_URL_STORAGE_KEY, baseUrl);
|
|
79
|
+
} catch {}
|
|
37
80
|
}
|
|
81
|
+
return baseUrl;
|
|
82
|
+
}
|
|
83
|
+
function clearRuntimeApiBaseUrl() {
|
|
84
|
+
if (typeof window === "undefined")
|
|
85
|
+
return;
|
|
86
|
+
const win = window;
|
|
87
|
+
delete win.OTTO_SERVER_URL;
|
|
88
|
+
delete win.__OTTO_API_URL__;
|
|
89
|
+
try {
|
|
90
|
+
window.localStorage.removeItem(RUNTIME_API_BASE_URL_STORAGE_KEY);
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
function computeApiBaseUrl() {
|
|
94
|
+
const runtimeUrl = getConfiguredRuntimeApiBaseUrl();
|
|
95
|
+
if (runtimeUrl) {
|
|
96
|
+
return runtimeUrl;
|
|
97
|
+
}
|
|
98
|
+
const envUrl = import.meta.env?.VITE_API_BASE_URL;
|
|
99
|
+
if (envUrl)
|
|
100
|
+
return normalizeApiBaseUrl(envUrl);
|
|
38
101
|
return "http://localhost:9100";
|
|
39
102
|
}
|
|
40
103
|
function getRuntimeApiBaseUrl() {
|
|
@@ -77,8 +140,7 @@ function extractErrorMessage(error) {
|
|
|
77
140
|
return "Unknown error";
|
|
78
141
|
}
|
|
79
142
|
function configureApiClient() {
|
|
80
|
-
const
|
|
81
|
-
const baseURL = win.OTTO_SERVER_URL || API_BASE_URL;
|
|
143
|
+
const baseURL = getRuntimeApiBaseUrl();
|
|
82
144
|
client.setConfig({
|
|
83
145
|
baseURL,
|
|
84
146
|
adapter: getClientAdapter()
|
|
@@ -86,10 +148,7 @@ function configureApiClient() {
|
|
|
86
148
|
}
|
|
87
149
|
configureApiClient();
|
|
88
150
|
function getBaseUrl() {
|
|
89
|
-
|
|
90
|
-
if (win.OTTO_SERVER_URL)
|
|
91
|
-
return win.OTTO_SERVER_URL;
|
|
92
|
-
return API_BASE_URL;
|
|
151
|
+
return getRuntimeApiBaseUrl();
|
|
93
152
|
}
|
|
94
153
|
function convertSession(apiSession) {
|
|
95
154
|
return {
|
|
@@ -611,9 +670,9 @@ var approvalMixin = {
|
|
|
611
670
|
|
|
612
671
|
// src/lib/api-client/ottorouter.ts
|
|
613
672
|
import {
|
|
614
|
-
getOttoRouterBalance as
|
|
615
|
-
getOttoRouterWallet as
|
|
616
|
-
getOttoRouterUsdcBalance as
|
|
673
|
+
getOttoRouterBalance as apiGetOttoRouterBalance,
|
|
674
|
+
getOttoRouterWallet as apiGetOttoRouterWallet,
|
|
675
|
+
getOttoRouterUsdcBalance as apiGetOttoRouterUsdcBalance,
|
|
617
676
|
createPolarCheckout as apiCreatePolarCheckout,
|
|
618
677
|
getPolarTopupEstimate as apiGetPolarTopupEstimate,
|
|
619
678
|
getPolarTopupStatus as apiGetPolarTopupStatus,
|
|
@@ -627,7 +686,7 @@ import {
|
|
|
627
686
|
var ottorouterMixin = {
|
|
628
687
|
async getOttoRouterBalance() {
|
|
629
688
|
try {
|
|
630
|
-
const response = await
|
|
689
|
+
const response = await apiGetOttoRouterBalance();
|
|
631
690
|
if (response.error)
|
|
632
691
|
return null;
|
|
633
692
|
return response.data;
|
|
@@ -637,7 +696,7 @@ var ottorouterMixin = {
|
|
|
637
696
|
},
|
|
638
697
|
async getOttoRouterWallet() {
|
|
639
698
|
try {
|
|
640
|
-
const response = await
|
|
699
|
+
const response = await apiGetOttoRouterWallet();
|
|
641
700
|
if (response.error)
|
|
642
701
|
return { configured: false };
|
|
643
702
|
return response.data;
|
|
@@ -647,7 +706,7 @@ var ottorouterMixin = {
|
|
|
647
706
|
},
|
|
648
707
|
async getOttoRouterUsdcBalance(network = "mainnet") {
|
|
649
708
|
try {
|
|
650
|
-
const response = await
|
|
709
|
+
const response = await apiGetOttoRouterUsdcBalance({
|
|
651
710
|
query: { network }
|
|
652
711
|
});
|
|
653
712
|
if (response.error)
|
|
@@ -750,14 +809,16 @@ var ottorouterMixin = {
|
|
|
750
809
|
// src/lib/api-client/auth.ts
|
|
751
810
|
import {
|
|
752
811
|
getAuthStatus as apiGetAuthStatus,
|
|
753
|
-
setupOttoRouterWallet as
|
|
754
|
-
importOttoRouterWallet as
|
|
755
|
-
exportOttoRouterWallet as
|
|
812
|
+
setupOttoRouterWallet as apiSetupOttoRouterWallet,
|
|
813
|
+
importOttoRouterWallet as apiImportOttoRouterWallet,
|
|
814
|
+
exportOttoRouterWallet as apiExportOttoRouterWallet,
|
|
756
815
|
addProviderApiKey as apiAddProviderApiKey,
|
|
757
816
|
removeProvider as apiRemoveProvider,
|
|
758
817
|
completeOnboarding as apiCompleteOnboarding,
|
|
759
818
|
getOAuthUrl as apiGetOAuthUrl,
|
|
760
819
|
exchangeOAuthCode as apiExchangeOAuthCode,
|
|
820
|
+
startOpenAiDeviceFlow as apiStartOpenAiDeviceFlow,
|
|
821
|
+
pollOpenAiDeviceFlow as apiPollOpenAiDeviceFlow,
|
|
761
822
|
startCopilotDeviceFlow as apiStartCopilotDeviceFlow,
|
|
762
823
|
pollCopilotDeviceFlow as apiPollCopilotDeviceFlow,
|
|
763
824
|
getCopilotAuthMethods as apiGetCopilotAuthMethods,
|
|
@@ -774,13 +835,13 @@ var authMixin = {
|
|
|
774
835
|
return response.data;
|
|
775
836
|
},
|
|
776
837
|
async setupOttoRouterWallet() {
|
|
777
|
-
const response = await
|
|
838
|
+
const response = await apiSetupOttoRouterWallet();
|
|
778
839
|
if (response.error)
|
|
779
840
|
throw new Error(extractErrorMessage(response.error));
|
|
780
841
|
return response.data;
|
|
781
842
|
},
|
|
782
843
|
async importOttoRouterWallet(privateKey) {
|
|
783
|
-
const response = await
|
|
844
|
+
const response = await apiImportOttoRouterWallet({
|
|
784
845
|
body: { privateKey }
|
|
785
846
|
});
|
|
786
847
|
if (response.error)
|
|
@@ -788,7 +849,7 @@ var authMixin = {
|
|
|
788
849
|
return response.data;
|
|
789
850
|
},
|
|
790
851
|
async exportOttoRouterWallet() {
|
|
791
|
-
const response = await
|
|
852
|
+
const response = await apiExportOttoRouterWallet();
|
|
792
853
|
if (response.error)
|
|
793
854
|
throw new Error(extractErrorMessage(response.error));
|
|
794
855
|
return response.data;
|
|
@@ -838,6 +899,20 @@ var authMixin = {
|
|
|
838
899
|
throw new Error(extractErrorMessage(response.error));
|
|
839
900
|
return response.data;
|
|
840
901
|
},
|
|
902
|
+
async startOpenAIDeviceFlow() {
|
|
903
|
+
const response = await apiStartOpenAiDeviceFlow();
|
|
904
|
+
if (response.error)
|
|
905
|
+
throw new Error(extractErrorMessage(response.error));
|
|
906
|
+
return response.data;
|
|
907
|
+
},
|
|
908
|
+
async pollOpenAIDeviceFlow(sessionId) {
|
|
909
|
+
const response = await apiPollOpenAiDeviceFlow({
|
|
910
|
+
body: { sessionId }
|
|
911
|
+
});
|
|
912
|
+
if (response.error)
|
|
913
|
+
throw new Error(extractErrorMessage(response.error));
|
|
914
|
+
return response.data;
|
|
915
|
+
},
|
|
841
916
|
async startCopilotDeviceFlow() {
|
|
842
917
|
const response = await apiStartCopilotDeviceFlow();
|
|
843
918
|
if (response.error)
|
|
@@ -960,6 +1035,74 @@ var usageMixin = {
|
|
|
960
1035
|
}
|
|
961
1036
|
};
|
|
962
1037
|
|
|
1038
|
+
// src/lib/api-client/dictation.ts
|
|
1039
|
+
import {
|
|
1040
|
+
getDictationStatus as apiGetDictationStatus,
|
|
1041
|
+
installDictationModel as apiInstallDictationModel,
|
|
1042
|
+
listDictationModels as apiListDictationModels,
|
|
1043
|
+
removeDictationModel as apiRemoveDictationModel,
|
|
1044
|
+
createDictationSession as apiCreateDictationSession
|
|
1045
|
+
} from "@ottocode/api";
|
|
1046
|
+
function coerceModelState(value) {
|
|
1047
|
+
return value;
|
|
1048
|
+
}
|
|
1049
|
+
function coerceModels(value) {
|
|
1050
|
+
return Array.isArray(value) ? value.map(coerceModelState) : [];
|
|
1051
|
+
}
|
|
1052
|
+
function buildInstallEventsUrl(model) {
|
|
1053
|
+
const baseUrl = getBaseUrl().replace(/\/+$/, "");
|
|
1054
|
+
return `${baseUrl}/v1/dictation/models/${encodeURIComponent(model)}/install/events`;
|
|
1055
|
+
}
|
|
1056
|
+
var dictationMixin = {
|
|
1057
|
+
async getDictationStatus() {
|
|
1058
|
+
const response = await apiGetDictationStatus();
|
|
1059
|
+
if (response.error)
|
|
1060
|
+
throw new Error(extractErrorMessage(response.error));
|
|
1061
|
+
const data = response.data;
|
|
1062
|
+
return {
|
|
1063
|
+
...data,
|
|
1064
|
+
models: coerceModels(data.models)
|
|
1065
|
+
};
|
|
1066
|
+
},
|
|
1067
|
+
async listDictationModels() {
|
|
1068
|
+
const response = await apiListDictationModels();
|
|
1069
|
+
if (response.error)
|
|
1070
|
+
throw new Error(extractErrorMessage(response.error));
|
|
1071
|
+
const data = response.data;
|
|
1072
|
+
return { models: coerceModels(data.models) };
|
|
1073
|
+
},
|
|
1074
|
+
async installDictationModel({
|
|
1075
|
+
model,
|
|
1076
|
+
force
|
|
1077
|
+
}) {
|
|
1078
|
+
const response = await apiInstallDictationModel({
|
|
1079
|
+
path: { model },
|
|
1080
|
+
body: { force }
|
|
1081
|
+
});
|
|
1082
|
+
if (response.error)
|
|
1083
|
+
throw new Error(extractErrorMessage(response.error));
|
|
1084
|
+
const data = response.data;
|
|
1085
|
+
return { model: coerceModelState(data.model) };
|
|
1086
|
+
},
|
|
1087
|
+
async removeDictationModel(model) {
|
|
1088
|
+
const response = await apiRemoveDictationModel({ path: { model } });
|
|
1089
|
+
if (response.error)
|
|
1090
|
+
throw new Error(extractErrorMessage(response.error));
|
|
1091
|
+
const data = response.data;
|
|
1092
|
+
return {
|
|
1093
|
+
removed: data.removed,
|
|
1094
|
+
model: coerceModelState(data.model)
|
|
1095
|
+
};
|
|
1096
|
+
},
|
|
1097
|
+
async createDictationSession(input = {}) {
|
|
1098
|
+
const response = await apiCreateDictationSession({ body: input });
|
|
1099
|
+
if (response.error)
|
|
1100
|
+
throw new Error(extractErrorMessage(response.error));
|
|
1101
|
+
return response.data;
|
|
1102
|
+
},
|
|
1103
|
+
getDictationModelInstallEventsUrl: buildInstallEventsUrl
|
|
1104
|
+
};
|
|
1105
|
+
|
|
963
1106
|
// src/lib/api-client/index.ts
|
|
964
1107
|
class ApiClient {
|
|
965
1108
|
getSessions = sessionsMixin.getSessions;
|
|
@@ -1040,6 +1183,8 @@ class ApiClient {
|
|
|
1040
1183
|
getOAuthStartUrl = authMixin.getOAuthStartUrl;
|
|
1041
1184
|
getOAuthUrl = authMixin.getOAuthUrl;
|
|
1042
1185
|
exchangeOAuthCode = authMixin.exchangeOAuthCode;
|
|
1186
|
+
startOpenAIDeviceFlow = authMixin.startOpenAIDeviceFlow;
|
|
1187
|
+
pollOpenAIDeviceFlow = authMixin.pollOpenAIDeviceFlow;
|
|
1043
1188
|
startCopilotDeviceFlow = authMixin.startCopilotDeviceFlow;
|
|
1044
1189
|
pollCopilotDeviceFlow = authMixin.pollCopilotDeviceFlow;
|
|
1045
1190
|
getCopilotAuthMethods = authMixin.getCopilotAuthMethods;
|
|
@@ -1055,6 +1200,12 @@ class ApiClient {
|
|
|
1055
1200
|
updateSkillsConfig = skillsMixin.updateSkillsConfig;
|
|
1056
1201
|
getUsageStats = usageMixin.getUsageStats;
|
|
1057
1202
|
getGlobalUsageStats = usageMixin.getGlobalUsageStats;
|
|
1203
|
+
getDictationStatus = dictationMixin.getDictationStatus;
|
|
1204
|
+
listDictationModels = dictationMixin.listDictationModels;
|
|
1205
|
+
installDictationModel = dictationMixin.installDictationModel;
|
|
1206
|
+
removeDictationModel = dictationMixin.removeDictationModel;
|
|
1207
|
+
createDictationSession = dictationMixin.createDictationSession;
|
|
1208
|
+
getDictationModelInstallEventsUrl = dictationMixin.getDictationModelInstallEventsUrl;
|
|
1058
1209
|
}
|
|
1059
1210
|
var apiClient = new ApiClient;
|
|
1060
1211
|
|
|
@@ -1109,7 +1260,7 @@ function useUpdateDefaults() {
|
|
|
1109
1260
|
});
|
|
1110
1261
|
}
|
|
1111
1262
|
// src/hooks/usePreferences.ts
|
|
1112
|
-
import { useCallback,
|
|
1263
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
1113
1264
|
|
|
1114
1265
|
// src/lib/platform.ts
|
|
1115
1266
|
function getPlatformWindow() {
|
|
@@ -1174,13 +1325,14 @@ function isPlatformDesktop() {
|
|
|
1174
1325
|
}
|
|
1175
1326
|
|
|
1176
1327
|
// src/hooks/usePreferences.ts
|
|
1177
|
-
var STORAGE_KEY = "otto-preferences";
|
|
1178
1328
|
var DEFAULT_FONT_FAMILY = "IBM Plex Mono";
|
|
1179
|
-
var
|
|
1329
|
+
var DEFAULT_PREFERENCES = {
|
|
1180
1330
|
vimMode: false,
|
|
1181
1331
|
compactThread: true,
|
|
1182
1332
|
fontFamily: DEFAULT_FONT_FAMILY,
|
|
1183
|
-
smartEdges: true
|
|
1333
|
+
smartEdges: true,
|
|
1334
|
+
releaseToSend: false,
|
|
1335
|
+
fullWidthContent: false
|
|
1184
1336
|
};
|
|
1185
1337
|
function cssFontFamily(fontFamily) {
|
|
1186
1338
|
const trimmed = fontFamily.trim();
|
|
@@ -1202,90 +1354,52 @@ function applyFontFamily(fontFamily) {
|
|
|
1202
1354
|
window.parent.postMessage({ type: "otto-font-family-changed", fontFamily }, "*");
|
|
1203
1355
|
}
|
|
1204
1356
|
}
|
|
1205
|
-
function resolveInitialPreferences() {
|
|
1206
|
-
if (typeof window === "undefined") {
|
|
1207
|
-
return DEFAULT_STORED_PREFERENCES;
|
|
1208
|
-
}
|
|
1209
|
-
try {
|
|
1210
|
-
const stored = window.localStorage.getItem(STORAGE_KEY);
|
|
1211
|
-
if (stored) {
|
|
1212
|
-
const parsed = JSON.parse(stored);
|
|
1213
|
-
return {
|
|
1214
|
-
vimMode: typeof parsed.vimMode === "boolean" ? parsed.vimMode : DEFAULT_STORED_PREFERENCES.vimMode,
|
|
1215
|
-
compactThread: typeof parsed.compactThread === "boolean" ? parsed.compactThread : DEFAULT_STORED_PREFERENCES.compactThread,
|
|
1216
|
-
fontFamily: typeof parsed.fontFamily === "string" && parsed.fontFamily.trim() ? parsed.fontFamily.trim() : DEFAULT_STORED_PREFERENCES.fontFamily,
|
|
1217
|
-
smartEdges: typeof parsed.smartEdges === "boolean" ? parsed.smartEdges : DEFAULT_STORED_PREFERENCES.smartEdges
|
|
1218
|
-
};
|
|
1219
|
-
}
|
|
1220
|
-
} catch (error) {
|
|
1221
|
-
console.warn("Failed to load preferences", error);
|
|
1222
|
-
}
|
|
1223
|
-
return DEFAULT_STORED_PREFERENCES;
|
|
1224
|
-
}
|
|
1225
|
-
var preferences = resolveInitialPreferences();
|
|
1226
|
-
applyFontFamily(preferences.fontFamily);
|
|
1227
|
-
var listeners = new Set;
|
|
1228
|
-
function getSnapshot() {
|
|
1229
|
-
return preferences;
|
|
1230
|
-
}
|
|
1231
|
-
function getServerSnapshot() {
|
|
1232
|
-
return DEFAULT_STORED_PREFERENCES;
|
|
1233
|
-
}
|
|
1234
|
-
function subscribe(listener) {
|
|
1235
|
-
listeners.add(listener);
|
|
1236
|
-
return () => listeners.delete(listener);
|
|
1237
|
-
}
|
|
1238
|
-
function notifyListeners() {
|
|
1239
|
-
for (const listener of listeners) {
|
|
1240
|
-
listener();
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
function updateStore(updates) {
|
|
1244
|
-
preferences = { ...preferences, ...updates };
|
|
1245
|
-
if (updates.fontFamily !== undefined) {
|
|
1246
|
-
applyFontFamily(preferences.fontFamily);
|
|
1247
|
-
}
|
|
1248
|
-
if (typeof window !== "undefined") {
|
|
1249
|
-
try {
|
|
1250
|
-
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(preferences));
|
|
1251
|
-
} catch (error) {
|
|
1252
|
-
console.warn("Failed to persist preferences", error);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
notifyListeners();
|
|
1256
|
-
}
|
|
1257
1357
|
function usePreferences() {
|
|
1258
|
-
const currentPreferences = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
1259
1358
|
const { data: config2 } = useConfig();
|
|
1260
1359
|
const updateDefaults = useUpdateDefaults();
|
|
1360
|
+
const resolvedPreferences = useMemo(() => ({
|
|
1361
|
+
vimMode: config2?.defaults?.vimMode ?? DEFAULT_PREFERENCES.vimMode,
|
|
1362
|
+
compactThread: config2?.defaults?.compactThread ?? DEFAULT_PREFERENCES.compactThread,
|
|
1363
|
+
fontFamily: config2?.defaults?.fontFamily?.trim() || DEFAULT_PREFERENCES.fontFamily,
|
|
1364
|
+
smartEdges: config2?.defaults?.smartEdges ?? DEFAULT_PREFERENCES.smartEdges,
|
|
1365
|
+
releaseToSend: config2?.defaults?.releaseToSend ?? DEFAULT_PREFERENCES.releaseToSend,
|
|
1366
|
+
fullWidthContent: config2?.defaults?.fullWidthContent ?? DEFAULT_PREFERENCES.fullWidthContent
|
|
1367
|
+
}), [
|
|
1368
|
+
config2?.defaults?.vimMode,
|
|
1369
|
+
config2?.defaults?.compactThread,
|
|
1370
|
+
config2?.defaults?.fontFamily,
|
|
1371
|
+
config2?.defaults?.smartEdges,
|
|
1372
|
+
config2?.defaults?.releaseToSend,
|
|
1373
|
+
config2?.defaults?.fullWidthContent
|
|
1374
|
+
]);
|
|
1375
|
+
useEffect(() => {
|
|
1376
|
+
applyFontFamily(resolvedPreferences.fontFamily);
|
|
1377
|
+
}, [resolvedPreferences.fontFamily]);
|
|
1261
1378
|
const updatePreferences = useCallback((updates) => {
|
|
1262
|
-
const
|
|
1379
|
+
const nextUpdates = {};
|
|
1263
1380
|
if (updates.vimMode !== undefined) {
|
|
1264
|
-
|
|
1381
|
+
nextUpdates.vimMode = updates.vimMode;
|
|
1265
1382
|
}
|
|
1266
1383
|
if (updates.compactThread !== undefined) {
|
|
1267
|
-
|
|
1384
|
+
nextUpdates.compactThread = updates.compactThread;
|
|
1268
1385
|
}
|
|
1269
1386
|
if (updates.fontFamily !== undefined) {
|
|
1270
|
-
|
|
1387
|
+
nextUpdates.fontFamily = updates.fontFamily.trim() || DEFAULT_FONT_FAMILY;
|
|
1271
1388
|
}
|
|
1272
1389
|
if (updates.smartEdges !== undefined) {
|
|
1273
|
-
|
|
1390
|
+
nextUpdates.smartEdges = updates.smartEdges;
|
|
1274
1391
|
}
|
|
1275
|
-
if (
|
|
1276
|
-
|
|
1392
|
+
if (updates.releaseToSend !== undefined) {
|
|
1393
|
+
nextUpdates.releaseToSend = updates.releaseToSend;
|
|
1277
1394
|
}
|
|
1278
|
-
if (updates.fullWidthContent !== undefined
|
|
1279
|
-
|
|
1280
|
-
fullWidthContent: updates.fullWidthContent,
|
|
1281
|
-
scope: "global"
|
|
1282
|
-
});
|
|
1395
|
+
if (updates.fullWidthContent !== undefined) {
|
|
1396
|
+
nextUpdates.fullWidthContent = updates.fullWidthContent;
|
|
1283
1397
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
}
|
|
1398
|
+
if (Object.keys(nextUpdates).length === 0) {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
updateDefaults.mutate({ ...nextUpdates, scope: "global" });
|
|
1402
|
+
}, [updateDefaults]);
|
|
1289
1403
|
return useMemo(() => ({ preferences: resolvedPreferences, updatePreferences }), [resolvedPreferences, updatePreferences]);
|
|
1290
1404
|
}
|
|
1291
1405
|
// src/hooks/useFiles.ts
|
|
@@ -1770,7 +1884,14 @@ var useSkillsStore = create2((set, get) => ({
|
|
|
1770
1884
|
collapseSidebar: () => set({ isExpanded: false }),
|
|
1771
1885
|
setSkills: (skills) => set({ skills }),
|
|
1772
1886
|
setSkillsConfig: ({ skills, globalEnabled, totalCount, enabledCount }) => set({ skills, globalEnabled, totalCount, enabledCount }),
|
|
1773
|
-
selectSkill: (name) =>
|
|
1887
|
+
selectSkill: (name) => {
|
|
1888
|
+
if (name) {
|
|
1889
|
+
useViewerTabsStore.getState().openSkillFileTab(name, null);
|
|
1890
|
+
set({ selectedSkill: name, isViewerOpen: true, viewingFile: null });
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
set({ selectedSkill: null, isViewerOpen: false, viewingFile: null });
|
|
1894
|
+
},
|
|
1774
1895
|
openViewer: (file) => {
|
|
1775
1896
|
const selectedSkill = get().selectedSkill;
|
|
1776
1897
|
if (selectedSkill) {
|
|
@@ -2562,7 +2683,7 @@ function useSendMessage(sessionId) {
|
|
|
2562
2683
|
});
|
|
2563
2684
|
}
|
|
2564
2685
|
// src/hooks/useSessionStream.ts
|
|
2565
|
-
import { useEffect, useRef } from "react";
|
|
2686
|
+
import { useEffect as useEffect2, useRef } from "react";
|
|
2566
2687
|
import { useQueryClient as useQueryClient5 } from "@tanstack/react-query";
|
|
2567
2688
|
|
|
2568
2689
|
// src/lib/sse-client.ts
|
|
@@ -2717,7 +2838,7 @@ function useSessionStream(sessionId, enabled = true) {
|
|
|
2717
2838
|
updatePendingApproval,
|
|
2718
2839
|
setPendingApprovals
|
|
2719
2840
|
} = useToolApprovalStore();
|
|
2720
|
-
|
|
2841
|
+
useEffect2(() => {
|
|
2721
2842
|
if (!sessionId || !enabled) {
|
|
2722
2843
|
return;
|
|
2723
2844
|
}
|
|
@@ -4048,7 +4169,7 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
|
|
|
4048
4169
|
]);
|
|
4049
4170
|
}
|
|
4050
4171
|
// src/hooks/useClientEvents.ts
|
|
4051
|
-
import { useEffect as
|
|
4172
|
+
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
4052
4173
|
import { useQueryClient as useQueryClient6 } from "@tanstack/react-query";
|
|
4053
4174
|
import {
|
|
4054
4175
|
buildClientEventsStreamUrl,
|
|
@@ -4298,10 +4419,10 @@ async function maybeShowLocalAccessToast(baseUrl) {
|
|
|
4298
4419
|
function useClientEvents(activeSessionId) {
|
|
4299
4420
|
const queryClient = useQueryClient6();
|
|
4300
4421
|
const activeSessionIdRef = useRef2(activeSessionId);
|
|
4301
|
-
|
|
4422
|
+
useEffect3(() => {
|
|
4302
4423
|
activeSessionIdRef.current = activeSessionId;
|
|
4303
4424
|
}, [activeSessionId]);
|
|
4304
|
-
|
|
4425
|
+
useEffect3(() => {
|
|
4305
4426
|
if (typeof window === "undefined" || window.parent !== window)
|
|
4306
4427
|
return;
|
|
4307
4428
|
if (!("Notification" in window))
|
|
@@ -4330,7 +4451,7 @@ function useClientEvents(activeSessionId) {
|
|
|
4330
4451
|
}
|
|
4331
4452
|
});
|
|
4332
4453
|
}, []);
|
|
4333
|
-
|
|
4454
|
+
useEffect3(() => {
|
|
4334
4455
|
const controller = new AbortController;
|
|
4335
4456
|
const baseUrl = getBaseUrl();
|
|
4336
4457
|
createClientEventsStream({
|
|
@@ -4380,24 +4501,15 @@ function useClientEvents(activeSessionId) {
|
|
|
4380
4501
|
return buildClientEventsStreamUrl({ baseUrl: getBaseUrl() });
|
|
4381
4502
|
}
|
|
4382
4503
|
// src/hooks/useTheme.ts
|
|
4383
|
-
import {
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
if (typeof window === "undefined") {
|
|
4387
|
-
return "dark";
|
|
4388
|
-
}
|
|
4389
|
-
const stored = window.localStorage.getItem(STORAGE_KEY2);
|
|
4390
|
-
if (stored === "light" || stored === "dark") {
|
|
4391
|
-
return stored;
|
|
4392
|
-
}
|
|
4393
|
-
if (window.matchMedia?.("(prefers-color-scheme: light)").matches) {
|
|
4394
|
-
return "light";
|
|
4395
|
-
}
|
|
4396
|
-
return "dark";
|
|
4504
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3 } from "react";
|
|
4505
|
+
function normalizeTheme(theme) {
|
|
4506
|
+
return theme === "light" ? "light" : "dark";
|
|
4397
4507
|
}
|
|
4398
4508
|
function useTheme() {
|
|
4399
|
-
const
|
|
4400
|
-
|
|
4509
|
+
const { data: config2 } = useConfig();
|
|
4510
|
+
const updateDefaults = useUpdateDefaults();
|
|
4511
|
+
const theme = normalizeTheme(config2?.defaults?.theme);
|
|
4512
|
+
useEffect4(() => {
|
|
4401
4513
|
if (typeof document === "undefined")
|
|
4402
4514
|
return;
|
|
4403
4515
|
const root = document.documentElement;
|
|
@@ -4406,37 +4518,24 @@ function useTheme() {
|
|
|
4406
4518
|
} else {
|
|
4407
4519
|
root.classList.remove("dark");
|
|
4408
4520
|
}
|
|
4409
|
-
try {
|
|
4410
|
-
window.localStorage.setItem(STORAGE_KEY2, theme);
|
|
4411
|
-
} catch (error) {
|
|
4412
|
-
console.warn("Failed to persist theme preference", error);
|
|
4413
|
-
}
|
|
4414
4521
|
if (window.parent && window.parent !== window) {
|
|
4415
4522
|
window.parent.postMessage({ type: "otto-set-theme", theme }, "*");
|
|
4416
4523
|
}
|
|
4417
4524
|
}, [theme]);
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
const handler = (e) => {
|
|
4422
|
-
if (e.data?.type === "otto-set-theme" && (e.data.theme === "light" || e.data.theme === "dark")) {
|
|
4423
|
-
setTheme(e.data.theme);
|
|
4424
|
-
}
|
|
4425
|
-
};
|
|
4426
|
-
window.addEventListener("message", handler);
|
|
4427
|
-
return () => window.removeEventListener("message", handler);
|
|
4428
|
-
}, []);
|
|
4525
|
+
const setTheme = useCallback2((nextTheme) => {
|
|
4526
|
+
updateDefaults.mutate({ theme: nextTheme, scope: "global" });
|
|
4527
|
+
}, [updateDefaults]);
|
|
4429
4528
|
const toggleTheme = useCallback2(() => {
|
|
4430
|
-
setTheme(
|
|
4431
|
-
}, []);
|
|
4432
|
-
return useMemo3(() => ({ theme, setTheme, toggleTheme }), [theme, toggleTheme]);
|
|
4529
|
+
setTheme(theme === "dark" ? "light" : "dark");
|
|
4530
|
+
}, [setTheme, theme]);
|
|
4531
|
+
return useMemo3(() => ({ theme, setTheme, toggleTheme }), [theme, setTheme, toggleTheme]);
|
|
4433
4532
|
}
|
|
4434
4533
|
// src/hooks/useWorkingDirectory.ts
|
|
4435
|
-
import { useEffect as
|
|
4534
|
+
import { useEffect as useEffect5, useState } from "react";
|
|
4436
4535
|
import { getCwd } from "@ottocode/api";
|
|
4437
4536
|
function useWorkingDirectory() {
|
|
4438
|
-
const [dirName, setDirName] =
|
|
4439
|
-
|
|
4537
|
+
const [dirName, setDirName] = useState(null);
|
|
4538
|
+
useEffect5(() => {
|
|
4440
4539
|
const fetchWorkingDirectory = async () => {
|
|
4441
4540
|
try {
|
|
4442
4541
|
const response = await getCwd({ baseURL: getBaseUrl() });
|
|
@@ -4460,7 +4559,7 @@ function useWorkingDirectory() {
|
|
|
4460
4559
|
return dirName;
|
|
4461
4560
|
}
|
|
4462
4561
|
// src/hooks/useKeyboardShortcuts.ts
|
|
4463
|
-
import { useEffect as
|
|
4562
|
+
import { useEffect as useEffect6, useCallback as useCallback3 } from "react";
|
|
4464
4563
|
|
|
4465
4564
|
// src/stores/focusStore.ts
|
|
4466
4565
|
import { create as create12 } from "zustand";
|
|
@@ -4821,7 +4920,7 @@ function useKeyboardShortcuts({
|
|
|
4821
4920
|
onReturnToInput,
|
|
4822
4921
|
closeDiff
|
|
4823
4922
|
]);
|
|
4824
|
-
|
|
4923
|
+
useEffect6(() => {
|
|
4825
4924
|
window.addEventListener("keydown", handleKeyDown, true);
|
|
4826
4925
|
return () => window.removeEventListener("keydown", handleKeyDown, true);
|
|
4827
4926
|
}, [handleKeyDown]);
|
|
@@ -4833,9 +4932,9 @@ function useKeyboardShortcuts({
|
|
|
4833
4932
|
}
|
|
4834
4933
|
// src/hooks/useImageUpload.ts
|
|
4835
4934
|
import {
|
|
4836
|
-
useState as
|
|
4935
|
+
useState as useState2,
|
|
4837
4936
|
useCallback as useCallback4,
|
|
4838
|
-
useEffect as
|
|
4937
|
+
useEffect as useEffect7
|
|
4839
4938
|
} from "react";
|
|
4840
4939
|
var SUPPORTED_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
4841
4940
|
function generateId() {
|
|
@@ -4863,9 +4962,9 @@ async function fileToPreview(file) {
|
|
|
4863
4962
|
}
|
|
4864
4963
|
function useImageUpload(options = {}) {
|
|
4865
4964
|
const { maxImages = 5, maxSizeMB = 5, pageWide = true } = options;
|
|
4866
|
-
const [images, setImages] =
|
|
4867
|
-
const [isDragging, setIsDragging] =
|
|
4868
|
-
const [error, setError] =
|
|
4965
|
+
const [images, setImages] = useState2([]);
|
|
4966
|
+
const [isDragging, setIsDragging] = useState2(false);
|
|
4967
|
+
const [error, setError] = useState2(null);
|
|
4869
4968
|
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
|
4870
4969
|
const validateFile = useCallback4((file) => {
|
|
4871
4970
|
if (!SUPPORTED_TYPES.includes(file.type)) {
|
|
@@ -4971,7 +5070,7 @@ function useImageUpload(options = {}) {
|
|
|
4971
5070
|
addImages(imageFiles);
|
|
4972
5071
|
}
|
|
4973
5072
|
}, [addImages]);
|
|
4974
|
-
|
|
5073
|
+
useEffect7(() => {
|
|
4975
5074
|
if (!pageWide)
|
|
4976
5075
|
return;
|
|
4977
5076
|
let dragCounter = 0;
|
|
@@ -5033,9 +5132,9 @@ function useImageUpload(options = {}) {
|
|
|
5033
5132
|
}
|
|
5034
5133
|
// src/hooks/useFileUpload.ts
|
|
5035
5134
|
import {
|
|
5036
|
-
useState as
|
|
5135
|
+
useState as useState3,
|
|
5037
5136
|
useCallback as useCallback5,
|
|
5038
|
-
useEffect as
|
|
5137
|
+
useEffect as useEffect8
|
|
5039
5138
|
} from "react";
|
|
5040
5139
|
var IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
5041
5140
|
var PDF_TYPES = ["application/pdf"];
|
|
@@ -5165,9 +5264,9 @@ function useFileUpload(options = {}) {
|
|
|
5165
5264
|
sessionId,
|
|
5166
5265
|
onError
|
|
5167
5266
|
} = options;
|
|
5168
|
-
const [files, setFiles] =
|
|
5169
|
-
const [isDragging, setIsDragging] =
|
|
5170
|
-
const [error, setError] =
|
|
5267
|
+
const [files, setFiles] = useState3([]);
|
|
5268
|
+
const [isDragging, setIsDragging] = useState3(false);
|
|
5269
|
+
const [error, setError] = useState3(null);
|
|
5171
5270
|
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
|
5172
5271
|
const validateFile = useCallback5((file) => {
|
|
5173
5272
|
if (file.size > maxSizeBytes) {
|
|
@@ -5326,7 +5425,7 @@ function useFileUpload(options = {}) {
|
|
|
5326
5425
|
addFiles(pastedFiles);
|
|
5327
5426
|
}
|
|
5328
5427
|
}, [addFiles]);
|
|
5329
|
-
|
|
5428
|
+
useEffect8(() => {
|
|
5330
5429
|
if (!pageWide)
|
|
5331
5430
|
return;
|
|
5332
5431
|
let dragCounter = 0;
|
|
@@ -5599,8 +5698,8 @@ function useExportToSession() {
|
|
|
5599
5698
|
}
|
|
5600
5699
|
});
|
|
5601
5700
|
}
|
|
5602
|
-
// src/hooks/
|
|
5603
|
-
import { useEffect as
|
|
5701
|
+
// src/hooks/useOttoRouterPayments.ts
|
|
5702
|
+
import { useEffect as useEffect9, useRef as useRef3 } from "react";
|
|
5604
5703
|
|
|
5605
5704
|
// src/stores/ottorouterStore.ts
|
|
5606
5705
|
import { create as create17 } from "zustand";
|
|
@@ -5644,8 +5743,8 @@ var useTopupApprovalStore = create18((set) => ({
|
|
|
5644
5743
|
clearPendingTopup: () => set({ pendingTopup: null, isProcessing: false, selectedMethod: null })
|
|
5645
5744
|
}));
|
|
5646
5745
|
|
|
5647
|
-
// src/hooks/
|
|
5648
|
-
function
|
|
5746
|
+
// src/hooks/useOttoRouterPayments.ts
|
|
5747
|
+
function useOttoRouterPayments(sessionId) {
|
|
5649
5748
|
const clientRef = useRef3(null);
|
|
5650
5749
|
const loadingToastIdRef = useRef3(null);
|
|
5651
5750
|
const setBalance = useOttoRouterStore((s) => s.setBalance);
|
|
@@ -5654,7 +5753,7 @@ function useSetuPayments(sessionId) {
|
|
|
5654
5753
|
const updateToast = useToastStore((s) => s.updateToast);
|
|
5655
5754
|
const setPendingTopup = useTopupApprovalStore((s) => s.setPendingTopup);
|
|
5656
5755
|
const clearPendingTopup = useTopupApprovalStore((s) => s.clearPendingTopup);
|
|
5657
|
-
|
|
5756
|
+
useEffect9(() => {
|
|
5658
5757
|
if (!sessionId)
|
|
5659
5758
|
return;
|
|
5660
5759
|
const client2 = new SSEClient;
|
|
@@ -5784,7 +5883,7 @@ function useSetuPayments(sessionId) {
|
|
|
5784
5883
|
]);
|
|
5785
5884
|
}
|
|
5786
5885
|
// src/hooks/useOttoRouterBalance.ts
|
|
5787
|
-
import { useEffect as
|
|
5886
|
+
import { useEffect as useEffect10, useCallback as useCallback6 } from "react";
|
|
5788
5887
|
|
|
5789
5888
|
// src/stores/usageStore.ts
|
|
5790
5889
|
import { create as create19 } from "zustand";
|
|
@@ -5822,19 +5921,19 @@ function useOttoRouterBalance(providerName) {
|
|
|
5822
5921
|
}
|
|
5823
5922
|
setLoading(true);
|
|
5824
5923
|
try {
|
|
5825
|
-
const [
|
|
5924
|
+
const [ottorouterData, usdcData, walletData] = await Promise.all([
|
|
5826
5925
|
apiClient.getOttoRouterBalance(),
|
|
5827
5926
|
apiClient.getOttoRouterUsdcBalance(network),
|
|
5828
5927
|
apiClient.getOttoRouterWallet()
|
|
5829
5928
|
]);
|
|
5830
|
-
if (
|
|
5831
|
-
setBalance(
|
|
5832
|
-
setWalletAddress(
|
|
5833
|
-
setScope(
|
|
5834
|
-
setPayg(
|
|
5835
|
-
setSubscription(
|
|
5836
|
-
setLimits(
|
|
5837
|
-
const sub =
|
|
5929
|
+
if (ottorouterData) {
|
|
5930
|
+
setBalance(ottorouterData.balance);
|
|
5931
|
+
setWalletAddress(ottorouterData.walletAddress);
|
|
5932
|
+
setScope(ottorouterData.scope ?? null);
|
|
5933
|
+
setPayg(ottorouterData.payg ?? null);
|
|
5934
|
+
setSubscription(ottorouterData.subscription ?? null);
|
|
5935
|
+
setLimits(ottorouterData.limits ?? null);
|
|
5936
|
+
const sub = ottorouterData.subscription;
|
|
5838
5937
|
if (sub?.active && sub.usageWindows) {
|
|
5839
5938
|
setUsage("ottorouter", {
|
|
5840
5939
|
provider: "ottorouter",
|
|
@@ -5857,7 +5956,7 @@ function useOttoRouterBalance(providerName) {
|
|
|
5857
5956
|
}
|
|
5858
5957
|
if (usdcData) {
|
|
5859
5958
|
setUsdcBalance(usdcData.usdcBalance);
|
|
5860
|
-
if (!
|
|
5959
|
+
if (!ottorouterData && usdcData.walletAddress) {
|
|
5861
5960
|
setWalletAddress(usdcData.walletAddress);
|
|
5862
5961
|
}
|
|
5863
5962
|
}
|
|
@@ -5878,7 +5977,7 @@ function useOttoRouterBalance(providerName) {
|
|
|
5878
5977
|
setUsage
|
|
5879
5978
|
]);
|
|
5880
5979
|
const needsUsageWindows = subscription?.active && !subscription.usageWindows;
|
|
5881
|
-
|
|
5980
|
+
useEffect10(() => {
|
|
5882
5981
|
if (providerName === "ottorouter" && (balance === null || usdcBalance === null || needsUsageWindows)) {
|
|
5883
5982
|
fetchBalance();
|
|
5884
5983
|
}
|
|
@@ -5899,7 +5998,7 @@ function useShareStatus(sessionId) {
|
|
|
5899
5998
|
return { data, isLoading, error };
|
|
5900
5999
|
}
|
|
5901
6000
|
// src/hooks/useToolApprovalShortcuts.ts
|
|
5902
|
-
import { useEffect as
|
|
6001
|
+
import { useEffect as useEffect11, useCallback as useCallback7 } from "react";
|
|
5903
6002
|
function useToolApprovalShortcuts(sessionId) {
|
|
5904
6003
|
const { pendingApprovals, removePendingApproval } = useToolApprovalStore();
|
|
5905
6004
|
const sessionPendingApprovals = pendingApprovals;
|
|
@@ -5935,7 +6034,7 @@ function useToolApprovalShortcuts(sessionId) {
|
|
|
5935
6034
|
console.error("Failed to approve all tool calls:", error);
|
|
5936
6035
|
}
|
|
5937
6036
|
}, [sessionId, sessionPendingApprovals, removePendingApproval]);
|
|
5938
|
-
|
|
6037
|
+
useEffect11(() => {
|
|
5939
6038
|
if (!sessionId || sessionPendingApprovals.length === 0)
|
|
5940
6039
|
return;
|
|
5941
6040
|
const handleKeyDown = (e) => {
|
|
@@ -5965,19 +6064,19 @@ function useToolApprovalShortcuts(sessionId) {
|
|
|
5965
6064
|
]);
|
|
5966
6065
|
}
|
|
5967
6066
|
// src/hooks/useTopupCallback.ts
|
|
5968
|
-
import { useEffect as
|
|
5969
|
-
var
|
|
6067
|
+
import { useEffect as useEffect12, useRef as useRef4 } from "react";
|
|
6068
|
+
var STORAGE_KEY = "pendingPolarCheckout";
|
|
5970
6069
|
function useTopupCallback() {
|
|
5971
6070
|
const hasHandled = useRef4(false);
|
|
5972
6071
|
const loadingToastId = useRef4(null);
|
|
5973
6072
|
const setBalance = useOttoRouterStore((s) => s.setBalance);
|
|
5974
6073
|
const removeToast = useToastStore((s) => s.removeToast);
|
|
5975
|
-
|
|
6074
|
+
useEffect12(() => {
|
|
5976
6075
|
if (hasHandled.current)
|
|
5977
6076
|
return;
|
|
5978
6077
|
const params = new URLSearchParams(window.location.search);
|
|
5979
6078
|
const topupStatus = params.get("topup");
|
|
5980
|
-
const pendingCheckoutId = localStorage.getItem(
|
|
6079
|
+
const pendingCheckoutId = localStorage.getItem(STORAGE_KEY);
|
|
5981
6080
|
if (topupStatus === "pending" || pendingCheckoutId) {
|
|
5982
6081
|
hasHandled.current = true;
|
|
5983
6082
|
const url = new URL(window.location.href);
|
|
@@ -6002,7 +6101,7 @@ function useTopupCallback() {
|
|
|
6002
6101
|
try {
|
|
6003
6102
|
const status = await apiClient.getPolarTopupStatus(pendingCheckoutId);
|
|
6004
6103
|
if (status?.confirmed) {
|
|
6005
|
-
localStorage.removeItem(
|
|
6104
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
6006
6105
|
dismissLoading();
|
|
6007
6106
|
const balanceData = await apiClient.getOttoRouterBalance();
|
|
6008
6107
|
if (balanceData?.balance !== undefined) {
|
|
@@ -6014,7 +6113,7 @@ function useTopupCallback() {
|
|
|
6014
6113
|
if (attempts < maxAttempts) {
|
|
6015
6114
|
setTimeout(checkStatus, delayMs);
|
|
6016
6115
|
} else {
|
|
6017
|
-
localStorage.removeItem(
|
|
6116
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
6018
6117
|
dismissLoading();
|
|
6019
6118
|
toast.info("Top-up is still processing. Balance will update automatically.");
|
|
6020
6119
|
}
|
|
@@ -6022,7 +6121,7 @@ function useTopupCallback() {
|
|
|
6022
6121
|
if (attempts < maxAttempts) {
|
|
6023
6122
|
setTimeout(checkStatus, delayMs);
|
|
6024
6123
|
} else {
|
|
6025
|
-
localStorage.removeItem(
|
|
6124
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
6026
6125
|
dismissLoading();
|
|
6027
6126
|
toast.error("Could not verify top-up. Please check your balance.");
|
|
6028
6127
|
}
|
|
@@ -6033,7 +6132,7 @@ function useTopupCallback() {
|
|
|
6033
6132
|
}, [setBalance, removeToast]);
|
|
6034
6133
|
}
|
|
6035
6134
|
// src/hooks/useAuthStatus.ts
|
|
6036
|
-
import { useEffect as
|
|
6135
|
+
import { useEffect as useEffect13, useCallback as useCallback8, useState as useState4, useRef as useRef5 } from "react";
|
|
6037
6136
|
import { useQueryClient as useQueryClient9 } from "@tanstack/react-query";
|
|
6038
6137
|
|
|
6039
6138
|
// src/stores/onboardingStore.ts
|
|
@@ -6085,8 +6184,8 @@ function useAuthStatus() {
|
|
|
6085
6184
|
const authStatus = useOnboardingStore((s) => s.authStatus);
|
|
6086
6185
|
const isOpen = useOnboardingStore((s) => s.isOpen);
|
|
6087
6186
|
const queryClient = useQueryClient9();
|
|
6088
|
-
const [initialized, setInitialized] =
|
|
6089
|
-
const [oauthPolling, setOauthPolling] =
|
|
6187
|
+
const [initialized, setInitialized] = useState4(false);
|
|
6188
|
+
const [oauthPolling, setOauthPolling] = useState4(false);
|
|
6090
6189
|
const oauthPollingRef = useRef5(null);
|
|
6091
6190
|
const preOauthProvidersRef = useRef5(new Set);
|
|
6092
6191
|
const fetchAuthStatus = useCallback8(async () => {
|
|
@@ -6278,7 +6377,7 @@ function useAuthStatus() {
|
|
|
6278
6377
|
setLoading(false);
|
|
6279
6378
|
}
|
|
6280
6379
|
}, [fetchAuthStatus, setLoading, setError]);
|
|
6281
|
-
|
|
6380
|
+
useEffect13(() => {
|
|
6282
6381
|
if (!oauthPolling || !isInIframe)
|
|
6283
6382
|
return;
|
|
6284
6383
|
oauthPollingRef.current = setInterval(() => {
|
|
@@ -6292,7 +6391,7 @@ function useAuthStatus() {
|
|
|
6292
6391
|
clearTimeout(timeout);
|
|
6293
6392
|
};
|
|
6294
6393
|
}, [oauthPolling, fetchAuthStatus]);
|
|
6295
|
-
|
|
6394
|
+
useEffect13(() => {
|
|
6296
6395
|
if (!oauthPolling || !authStatus)
|
|
6297
6396
|
return;
|
|
6298
6397
|
const currentConfigured = Object.entries(authStatus.providers).filter(([, p]) => p.configured);
|
|
@@ -6301,7 +6400,7 @@ function useAuthStatus() {
|
|
|
6301
6400
|
setOauthPolling(false);
|
|
6302
6401
|
}
|
|
6303
6402
|
}, [authStatus, oauthPolling]);
|
|
6304
|
-
|
|
6403
|
+
useEffect13(() => {
|
|
6305
6404
|
const handleOAuthMessage = (event) => {
|
|
6306
6405
|
if (event.data?.type === "oauth-success") {
|
|
6307
6406
|
fetchAuthStatus();
|
|
@@ -6317,6 +6416,13 @@ function useAuthStatus() {
|
|
|
6317
6416
|
}
|
|
6318
6417
|
return result;
|
|
6319
6418
|
}, [fetchAuthStatus]);
|
|
6419
|
+
const pollOpenAIDeviceFlow = useCallback8(async (sessionId) => {
|
|
6420
|
+
const result = await apiClient.pollOpenAIDeviceFlow(sessionId);
|
|
6421
|
+
if (result.status === "complete") {
|
|
6422
|
+
await fetchAuthStatus();
|
|
6423
|
+
}
|
|
6424
|
+
return result;
|
|
6425
|
+
}, [fetchAuthStatus]);
|
|
6320
6426
|
const saveCopilotToken = useCallback8(async (token) => {
|
|
6321
6427
|
setLoading(true);
|
|
6322
6428
|
setError(null);
|
|
@@ -6362,6 +6468,8 @@ function useAuthStatus() {
|
|
|
6362
6468
|
startOAuth,
|
|
6363
6469
|
startOAuthManual,
|
|
6364
6470
|
exchangeOAuthCode,
|
|
6471
|
+
startOpenAIDeviceFlow: apiClient.startOpenAIDeviceFlow.bind(apiClient),
|
|
6472
|
+
pollOpenAIDeviceFlow,
|
|
6365
6473
|
startCopilotDeviceFlow: apiClient.startCopilotDeviceFlow.bind(apiClient),
|
|
6366
6474
|
pollCopilotDeviceFlow,
|
|
6367
6475
|
getCopilotAuthMethods: apiClient.getCopilotAuthMethods.bind(apiClient),
|
|
@@ -6372,7 +6480,7 @@ function useAuthStatus() {
|
|
|
6372
6480
|
}
|
|
6373
6481
|
// src/hooks/useTunnel.ts
|
|
6374
6482
|
import { useQuery as useQuery10, useMutation as useMutation7, useQueryClient as useQueryClient10 } from "@tanstack/react-query";
|
|
6375
|
-
import { useEffect as
|
|
6483
|
+
import { useEffect as useEffect14, useCallback as useCallback9, useRef as useRef6 } from "react";
|
|
6376
6484
|
import {
|
|
6377
6485
|
client as client2,
|
|
6378
6486
|
getTunnelQr,
|
|
@@ -6415,7 +6523,7 @@ function useTunnelStatus() {
|
|
|
6415
6523
|
queryFn: fetchTunnelStatus,
|
|
6416
6524
|
refetchInterval: 3000
|
|
6417
6525
|
});
|
|
6418
|
-
|
|
6526
|
+
useEffect14(() => {
|
|
6419
6527
|
if (query.data) {
|
|
6420
6528
|
setStatus(query.data.status);
|
|
6421
6529
|
setUrl(query.data.url);
|
|
@@ -6475,7 +6583,7 @@ function useTunnelQr() {
|
|
|
6475
6583
|
queryFn: fetchTunnelQr,
|
|
6476
6584
|
enabled: !!url
|
|
6477
6585
|
});
|
|
6478
|
-
|
|
6586
|
+
useEffect14(() => {
|
|
6479
6587
|
if (query.data?.ok && query.data.qrCode) {
|
|
6480
6588
|
setQrCode(query.data.qrCode);
|
|
6481
6589
|
}
|
|
@@ -6515,7 +6623,7 @@ function useTunnelStream() {
|
|
|
6515
6623
|
eventSourceRef.current = null;
|
|
6516
6624
|
};
|
|
6517
6625
|
}, [setStatus, setUrl, setError, setProgress]);
|
|
6518
|
-
|
|
6626
|
+
useEffect14(() => {
|
|
6519
6627
|
if (isExpanded) {
|
|
6520
6628
|
const cleanup = connect();
|
|
6521
6629
|
return cleanup;
|
|
@@ -6530,7 +6638,7 @@ function useTunnelStream() {
|
|
|
6530
6638
|
return { connect };
|
|
6531
6639
|
}
|
|
6532
6640
|
// src/hooks/useProviderUsage.ts
|
|
6533
|
-
import { useEffect as
|
|
6641
|
+
import { useEffect as useEffect15, useCallback as useCallback10, useRef as useRef7 } from "react";
|
|
6534
6642
|
var POLL_INTERVAL = 60000;
|
|
6535
6643
|
var STALE_THRESHOLD = 60000;
|
|
6536
6644
|
var inflight = new Set;
|
|
@@ -6563,12 +6671,12 @@ function useProviderUsage(provider, authType) {
|
|
|
6563
6671
|
}, [provider, isOAuthProvider, setUsage, setLoading, setLastFetched]);
|
|
6564
6672
|
const fetchRef = useRef7(fetchUsage);
|
|
6565
6673
|
fetchRef.current = fetchUsage;
|
|
6566
|
-
|
|
6674
|
+
useEffect15(() => {
|
|
6567
6675
|
if (!provider || !isOAuthProvider)
|
|
6568
6676
|
return;
|
|
6569
6677
|
fetchRef.current();
|
|
6570
6678
|
}, [isOAuthProvider, provider]);
|
|
6571
|
-
|
|
6679
|
+
useEffect15(() => {
|
|
6572
6680
|
if (!provider || !isOAuthProvider || !isModalOpen || modalProvider !== provider) {
|
|
6573
6681
|
return;
|
|
6574
6682
|
}
|
|
@@ -6613,7 +6721,7 @@ function useGitDiffFullFile(file, staged = false, enabled = false) {
|
|
|
6613
6721
|
}
|
|
6614
6722
|
// src/hooks/useMCP.ts
|
|
6615
6723
|
import { useQuery as useQuery12, useMutation as useMutation8, useQueryClient as useQueryClient11 } from "@tanstack/react-query";
|
|
6616
|
-
import { useEffect as
|
|
6724
|
+
import { useEffect as useEffect16, useRef as useRef8, useCallback as useCallback11 } from "react";
|
|
6617
6725
|
import {
|
|
6618
6726
|
listMcpServers,
|
|
6619
6727
|
startMcpServer,
|
|
@@ -6635,7 +6743,7 @@ function useMCPServers() {
|
|
|
6635
6743
|
},
|
|
6636
6744
|
refetchInterval: 1e4
|
|
6637
6745
|
});
|
|
6638
|
-
|
|
6746
|
+
useEffect16(() => {
|
|
6639
6747
|
if (query.data?.servers) {
|
|
6640
6748
|
setServers(query.data.servers);
|
|
6641
6749
|
}
|
|
@@ -6786,7 +6894,7 @@ function useCopilotDevicePoller() {
|
|
|
6786
6894
|
timerRef.current = null;
|
|
6787
6895
|
}
|
|
6788
6896
|
}, []);
|
|
6789
|
-
|
|
6897
|
+
useEffect16(() => {
|
|
6790
6898
|
if (!copilotDevice) {
|
|
6791
6899
|
stopPolling();
|
|
6792
6900
|
return;
|
|
@@ -6822,7 +6930,7 @@ function useCopilotDevicePoller() {
|
|
|
6822
6930
|
}
|
|
6823
6931
|
// src/hooks/useSkills.ts
|
|
6824
6932
|
import { useMutation as useMutation9, useQuery as useQuery13, useQueryClient as useQueryClient12 } from "@tanstack/react-query";
|
|
6825
|
-
import { useEffect as
|
|
6933
|
+
import { useEffect as useEffect17 } from "react";
|
|
6826
6934
|
function useSkills() {
|
|
6827
6935
|
const setSkillsConfig = useSkillsStore((s) => s.setSkillsConfig);
|
|
6828
6936
|
const query = useQuery13({
|
|
@@ -6832,7 +6940,7 @@ function useSkills() {
|
|
|
6832
6940
|
},
|
|
6833
6941
|
refetchInterval: 30000
|
|
6834
6942
|
});
|
|
6835
|
-
|
|
6943
|
+
useEffect17(() => {
|
|
6836
6944
|
if (query.data?.items) {
|
|
6837
6945
|
setSkillsConfig({
|
|
6838
6946
|
skills: query.data.items,
|
|
@@ -6894,10 +7002,10 @@ function useSkillFileContent(name, filePath) {
|
|
|
6894
7002
|
});
|
|
6895
7003
|
}
|
|
6896
7004
|
// src/hooks/useContainerWidth.ts
|
|
6897
|
-
import { useEffect as
|
|
7005
|
+
import { useEffect as useEffect18, useState as useState5 } from "react";
|
|
6898
7006
|
function useContainerWidth(ref) {
|
|
6899
|
-
const [width, setWidth] =
|
|
6900
|
-
|
|
7007
|
+
const [width, setWidth] = useState5(0);
|
|
7008
|
+
useEffect18(() => {
|
|
6901
7009
|
const el = ref.current;
|
|
6902
7010
|
if (!el)
|
|
6903
7011
|
return;
|
|
@@ -6909,8 +7017,402 @@ function useContainerWidth(ref) {
|
|
|
6909
7017
|
}, [ref]);
|
|
6910
7018
|
return width;
|
|
6911
7019
|
}
|
|
7020
|
+
// src/hooks/useVoiceInput.ts
|
|
7021
|
+
import { useCallback as useCallback12, useEffect as useEffect19, useRef as useRef9, useState as useState6 } from "react";
|
|
7022
|
+
var TARGET_SAMPLE_RATE = 16000;
|
|
7023
|
+
var PCM_FRAME_BYTES = 3200;
|
|
7024
|
+
function getAudioContextConstructor() {
|
|
7025
|
+
if (typeof window === "undefined")
|
|
7026
|
+
return null;
|
|
7027
|
+
return window.AudioContext || window.webkitAudioContext || null;
|
|
7028
|
+
}
|
|
7029
|
+
function toLanguageCode(lang) {
|
|
7030
|
+
return lang.split("-", 1)[0]?.toLowerCase() || "en";
|
|
7031
|
+
}
|
|
7032
|
+
function parseServerEvent(raw) {
|
|
7033
|
+
try {
|
|
7034
|
+
const value = JSON.parse(raw);
|
|
7035
|
+
if (!value || typeof value.type !== "string")
|
|
7036
|
+
return null;
|
|
7037
|
+
return value;
|
|
7038
|
+
} catch {
|
|
7039
|
+
return null;
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
7042
|
+
function resampleLinear(input, inputSampleRate, outputSampleRate) {
|
|
7043
|
+
if (inputSampleRate === outputSampleRate)
|
|
7044
|
+
return input;
|
|
7045
|
+
const ratio = inputSampleRate / outputSampleRate;
|
|
7046
|
+
const outputLength = Math.max(1, Math.floor(input.length / ratio));
|
|
7047
|
+
const output = new Float32Array(outputLength);
|
|
7048
|
+
for (let i = 0;i < outputLength; i++) {
|
|
7049
|
+
const inputIndex = i * ratio;
|
|
7050
|
+
const before = Math.floor(inputIndex);
|
|
7051
|
+
const after = Math.min(before + 1, input.length - 1);
|
|
7052
|
+
const weight = inputIndex - before;
|
|
7053
|
+
output[i] = input[before] * (1 - weight) + input[after] * weight;
|
|
7054
|
+
}
|
|
7055
|
+
return output;
|
|
7056
|
+
}
|
|
7057
|
+
function floatToPcm16(samples) {
|
|
7058
|
+
const buffer = new ArrayBuffer(samples.length * 2);
|
|
7059
|
+
const view = new DataView(buffer);
|
|
7060
|
+
for (let i = 0;i < samples.length; i++) {
|
|
7061
|
+
const sample = Math.max(-1, Math.min(1, samples[i]));
|
|
7062
|
+
view.setInt16(i * 2, sample < 0 ? sample * 32768 : sample * 32767, true);
|
|
7063
|
+
}
|
|
7064
|
+
return buffer;
|
|
7065
|
+
}
|
|
7066
|
+
function appendBuffer(a, b) {
|
|
7067
|
+
const next = new Uint8Array(a.byteLength + b.byteLength);
|
|
7068
|
+
next.set(a, 0);
|
|
7069
|
+
next.set(b, a.byteLength);
|
|
7070
|
+
return next;
|
|
7071
|
+
}
|
|
7072
|
+
function useVoiceInput({
|
|
7073
|
+
onTranscript,
|
|
7074
|
+
onError,
|
|
7075
|
+
lang = "en-US"
|
|
7076
|
+
} = {}) {
|
|
7077
|
+
const [isListening, setIsListening] = useState6(false);
|
|
7078
|
+
const [isTranscribing, setIsTranscribing] = useState6(false);
|
|
7079
|
+
const [analyser, setAnalyser] = useState6(null);
|
|
7080
|
+
const [error, setError] = useState6(null);
|
|
7081
|
+
const streamRef = useRef9(null);
|
|
7082
|
+
const audioContextRef = useRef9(null);
|
|
7083
|
+
const processorRef = useRef9(null);
|
|
7084
|
+
const sourceRef = useRef9(null);
|
|
7085
|
+
const socketRef = useRef9(null);
|
|
7086
|
+
const frameBufferRef = useRef9(new Uint8Array(0));
|
|
7087
|
+
const stoppingRef = useRef9(false);
|
|
7088
|
+
const sessionIdRef = useRef9(null);
|
|
7089
|
+
const onTranscriptRef = useRef9(onTranscript);
|
|
7090
|
+
const onErrorRef = useRef9(onError);
|
|
7091
|
+
useEffect19(() => {
|
|
7092
|
+
onTranscriptRef.current = onTranscript;
|
|
7093
|
+
onErrorRef.current = onError;
|
|
7094
|
+
}, [onTranscript, onError]);
|
|
7095
|
+
const isSupported = typeof window !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!getAudioContextConstructor() && typeof WebSocket !== "undefined";
|
|
7096
|
+
const emitError = useCallback12((message) => {
|
|
7097
|
+
setError(message);
|
|
7098
|
+
onErrorRef.current?.(message);
|
|
7099
|
+
}, []);
|
|
7100
|
+
const cleanupAudio = useCallback12(() => {
|
|
7101
|
+
if (processorRef.current) {
|
|
7102
|
+
processorRef.current.onaudioprocess = null;
|
|
7103
|
+
processorRef.current.disconnect();
|
|
7104
|
+
processorRef.current = null;
|
|
7105
|
+
}
|
|
7106
|
+
if (sourceRef.current) {
|
|
7107
|
+
sourceRef.current.disconnect();
|
|
7108
|
+
sourceRef.current = null;
|
|
7109
|
+
}
|
|
7110
|
+
if (streamRef.current) {
|
|
7111
|
+
for (const track of streamRef.current.getTracks())
|
|
7112
|
+
track.stop();
|
|
7113
|
+
streamRef.current = null;
|
|
7114
|
+
}
|
|
7115
|
+
if (audioContextRef.current && audioContextRef.current.state !== "closed") {
|
|
7116
|
+
audioContextRef.current.close().catch(() => {});
|
|
7117
|
+
}
|
|
7118
|
+
audioContextRef.current = null;
|
|
7119
|
+
frameBufferRef.current = new Uint8Array(0);
|
|
7120
|
+
setAnalyser(null);
|
|
7121
|
+
}, []);
|
|
7122
|
+
const cleanup = useCallback12(() => {
|
|
7123
|
+
cleanupAudio();
|
|
7124
|
+
if (socketRef.current) {
|
|
7125
|
+
const socket = socketRef.current;
|
|
7126
|
+
socket.onopen = null;
|
|
7127
|
+
socket.onmessage = null;
|
|
7128
|
+
socket.onerror = null;
|
|
7129
|
+
socket.onclose = null;
|
|
7130
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
7131
|
+
socket.close(1000, "Dictation cleanup");
|
|
7132
|
+
}
|
|
7133
|
+
socketRef.current = null;
|
|
7134
|
+
}
|
|
7135
|
+
sessionIdRef.current = null;
|
|
7136
|
+
stoppingRef.current = false;
|
|
7137
|
+
setIsListening(false);
|
|
7138
|
+
setIsTranscribing(false);
|
|
7139
|
+
}, [cleanupAudio]);
|
|
7140
|
+
const flushFrameBuffer = useCallback12((force = false) => {
|
|
7141
|
+
const socket = socketRef.current;
|
|
7142
|
+
if (!socket || socket.readyState !== WebSocket.OPEN)
|
|
7143
|
+
return;
|
|
7144
|
+
let buffer = frameBufferRef.current;
|
|
7145
|
+
while (buffer.byteLength >= PCM_FRAME_BYTES) {
|
|
7146
|
+
const frame = buffer.slice(0, PCM_FRAME_BYTES);
|
|
7147
|
+
socket.send(frame);
|
|
7148
|
+
buffer = buffer.slice(PCM_FRAME_BYTES);
|
|
7149
|
+
}
|
|
7150
|
+
if (force && buffer.byteLength > 0) {
|
|
7151
|
+
socket.send(buffer);
|
|
7152
|
+
buffer = new Uint8Array(0);
|
|
7153
|
+
}
|
|
7154
|
+
frameBufferRef.current = buffer;
|
|
7155
|
+
}, []);
|
|
7156
|
+
const handleAudioProcess = useCallback12((event) => {
|
|
7157
|
+
const audioContext = audioContextRef.current;
|
|
7158
|
+
const socket = socketRef.current;
|
|
7159
|
+
if (!audioContext || !socket || socket.readyState !== WebSocket.OPEN || stoppingRef.current) {
|
|
7160
|
+
return;
|
|
7161
|
+
}
|
|
7162
|
+
const input = event.inputBuffer.getChannelData(0);
|
|
7163
|
+
const resampled = resampleLinear(input, audioContext.sampleRate, TARGET_SAMPLE_RATE);
|
|
7164
|
+
frameBufferRef.current = appendBuffer(frameBufferRef.current, new Uint8Array(floatToPcm16(resampled)));
|
|
7165
|
+
flushFrameBuffer(false);
|
|
7166
|
+
}, [flushFrameBuffer]);
|
|
7167
|
+
const stop = useCallback12(() => {
|
|
7168
|
+
stoppingRef.current = true;
|
|
7169
|
+
flushFrameBuffer(true);
|
|
7170
|
+
cleanupAudio();
|
|
7171
|
+
const socket = socketRef.current;
|
|
7172
|
+
if (socket?.readyState === WebSocket.OPEN) {
|
|
7173
|
+
setIsListening(false);
|
|
7174
|
+
setIsTranscribing(true);
|
|
7175
|
+
socket.send(JSON.stringify({ type: "stop" }));
|
|
7176
|
+
} else {
|
|
7177
|
+
setIsListening(false);
|
|
7178
|
+
setIsTranscribing(false);
|
|
7179
|
+
}
|
|
7180
|
+
}, [cleanupAudio, flushFrameBuffer]);
|
|
7181
|
+
const start = useCallback12(async () => {
|
|
7182
|
+
if (!isSupported) {
|
|
7183
|
+
emitError("Voice input is not supported in this browser");
|
|
7184
|
+
return;
|
|
7185
|
+
}
|
|
7186
|
+
cleanup();
|
|
7187
|
+
setError(null);
|
|
7188
|
+
setIsTranscribing(false);
|
|
7189
|
+
stoppingRef.current = false;
|
|
7190
|
+
try {
|
|
7191
|
+
const status = await apiClient.getDictationStatus();
|
|
7192
|
+
const model = status.models.find((item) => item.id === status.defaultModel);
|
|
7193
|
+
if (!model?.installed) {
|
|
7194
|
+
emitError("Install a local dictation model from Settings before recording.");
|
|
7195
|
+
return;
|
|
7196
|
+
}
|
|
7197
|
+
const session = await apiClient.createDictationSession({
|
|
7198
|
+
model: status.defaultModel,
|
|
7199
|
+
language: toLanguageCode(lang)
|
|
7200
|
+
});
|
|
7201
|
+
if (!session.modelInstalled) {
|
|
7202
|
+
emitError("Install a local dictation model from Settings before recording.");
|
|
7203
|
+
return;
|
|
7204
|
+
}
|
|
7205
|
+
const socket = new WebSocket(session.wsUrl);
|
|
7206
|
+
socket.binaryType = "arraybuffer";
|
|
7207
|
+
socketRef.current = socket;
|
|
7208
|
+
sessionIdRef.current = session.id;
|
|
7209
|
+
await new Promise((resolve, reject) => {
|
|
7210
|
+
const timeout = window.setTimeout(() => {
|
|
7211
|
+
reject(new Error("Timed out connecting to local dictation"));
|
|
7212
|
+
}, 5000);
|
|
7213
|
+
socket.onopen = () => {
|
|
7214
|
+
window.clearTimeout(timeout);
|
|
7215
|
+
socket.send(JSON.stringify({
|
|
7216
|
+
type: "start",
|
|
7217
|
+
model: session.model,
|
|
7218
|
+
language: toLanguageCode(lang),
|
|
7219
|
+
format: {
|
|
7220
|
+
encoding: "pcm_s16le",
|
|
7221
|
+
sampleRate: TARGET_SAMPLE_RATE,
|
|
7222
|
+
channels: 1
|
|
7223
|
+
},
|
|
7224
|
+
partialResults: false
|
|
7225
|
+
}));
|
|
7226
|
+
resolve();
|
|
7227
|
+
};
|
|
7228
|
+
socket.onerror = () => {
|
|
7229
|
+
window.clearTimeout(timeout);
|
|
7230
|
+
reject(new Error("Could not connect to local dictation"));
|
|
7231
|
+
};
|
|
7232
|
+
});
|
|
7233
|
+
socket.onmessage = (event) => {
|
|
7234
|
+
if (typeof event.data !== "string")
|
|
7235
|
+
return;
|
|
7236
|
+
const payload = parseServerEvent(event.data);
|
|
7237
|
+
if (!payload)
|
|
7238
|
+
return;
|
|
7239
|
+
if (payload.type === "final") {
|
|
7240
|
+
onTranscriptRef.current?.(payload.text.trim(), true);
|
|
7241
|
+
cleanup();
|
|
7242
|
+
return;
|
|
7243
|
+
}
|
|
7244
|
+
if (payload.type === "error") {
|
|
7245
|
+
emitError(payload.message);
|
|
7246
|
+
cleanup();
|
|
7247
|
+
}
|
|
7248
|
+
};
|
|
7249
|
+
socket.onclose = () => {
|
|
7250
|
+
if (!stoppingRef.current)
|
|
7251
|
+
setIsListening(false);
|
|
7252
|
+
setIsTranscribing(false);
|
|
7253
|
+
};
|
|
7254
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
7255
|
+
audio: {
|
|
7256
|
+
echoCancellation: true,
|
|
7257
|
+
noiseSuppression: true,
|
|
7258
|
+
autoGainControl: true
|
|
7259
|
+
}
|
|
7260
|
+
});
|
|
7261
|
+
streamRef.current = stream;
|
|
7262
|
+
const AudioContextCtor = getAudioContextConstructor();
|
|
7263
|
+
if (!AudioContextCtor)
|
|
7264
|
+
throw new Error("AudioContext is unavailable");
|
|
7265
|
+
const audioContext = new AudioContextCtor;
|
|
7266
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
7267
|
+
const analyserNode = audioContext.createAnalyser();
|
|
7268
|
+
analyserNode.fftSize = 256;
|
|
7269
|
+
analyserNode.smoothingTimeConstant = 0.55;
|
|
7270
|
+
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
7271
|
+
processor.onaudioprocess = handleAudioProcess;
|
|
7272
|
+
source.connect(analyserNode);
|
|
7273
|
+
source.connect(processor);
|
|
7274
|
+
processor.connect(audioContext.destination);
|
|
7275
|
+
audioContextRef.current = audioContext;
|
|
7276
|
+
sourceRef.current = source;
|
|
7277
|
+
processorRef.current = processor;
|
|
7278
|
+
setAnalyser(analyserNode);
|
|
7279
|
+
setIsListening(true);
|
|
7280
|
+
} catch (err) {
|
|
7281
|
+
const name = err instanceof Error ? err.name : "";
|
|
7282
|
+
const msg = name === "NotAllowedError" ? "Microphone permission denied" : err instanceof Error ? err.message : "Could not start voice input";
|
|
7283
|
+
emitError(msg);
|
|
7284
|
+
cleanup();
|
|
7285
|
+
}
|
|
7286
|
+
}, [cleanup, emitError, handleAudioProcess, isSupported, lang]);
|
|
7287
|
+
useEffect19(() => cleanup, [cleanup]);
|
|
7288
|
+
return {
|
|
7289
|
+
isListening,
|
|
7290
|
+
isTranscribing,
|
|
7291
|
+
isSupported,
|
|
7292
|
+
analyser,
|
|
7293
|
+
error,
|
|
7294
|
+
start,
|
|
7295
|
+
stop
|
|
7296
|
+
};
|
|
7297
|
+
}
|
|
7298
|
+
// src/hooks/useDictationModels.ts
|
|
7299
|
+
import { useCallback as useCallback13, useEffect as useEffect20, useRef as useRef10, useState as useState7 } from "react";
|
|
7300
|
+
import { useMutation as useMutation10, useQuery as useQuery14, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
|
|
7301
|
+
var DICTATION_STATUS_QUERY_KEY = ["dictation", "status"];
|
|
7302
|
+
function mergeModelState(current, model) {
|
|
7303
|
+
if (!current)
|
|
7304
|
+
return current;
|
|
7305
|
+
return {
|
|
7306
|
+
...current,
|
|
7307
|
+
models: current.models.map((item) => item.id === model.id ? model : item)
|
|
7308
|
+
};
|
|
7309
|
+
}
|
|
7310
|
+
function parseInstallEvent(raw) {
|
|
7311
|
+
try {
|
|
7312
|
+
const value = JSON.parse(raw);
|
|
7313
|
+
if (value.type !== "model" || !value.model)
|
|
7314
|
+
return null;
|
|
7315
|
+
return value;
|
|
7316
|
+
} catch {
|
|
7317
|
+
return null;
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
function useDictationModels() {
|
|
7321
|
+
const queryClient = useQueryClient13();
|
|
7322
|
+
const eventSourceRef = useRef10(null);
|
|
7323
|
+
const [activeInstallModelId, setActiveInstallModelId] = useState7(null);
|
|
7324
|
+
const [installProgress, setInstallProgress] = useState7(null);
|
|
7325
|
+
const [installStreamError, setInstallStreamError] = useState7(null);
|
|
7326
|
+
const statusQuery = useQuery14({
|
|
7327
|
+
queryKey: DICTATION_STATUS_QUERY_KEY,
|
|
7328
|
+
queryFn: () => apiClient.getDictationStatus(),
|
|
7329
|
+
refetchInterval: (query) => query.state.data?.models.some((model) => model.installing) ? 1000 : 30000
|
|
7330
|
+
});
|
|
7331
|
+
const closeInstallStream = useCallback13(() => {
|
|
7332
|
+
if (eventSourceRef.current) {
|
|
7333
|
+
eventSourceRef.current.close();
|
|
7334
|
+
eventSourceRef.current = null;
|
|
7335
|
+
}
|
|
7336
|
+
}, []);
|
|
7337
|
+
const openInstallStream = useCallback13((modelId) => {
|
|
7338
|
+
if (typeof EventSource === "undefined")
|
|
7339
|
+
return;
|
|
7340
|
+
closeInstallStream();
|
|
7341
|
+
setActiveInstallModelId(modelId);
|
|
7342
|
+
setInstallStreamError(null);
|
|
7343
|
+
const eventSource = new EventSource(apiClient.getDictationModelInstallEventsUrl(modelId));
|
|
7344
|
+
eventSourceRef.current = eventSource;
|
|
7345
|
+
eventSource.onmessage = (event) => {
|
|
7346
|
+
const payload = parseInstallEvent(event.data);
|
|
7347
|
+
if (!payload)
|
|
7348
|
+
return;
|
|
7349
|
+
setInstallProgress(payload.model);
|
|
7350
|
+
queryClient.setQueryData(DICTATION_STATUS_QUERY_KEY, (current) => mergeModelState(current, payload.model));
|
|
7351
|
+
if (!payload.model.installing) {
|
|
7352
|
+
closeInstallStream();
|
|
7353
|
+
setActiveInstallModelId(null);
|
|
7354
|
+
queryClient.invalidateQueries({
|
|
7355
|
+
queryKey: DICTATION_STATUS_QUERY_KEY
|
|
7356
|
+
});
|
|
7357
|
+
}
|
|
7358
|
+
};
|
|
7359
|
+
eventSource.onerror = () => {
|
|
7360
|
+
setInstallStreamError("Lost dictation model install progress stream");
|
|
7361
|
+
closeInstallStream();
|
|
7362
|
+
setActiveInstallModelId(null);
|
|
7363
|
+
queryClient.invalidateQueries({
|
|
7364
|
+
queryKey: DICTATION_STATUS_QUERY_KEY
|
|
7365
|
+
});
|
|
7366
|
+
};
|
|
7367
|
+
}, [closeInstallStream, queryClient]);
|
|
7368
|
+
useEffect20(() => closeInstallStream, [closeInstallStream]);
|
|
7369
|
+
const installMutation = useMutation10({
|
|
7370
|
+
mutationFn: (input) => apiClient.installDictationModel(input),
|
|
7371
|
+
onSuccess: (data) => {
|
|
7372
|
+
setInstallProgress(data.model);
|
|
7373
|
+
queryClient.setQueryData(DICTATION_STATUS_QUERY_KEY, (current) => mergeModelState(current, data.model));
|
|
7374
|
+
if (data.model.installing) {
|
|
7375
|
+
openInstallStream(data.model.id);
|
|
7376
|
+
} else {
|
|
7377
|
+
closeInstallStream();
|
|
7378
|
+
setActiveInstallModelId(null);
|
|
7379
|
+
queryClient.invalidateQueries({
|
|
7380
|
+
queryKey: DICTATION_STATUS_QUERY_KEY
|
|
7381
|
+
});
|
|
7382
|
+
}
|
|
7383
|
+
}
|
|
7384
|
+
});
|
|
7385
|
+
const removeMutation = useMutation10({
|
|
7386
|
+
mutationFn: (model) => apiClient.removeDictationModel(model),
|
|
7387
|
+
onSuccess: (data) => {
|
|
7388
|
+
queryClient.setQueryData(DICTATION_STATUS_QUERY_KEY, (current) => mergeModelState(current, data.model));
|
|
7389
|
+
queryClient.invalidateQueries({
|
|
7390
|
+
queryKey: DICTATION_STATUS_QUERY_KEY
|
|
7391
|
+
});
|
|
7392
|
+
}
|
|
7393
|
+
});
|
|
7394
|
+
const installModel = useCallback13((model, options = {}) => installMutation.mutateAsync({ model, force: options.force }), [installMutation]);
|
|
7395
|
+
const removeModel = useCallback13((model) => removeMutation.mutateAsync(model), [removeMutation]);
|
|
7396
|
+
return {
|
|
7397
|
+
statusQuery,
|
|
7398
|
+
status: statusQuery.data,
|
|
7399
|
+
models: statusQuery.data?.models ?? [],
|
|
7400
|
+
defaultModel: statusQuery.data?.defaultModel ?? null,
|
|
7401
|
+
isAvailable: statusQuery.data?.available ?? false,
|
|
7402
|
+
activeInstallModelId,
|
|
7403
|
+
installProgress,
|
|
7404
|
+
installStreamError,
|
|
7405
|
+
installModel,
|
|
7406
|
+
removeModel,
|
|
7407
|
+
installMutation,
|
|
7408
|
+
removeMutation,
|
|
7409
|
+
openInstallStream,
|
|
7410
|
+
closeInstallStream
|
|
7411
|
+
};
|
|
7412
|
+
}
|
|
6912
7413
|
export {
|
|
6913
7414
|
useWorkingDirectory,
|
|
7415
|
+
useVoiceInput,
|
|
6914
7416
|
useUpdateSkillsConfig,
|
|
6915
7417
|
useUpdateSession,
|
|
6916
7418
|
useUpdateDefaults,
|
|
@@ -6931,7 +7433,6 @@ export {
|
|
|
6931
7433
|
useSkillFileContent,
|
|
6932
7434
|
useSkillDetail,
|
|
6933
7435
|
useShareStatus,
|
|
6934
|
-
useSetuPayments,
|
|
6935
7436
|
useSessionsInfinite,
|
|
6936
7437
|
useSessions,
|
|
6937
7438
|
useSessionStream,
|
|
@@ -6949,6 +7450,7 @@ export {
|
|
|
6949
7450
|
useProviderUsage,
|
|
6950
7451
|
usePreferences,
|
|
6951
7452
|
useParentSession,
|
|
7453
|
+
useOttoRouterPayments,
|
|
6952
7454
|
useOttoRouterBalance,
|
|
6953
7455
|
useModels,
|
|
6954
7456
|
useMessages,
|
|
@@ -6973,6 +7475,7 @@ export {
|
|
|
6973
7475
|
useFileTree,
|
|
6974
7476
|
useFileContent,
|
|
6975
7477
|
useExportToSession,
|
|
7478
|
+
useDictationModels,
|
|
6976
7479
|
useDeleteSession,
|
|
6977
7480
|
useDeleteResearchSession,
|
|
6978
7481
|
useDeleteFiles,
|
|
@@ -6997,4 +7500,4 @@ export {
|
|
|
6997
7500
|
normalizeQueueState
|
|
6998
7501
|
};
|
|
6999
7502
|
|
|
7000
|
-
//# debugId=
|
|
7503
|
+
//# debugId=6E5B9890DD6BFD3564756E2164756E21
|