diff --git a/src/routes/StatusRoutes.js b/src/routes/StatusRoutes.js
index b619759..6ac6d37 100644
--- a/src/routes/StatusRoutes.js
+++ b/src/routes/StatusRoutes.js
@@ -193,6 +193,24 @@ class StatusRoutes {
res.status(400).json({ error: "Invalid count", message: "settingFailed" });
}
});
+
+ app.put("/api/settings/session-error-threshold", isAuthenticated, (req, res) => {
+ const { threshold } = req.body;
+ const newThreshold = parseInt(threshold, 10);
+
+ if (Number.isFinite(newThreshold) && newThreshold >= 0) {
+ this.config.sessionErrorThreshold = newThreshold;
+ this.serverSystem.sessionRegistry.sessionErrorThreshold = newThreshold;
+ this.logger.info(`[WebUI] Session error threshold updated to: ${newThreshold}`);
+ res.status(200).json({
+ message: "settingUpdateSuccess",
+ setting: "sessionErrorThreshold",
+ value: newThreshold,
+ });
+ } else {
+ res.status(400).json({ error: "Invalid threshold", message: "settingFailed" });
+ }
+ });
}
_getSystemSummary() {
diff --git a/ui/app/components/MetricCard.vue b/ui/app/components/MetricCard.vue
new file mode 100644
index 0000000..c6afa2a
--- /dev/null
+++ b/ui/app/components/MetricCard.vue
@@ -0,0 +1,358 @@
+
+
+
+
+
+
+
+
+
+
+ {{ formattedValue }}
+
+
+ {{ subtitle }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/app/components/SideNavBar.vue b/ui/app/components/SideNavBar.vue
new file mode 100644
index 0000000..54cdc55
--- /dev/null
+++ b/ui/app/components/SideNavBar.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
diff --git a/ui/app/components/TopAppBar.vue b/ui/app/components/TopAppBar.vue
new file mode 100644
index 0000000..f87619b
--- /dev/null
+++ b/ui/app/components/TopAppBar.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
diff --git a/ui/app/composables/useI18nHelper.js b/ui/app/composables/useI18nHelper.js
new file mode 100644
index 0000000..becd51a
--- /dev/null
+++ b/ui/app/composables/useI18nHelper.js
@@ -0,0 +1,42 @@
+/**
+ * File: ui/app/composables/useI18nHelper.js
+ * Description: Composable for internationalization helper functions
+ *
+ * Author: iBUHUB
+ */
+
+import { ref } from 'vue';
+import I18n from '../utils/i18n';
+
+export function useI18nHelper() {
+ const langVersion = ref(I18n.state.version);
+
+ // i18n helper with reactivity trigger
+ const t = (key, options) => {
+ langVersion.value; // trigger reactivity
+ return I18n.t(key, options);
+ };
+
+ // Get current language
+ const getCurrentLang = () => I18n.getLang();
+
+ // Toggle language
+ const toggleLanguage = () => {
+ I18n.toggleLang();
+ langVersion.value++;
+ };
+
+ // Handle language change from select
+ const handleLanguageChange = lang => {
+ I18n.setLang(lang);
+ langVersion.value++;
+ };
+
+ return {
+ getCurrentLang,
+ handleLanguageChange,
+ langVersion,
+ t,
+ toggleLanguage,
+ };
+}
diff --git a/ui/app/composables/useLogs.js b/ui/app/composables/useLogs.js
new file mode 100644
index 0000000..e4e75a8
--- /dev/null
+++ b/ui/app/composables/useLogs.js
@@ -0,0 +1,62 @@
+/**
+ * File: ui/app/composables/useLogs.js
+ * Description: Composable for managing logs display and operations
+ *
+ * Author: iBUHUB
+ */
+
+import { ElMessage } from 'element-plus';
+import I18n from '../utils/i18n';
+import escapeHtml from '../utils/escapeHtml';
+
+export function useLogs() {
+ // i18n helper
+ const t = (key, options) => I18n.t(key, options);
+
+ // Helper function to highlight log levels
+ const highlightLogLevel = (content, level, color) =>
+ content.replace(
+ new RegExp(`(^|\\r?\\n)(\\[${level}\\])(?=\\s)`, 'g'),
+ `$1$2`
+ );
+
+ // Computed for formatted logs (receives raw logs string)
+ const getFormattedLogs = logs => {
+ let safeLogs = escapeHtml(logs || t('loading'));
+
+ safeLogs = highlightLogLevel(safeLogs, 'DEBUG', '#3498db');
+ safeLogs = highlightLogLevel(safeLogs, 'WARN', '#f39c12');
+ safeLogs = highlightLogLevel(safeLogs, 'ERROR', '#e74c3c');
+
+ return safeLogs;
+ };
+
+ // Clear logs view (updates state directly)
+ const clearLogsView = state => {
+ state.logs = '';
+ };
+
+ // Download logs
+ const downloadLogs = logs => {
+ if (!logs) {
+ ElMessage.warning(t('noLogsAvailable'));
+ return;
+ }
+
+ const blob = new Blob([logs], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `canvastoapi-logs-${new Date().toISOString().slice(0, 10)}.txt`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ };
+
+ return {
+ clearLogsView,
+ downloadLogs,
+ getFormattedLogs,
+ };
+}
diff --git a/ui/app/composables/useSessions.js b/ui/app/composables/useSessions.js
new file mode 100644
index 0000000..a27eaeb
--- /dev/null
+++ b/ui/app/composables/useSessions.js
@@ -0,0 +1,86 @@
+/**
+ * File: ui/app/composables/useSessions.js
+ * Description: Composable for managing browser sessions state and operations
+ *
+ * Author: iBUHUB
+ */
+
+import { computed, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import I18n from '../utils/i18n';
+
+export function useSessions() {
+ const sessions = ref([]);
+
+ // i18n helper
+ const t = (key, options) => I18n.t(key, options);
+
+ // Computed
+ const activeSessionCount = computed(() => sessions.value.filter(s => !s.disabledAt).length);
+ const disabledSessionCount = computed(() => sessions.value.filter(s => s.disabledAt).length);
+
+ // Helper functions
+ function formatTime(timestamp) {
+ if (!timestamp) return '-';
+ const date = new Date(timestamp);
+ return date.toLocaleString();
+ }
+
+ function getSessionStatusClass(session) {
+ if (session.disabledAt) {
+ return 'status-disabled';
+ }
+ return 'status-online';
+ }
+
+ function getSessionStatusText(session) {
+ if (session.disabledAt) {
+ return t('disabledLabel');
+ }
+ return t('onlineLabel');
+ }
+
+ // Reset session health
+ const handleResetHealth = async (session, onSuccess) => {
+ try {
+ await ElMessageBox.confirm(t('sessionResetConfirm', { session: session.connectionId }), t('warningTitle'), {
+ cancelButtonText: t('cancel'),
+ confirmButtonText: t('ok'),
+ type: 'warning',
+ });
+
+ const response = await fetch(`/api/sessions/${session.sessionId}/reset-health`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ method: 'PUT',
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}));
+ throw new Error(errorData.message || 'Reset failed');
+ }
+
+ ElMessage.success(t('sessionResetSuccess', { session: session.connectionId }));
+
+ // Call onSuccess callback to refresh session list
+ if (onSuccess) {
+ await onSuccess();
+ }
+ } catch (error) {
+ if (error !== 'cancel') {
+ ElMessage.error(error.message || t('unknownError'));
+ }
+ }
+ };
+
+ return {
+ activeSessionCount,
+ disabledSessionCount,
+ formatTime,
+ getSessionStatusClass,
+ getSessionStatusText,
+ handleResetHealth,
+ sessions,
+ };
+}
diff --git a/ui/app/composables/useSettings.js b/ui/app/composables/useSettings.js
new file mode 100644
index 0000000..0fdaa05
--- /dev/null
+++ b/ui/app/composables/useSettings.js
@@ -0,0 +1,267 @@
+/**
+ * File: ui/app/composables/useSettings.js
+ * Description: Composable for managing application settings state and API updates
+ *
+ * Author: iBUHUB
+ */
+
+import { reactive } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import I18n from '../utils/i18n';
+
+export function useSettings() {
+ const state = reactive({
+ debugMode: false,
+ forceThinking: false,
+ forceUrlContext: false,
+ forceWebSearch: false,
+ logMaxCount: 100,
+ selectionStrategy: 'round',
+ sessionErrorThreshold: 3,
+ streamingMode: 'fake',
+ });
+
+ const loading = reactive({
+ debugMode: false,
+ forceThinking: false,
+ forceUrlContext: false,
+ forceWebSearch: false,
+ logMaxCount: false,
+ selectionStrategy: false,
+ sessionErrorThreshold: false,
+ streamingMode: false,
+ });
+
+ // i18n helper
+ const t = (key, options) => I18n.t(key, options);
+
+ // Settings update methods
+ const handleDebugModeChange = async value => {
+ loading.debugMode = true;
+ try {
+ const response = await fetch('/api/settings/debug-mode', {
+ headers: { 'Content-Type': 'application/json' },
+ method: 'PUT',
+ });
+
+ const data = await response.json();
+ if (response.ok) {
+ ElMessage.success(t('settingUpdateSuccess', { setting: t('logLevel'), value: data.value }));
+ } else {
+ ElMessage.error(t('settingFailed', { message: data.message || 'Unknown error' }));
+ state.debugMode = !value;
+ }
+ } catch (error) {
+ ElMessage.error(t('settingFailed', { message: error.message }));
+ state.debugMode = !value;
+ } finally {
+ loading.debugMode = false;
+ }
+ };
+
+ const updateLogMaxCount = async value => {
+ loading.logMaxCount = true;
+ try {
+ const response = await fetch('/api/settings/log-max-count', {
+ body: JSON.stringify({ count: value }),
+ headers: { 'Content-Type': 'application/json' },
+ method: 'PUT',
+ });
+
+ const data = await response.json();
+ if (response.ok) {
+ ElMessage.success(t('settingUpdateSuccess', { setting: t('logMaxCount'), value }));
+ } else {
+ ElMessage.error(t('settingFailed', { message: data.message || 'Unknown error' }));
+ }
+ } catch (error) {
+ ElMessage.error(t('settingFailed', { message: error.message }));
+ } finally {
+ loading.logMaxCount = false;
+ }
+ };
+
+ const updateStreamingMode = async value => {
+ loading.streamingMode = true;
+ try {
+ // Show confirmation for "real" mode
+ if (value === 'real') {
+ try {
+ await ElMessageBox.confirm(t('streamingModeEnableConfirm'), t('warningTitle'), {
+ cancelButtonText: t('cancel'),
+ confirmButtonText: t('ok'),
+ type: 'warning',
+ });
+ } catch {
+ // User cancelled, revert to "fake"
+ state.streamingMode = 'fake';
+ loading.streamingMode = false;
+ return;
+ }
+ }
+
+ const response = await fetch('/api/settings/streaming-mode', {
+ body: JSON.stringify({ mode: value }),
+ headers: { 'Content-Type': 'application/json' },
+ method: 'PUT',
+ });
+
+ const data = await response.json();
+ if (response.ok) {
+ ElMessage.success(t('settingUpdateSuccess', { setting: t('streamingMode'), value }));
+ } else {
+ ElMessage.error(t('settingFailed', { message: data.message || 'Unknown error' }));
+ state.streamingMode = value === 'real' ? 'fake' : 'real';
+ }
+ } catch (error) {
+ ElMessage.error(t('settingFailed', { message: error.message }));
+ state.streamingMode = value === 'real' ? 'fake' : 'real';
+ } finally {
+ loading.streamingMode = false;
+ }
+ };
+
+ const updateSelectionStrategy = async value => {
+ loading.selectionStrategy = true;
+ try {
+ const response = await fetch('/api/settings/selection-strategy', {
+ body: JSON.stringify({ strategy: value }),
+ headers: { 'Content-Type': 'application/json' },
+ method: 'PUT',
+ });
+
+ const data = await response.json();
+ if (response.ok) {
+ ElMessage.success(t('settingUpdateSuccess', { setting: t('selectionStrategyLabel'), value }));
+ } else {
+ ElMessage.error(t('settingFailed', { message: data.message || 'Unknown error' }));
+ state.selectionStrategy = value === 'round' ? 'random' : 'round';
+ }
+ } catch (error) {
+ ElMessage.error(t('settingFailed', { message: error.message }));
+ state.selectionStrategy = value === 'round' ? 'random' : 'round';
+ } finally {
+ loading.selectionStrategy = false;
+ }
+ };
+
+ const updateSessionErrorThreshold = async value => {
+ loading.sessionErrorThreshold = true;
+ try {
+ const response = await fetch('/api/settings/session-error-threshold', {
+ body: JSON.stringify({ threshold: value }),
+ headers: { 'Content-Type': 'application/json' },
+ method: 'PUT',
+ });
+
+ const data = await response.json();
+ if (response.ok) {
+ ElMessage.success(t('settingUpdateSuccess', { setting: t('sessionErrorThreshold'), value }));
+ } else {
+ ElMessage.error(t('settingFailed', { message: data.message || 'Unknown error' }));
+ }
+ } catch (error) {
+ ElMessage.error(t('settingFailed', { message: error.message }));
+ } finally {
+ loading.sessionErrorThreshold = false;
+ }
+ };
+
+ const updateForceThinking = async value => {
+ loading.forceThinking = true;
+ try {
+ const response = await fetch('/api/settings/force-thinking', {
+ headers: { 'Content-Type': 'application/json' },
+ method: 'PUT',
+ });
+
+ const data = await response.json();
+ if (response.ok) {
+ ElMessage.success(t('settingUpdateSuccess', { setting: t('forceThinking'), value: data.value }));
+ } else {
+ ElMessage.error(t('settingFailed', { message: data.message || 'Unknown error' }));
+ state.forceThinking = !value;
+ }
+ } catch (error) {
+ ElMessage.error(t('settingFailed', { message: error.message }));
+ state.forceThinking = !value;
+ } finally {
+ loading.forceThinking = false;
+ }
+ };
+
+ const updateForceWebSearch = async value => {
+ loading.forceWebSearch = true;
+ try {
+ // Show confirmation when enabling
+ if (value) {
+ try {
+ await ElMessageBox.confirm(t('forceWebSearchEnableConfirm'), t('warningTitle'), {
+ cancelButtonText: t('cancel'),
+ confirmButtonText: t('ok'),
+ type: 'warning',
+ });
+ } catch {
+ // User cancelled, revert
+ state.forceWebSearch = false;
+ loading.forceWebSearch = false;
+ return;
+ }
+ }
+
+ const response = await fetch('/api/settings/force-web-search', {
+ headers: { 'Content-Type': 'application/json' },
+ method: 'PUT',
+ });
+
+ const data = await response.json();
+ if (response.ok) {
+ ElMessage.success(t('settingUpdateSuccess', { setting: t('forceWebSearch'), value: data.value }));
+ } else {
+ ElMessage.error(t('settingFailed', { message: data.message || 'Unknown error' }));
+ state.forceWebSearch = !value;
+ }
+ } catch (error) {
+ ElMessage.error(t('settingFailed', { message: error.message }));
+ state.forceWebSearch = !value;
+ } finally {
+ loading.forceWebSearch = false;
+ }
+ };
+
+ const updateForceUrlContext = async value => {
+ loading.forceUrlContext = true;
+ try {
+ const response = await fetch('/api/settings/force-url-context', {
+ headers: { 'Content-Type': 'application/json' },
+ method: 'PUT',
+ });
+
+ const data = await response.json();
+ if (response.ok) {
+ ElMessage.success(t('settingUpdateSuccess', { setting: t('forceUrlContext'), value: data.value }));
+ } else {
+ ElMessage.error(t('settingFailed', { message: data.message || 'Unknown error' }));
+ state.forceUrlContext = !value;
+ }
+ } catch (error) {
+ ElMessage.error(t('settingFailed', { message: error.message }));
+ state.forceUrlContext = !value;
+ } finally {
+ loading.forceUrlContext = false;
+ }
+ };
+
+ return {
+ handleDebugModeChange,
+ loading,
+ state,
+ updateForceThinking,
+ updateForceUrlContext,
+ updateForceWebSearch,
+ updateLogMaxCount,
+ updateSelectionStrategy,
+ updateSessionErrorThreshold,
+ updateStreamingMode,
+ };
+}
diff --git a/ui/app/composables/useStatusPolling.js b/ui/app/composables/useStatusPolling.js
new file mode 100644
index 0000000..c76f534
--- /dev/null
+++ b/ui/app/composables/useStatusPolling.js
@@ -0,0 +1,117 @@
+/**
+ * File: ui/app/composables/useStatusPolling.js
+ * Description: Composable for managing status polling and data synchronization
+ *
+ * Author: iBUHUB
+ */
+
+import { computed, nextTick, reactive, ref } from 'vue';
+
+export function useStatusPolling(settingsState, versionState, sessionsRef, getActiveTab) {
+ const updateTimer = ref(null);
+
+ const state = reactive({
+ browserWsPath: '/ws',
+ logCount: 0,
+ logs: '',
+ logScrollTop: 0,
+ serviceConnected: false,
+ sharePageUrl: '',
+ });
+
+ // Computed for WebSocket endpoint text
+ const browserWsEndpointText = computed(() => {
+ if (typeof window === 'undefined') {
+ return state.browserWsPath || '/ws';
+ }
+
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ return `${protocol}//${window.location.host}${state.browserWsPath || '/ws'}`;
+ });
+
+ // Apply status payload to all states
+ const applyStatusPayload = payload => {
+ const status = payload?.status || {};
+
+ // Update settings state
+ settingsState.sessionErrorThreshold = Number.isFinite(Number(status.sessionErrorThreshold))
+ ? Number(status.sessionErrorThreshold)
+ : 3;
+ settingsState.debugMode = Boolean(status.debugMode);
+ settingsState.forceThinking = Boolean(status.forceThinking);
+ settingsState.forceUrlContext = Boolean(status.forceUrlContext);
+ settingsState.forceWebSearch = Boolean(status.forceWebSearch);
+ settingsState.logMaxCount = Number(status.logMaxCount || 100);
+ settingsState.selectionStrategy = status.selectionStrategy || 'round';
+ settingsState.streamingMode = status.streamingMode || 'fake';
+
+ // Update local state
+ state.logCount = Number(payload?.logCount || 0);
+ state.logs = payload?.logs || '';
+ state.serviceConnected = Boolean(status.serviceConnected);
+ state.browserWsPath = status.browserWsPath || '/ws';
+ state.sharePageUrl = status.sharePageUrl || '';
+
+ // Update sessions
+ sessionsRef.value = Array.isArray(status.browserSessions) ? status.browserSessions : [];
+ };
+
+ // Fetch status data
+ const fetchStatus = async () => {
+ try {
+ const logContainer = getActiveTab() === 'logs' ? document.getElementById('log-container') : null;
+ if (logContainer) {
+ state.logScrollTop = logContainer.scrollTop;
+ }
+
+ const response = await fetch('/api/status');
+ if (response.redirected) {
+ window.location.href = response.url;
+ return;
+ }
+ if (response.status === 401) {
+ window.location.href = '/login';
+ return;
+ }
+ if (!response.ok) throw new Error(`status ${response.status}`);
+
+ const data = await response.json();
+ applyStatusPayload(data);
+
+ if (getActiveTab() === 'logs') {
+ nextTick(() => {
+ const updatedLogContainer = document.getElementById('log-container');
+ if (updatedLogContainer) {
+ updatedLogContainer.scrollTop = state.logScrollTop || 0;
+ }
+ });
+ }
+ } catch (error) {
+ state.serviceConnected = false;
+ }
+ };
+
+ // Start polling
+ const startPolling = (interval = 5000) => {
+ fetchStatus();
+ updateTimer.value = setInterval(fetchStatus, interval);
+ };
+
+ // Stop polling
+ const stopPolling = () => {
+ if (updateTimer.value) {
+ clearInterval(updateTimer.value);
+ updateTimer.value = null;
+ }
+ };
+
+ return {
+ applyStatusPayload,
+ browserWsEndpointText,
+ fetchStatus,
+ startPolling,
+ state,
+ stopPolling,
+ updateTimer,
+ };
+}
diff --git a/ui/app/composables/useVersionInfo.js b/ui/app/composables/useVersionInfo.js
new file mode 100644
index 0000000..90b9076
--- /dev/null
+++ b/ui/app/composables/useVersionInfo.js
@@ -0,0 +1,71 @@
+/**
+ * File: ui/app/composables/useVersionInfo.js
+ * Description: Composable for managing version information and update checking
+ *
+ * Author: iBUHUB
+ */
+
+import { computed, reactive } from 'vue';
+
+export function useVersionInfo() {
+ const state = reactive({
+ currentVersion: '',
+ hasUpdate: false,
+ latestVersion: '',
+ releaseUrl: '',
+ });
+
+ // Computed
+ const appVersion = computed(() => {
+ // eslint-disable-next-line no-undef
+ const version = state.currentVersion || (typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'dev');
+ if (/^\d/.test(version)) {
+ return `v${version}`;
+ }
+ if (version.startsWith('preview')) {
+ return version.charAt(0).toUpperCase() + version.slice(1);
+ }
+ return version;
+ });
+
+ const latestVersionFormatted = computed(() => {
+ /* eslint-disable no-undef */
+ const version =
+ state.latestVersion ||
+ state.currentVersion ||
+ (typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'dev');
+ /* eslint-enable no-undef */
+ if (/^\d/.test(version)) {
+ return `v${version}`;
+ }
+ if (version.startsWith('preview')) {
+ return version.charAt(0).toUpperCase() + version.slice(1);
+ }
+ return version;
+ });
+
+ // Fetch version info
+ const fetchVersionInfo = async () => {
+ try {
+ const response = await fetch('/api/version/check');
+ if (!response.ok) {
+ return;
+ }
+
+ const data = await response.json();
+ state.currentVersion = data.current || '';
+ state.hasUpdate = Boolean(data.hasUpdate);
+ state.latestVersion = data.latest || '';
+ state.releaseUrl = data.releaseUrl || '';
+ } catch {
+ state.hasUpdate = false;
+ }
+ };
+
+ return {
+ appVersion,
+ fetchVersionInfo,
+ latestVersionFormatted,
+ state,
+ };
+}
diff --git a/ui/app/index.html b/ui/app/index.html
index a7eeaf4..19617f7 100644
--- a/ui/app/index.html
+++ b/ui/app/index.html
@@ -9,8 +9,20 @@
- Gemini Canvas Proxy
+ CanvasToAPI - Technical Dashboard
+
+
+
+
+
+
diff --git a/ui/app/pages/StatusPage.vue b/ui/app/pages/StatusPage.vue
index 2910764..ef83f68 100644
--- a/ui/app/pages/StatusPage.vue
+++ b/ui/app/pages/StatusPage.vue
@@ -1,1530 +1,478 @@
-
-
-
-
-
-
-
-
-
-
- {{ t("serviceStatus") }}
-
-
-
-
-
-
- {{ t("serviceConnection") }} {{
- serviceConnectedText
- }}
-
-
-
-
- {{ t("browserConnection") }} {{
- browserConnectedText
- }}
-
-
-
-
- {{ t("wsEndpointLabel") }}
-
- {{ browserWsEndpointText }}
-
-
-
-
-
-
-
-
-
-
-
- {{ tf("sessionPoolHeading", "Session Pool") }}
-
-
-
-
-
- {{ tf("activeSessionsLabel", "Active Sessions") }} {{ activeSessionCount }}
-
-
-
-
- {{ tf("totalSessionsLabel", "Total Sessions") }} {{ sessions.length }}
-
-
-
-
-
- {{ tf("selectionStrategyLabel", "Selection Strategy") }}
-
- {{ selectionStrategyText }}
-
-
-
-
-
- {{ tf("errorThresholdLabel", "Error Threshold") }}
-
- {{ sessionErrorThresholdText }}
-
-
-
-
-
-
- {{ t("proxySettingsStatus") }}
-
-
-
-
-
-
- {{ t("streamingMode") }}
- ({{ t("onlyAppliesWhenStreamingEnabled") }})
-
- {{ streamingModeText }}
-
-
-
-
-
- {{ t("forceThinking") }}
-
- {{ state.forceThinking ? t("enabled") : t("disabled") }}
-
-
-
-
-
- {{ t("forceWebSearch") }}
-
- {{ state.forceWebSearch ? t("enabled") : t("disabled") }}
-
-
-
-
-
- {{ t("forceUrlContext") }}
-
- {{ state.forceUrlContext ? t("enabled") : t("disabled") }}
-
-
-
-
-
- {{ t("maxRetries") }}
- ({{ t("onlyAppliesWhenFakeStreaming") }})
-
- {{ state.maxRetries }}
-
-
-
+