mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Fix issue #5315: OpenHands UI should pop up a dialog box to warn before closing when agent is running
This commit is contained in:
parent
3ac57a61a7
commit
3007bb1809
204
frontend/__tests__/hooks/use-close-warning.test.tsx
Normal file
204
frontend/__tests__/hooks/use-close-warning.test.tsx
Normal file
@ -0,0 +1,204 @@
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { useCloseWarning } from "#/hooks/use-close-warning";
|
||||
import { Provider } from "react-redux";
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { rootReducer } from "#/store";
|
||||
import { UserPrefsProvider } from "#/context/user-prefs-context";
|
||||
import AgentState from "#/types/agent-state";
|
||||
import { test, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
const mockSettings = {
|
||||
CLOSE_WARNING: "while_working",
|
||||
LLM_MODEL: "",
|
||||
LLM_BASE_URL: "",
|
||||
AGENT: "",
|
||||
LANGUAGE: "en",
|
||||
LLM_API_KEY: "",
|
||||
CONFIRMATION_MODE: false,
|
||||
SECURITY_ANALYZER: "",
|
||||
};
|
||||
|
||||
const createStore = (agentState = AgentState.FINISHED) => configureStore({
|
||||
reducer: rootReducer,
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: agentState,
|
||||
},
|
||||
fileState: {
|
||||
files: [],
|
||||
selectedPath: null,
|
||||
modifiedFiles: {},
|
||||
},
|
||||
initalQuery: {
|
||||
selectedRepository: null,
|
||||
},
|
||||
browser: {
|
||||
url: "",
|
||||
isLoading: false,
|
||||
error: null,
|
||||
},
|
||||
chat: {
|
||||
messages: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
},
|
||||
code: {
|
||||
content: "",
|
||||
isLoading: false,
|
||||
error: null,
|
||||
},
|
||||
cmd: {
|
||||
output: "",
|
||||
isLoading: false,
|
||||
error: null,
|
||||
},
|
||||
jupyter: {
|
||||
cells: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
},
|
||||
securityAnalyzer: {
|
||||
isLoading: false,
|
||||
error: null,
|
||||
},
|
||||
status: {
|
||||
isLoading: false,
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const mockStore = createStore();
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<Provider store={mockStore}>
|
||||
<UserPrefsProvider initialSettings={mockSettings}>
|
||||
{children}
|
||||
</UserPrefsProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
window.addEventListener = vi.fn();
|
||||
window.removeEventListener = vi.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should add and remove event listener", () => {
|
||||
const { unmount } = renderHook(() => useCloseWarning(), { wrapper });
|
||||
|
||||
expect(window.addEventListener).toHaveBeenCalledWith(
|
||||
"beforeunload",
|
||||
expect.any(Function)
|
||||
);
|
||||
|
||||
unmount();
|
||||
|
||||
expect(window.removeEventListener).toHaveBeenCalledWith(
|
||||
"beforeunload",
|
||||
expect.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
test("should prevent unload when agent is working and setting is while_working", () => {
|
||||
const store = createStore(AgentState.RUNNING);
|
||||
|
||||
const customWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<Provider store={store}>
|
||||
<UserPrefsProvider initialSettings={mockSettings}>
|
||||
{children}
|
||||
</UserPrefsProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
renderHook(() => useCloseWarning(), { wrapper: customWrapper });
|
||||
|
||||
const addEventListenerMock = window.addEventListener as unknown as vi.Mock;
|
||||
const handler = addEventListenerMock.mock.calls[0][1];
|
||||
|
||||
const mockEvent = {
|
||||
preventDefault: vi.fn(),
|
||||
returnValue: "",
|
||||
};
|
||||
|
||||
handler(mockEvent);
|
||||
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should not prevent unload when agent is not working and setting is while_working", () => {
|
||||
renderHook(() => useCloseWarning(), { wrapper });
|
||||
|
||||
const addEventListenerMock = window.addEventListener as unknown as vi.Mock;
|
||||
const handler = addEventListenerMock.mock.calls[0][1];
|
||||
|
||||
const mockEvent = {
|
||||
preventDefault: vi.fn(),
|
||||
returnValue: "",
|
||||
};
|
||||
|
||||
handler(mockEvent);
|
||||
|
||||
expect(mockEvent.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should always prevent unload when setting is always", () => {
|
||||
const customSettings = {
|
||||
...mockSettings,
|
||||
CLOSE_WARNING: "always",
|
||||
};
|
||||
|
||||
const customWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<Provider store={mockStore}>
|
||||
<UserPrefsProvider initialSettings={customSettings}>
|
||||
{children}
|
||||
</UserPrefsProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
renderHook(() => useCloseWarning(), { wrapper: customWrapper });
|
||||
|
||||
const addEventListenerMock = window.addEventListener as unknown as vi.Mock;
|
||||
const handler = addEventListenerMock.mock.calls[0][1];
|
||||
|
||||
const mockEvent = {
|
||||
preventDefault: vi.fn(),
|
||||
returnValue: "",
|
||||
};
|
||||
|
||||
handler(mockEvent);
|
||||
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should never prevent unload when setting is never", () => {
|
||||
const customSettings = {
|
||||
...mockSettings,
|
||||
CLOSE_WARNING: "never",
|
||||
};
|
||||
|
||||
const customWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<Provider store={mockStore}>
|
||||
<UserPrefsProvider initialSettings={customSettings}>
|
||||
{children}
|
||||
</UserPrefsProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
renderHook(() => useCloseWarning(), { wrapper: customWrapper });
|
||||
|
||||
const addEventListenerMock = window.addEventListener as unknown as vi.Mock;
|
||||
const handler = addEventListenerMock.mock.calls[0][1];
|
||||
|
||||
const mockEvent = {
|
||||
preventDefault: vi.fn(),
|
||||
returnValue: "",
|
||||
};
|
||||
|
||||
handler(mockEvent);
|
||||
|
||||
expect(mockEvent.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
@ -351,6 +351,39 @@ export function SettingsForm({
|
||||
>
|
||||
{t(I18nKey.SETTINGS_FORM$ENABLE_CONFIRMATION_MODE_LABEL)}
|
||||
</Switch>
|
||||
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="close-warning"
|
||||
className="font-[500] text-[#A3A3A3] text-xs"
|
||||
>
|
||||
{t("CONFIGURATION$CLOSE_WARNING_LABEL")}
|
||||
</label>
|
||||
<Autocomplete
|
||||
isDisabled={disabled}
|
||||
isRequired
|
||||
id="close-warning"
|
||||
name="close-warning"
|
||||
aria-label="Close Warning"
|
||||
defaultSelectedKey={settings.CLOSE_WARNING}
|
||||
inputProps={{
|
||||
classNames: {
|
||||
inputWrapper:
|
||||
"bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AutocompleteItem key="always" value="always">
|
||||
Always
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem key="while_working" value="while_working">
|
||||
While agent is working
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem key="never" value="never">
|
||||
Never
|
||||
</AutocompleteItem>
|
||||
</Autocomplete>
|
||||
</fieldset>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
53
frontend/src/components/modals/close-warning-dialog.tsx
Normal file
53
frontend/src/components/modals/close-warning-dialog.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Dialog } from "@headlessui/react";
|
||||
|
||||
interface CloseWarningDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export function CloseWarningDialog({
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}: CloseWarningDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={onClose}
|
||||
className="relative z-50"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<Dialog.Panel className="mx-auto max-w-sm rounded bg-white p-6">
|
||||
<Dialog.Title className="text-lg font-medium leading-6 text-gray-900">
|
||||
{t("CLOSE_WARNING$DIALOG_TITLE")}
|
||||
</Dialog.Title>
|
||||
<Dialog.Description className="mt-2 text-sm text-gray-500">
|
||||
{t("CLOSE_WARNING$DIALOG_MESSAGE")}
|
||||
</Dialog.Description>
|
||||
|
||||
<div className="mt-4 flex justify-end space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-gray-100 px-4 py-2 text-sm font-medium text-gray-900 hover:bg-gray-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-500 focus-visible:ring-offset-2"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t("CLOSE_WARNING$DIALOG_CANCEL")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{t("CLOSE_WARNING$DIALOG_CONFIRM")}
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
32
frontend/src/hooks/use-close-warning.ts
Normal file
32
frontend/src/hooks/use-close-warning.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '#/store';
|
||||
import AgentState from '#/types/agent-state';
|
||||
import { useUserPrefs } from '#/context/user-prefs-context';
|
||||
|
||||
export function useCloseWarning() {
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
const { settings } = useUserPrefs();
|
||||
const agentState = useSelector((state: RootState) => state.agent.curAgentState);
|
||||
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
const isWorking = agentState === AgentState.RUNNING || agentState === AgentState.STARTING;
|
||||
|
||||
if (settings.CLOSE_WARNING === 'always' ||
|
||||
(settings.CLOSE_WARNING === 'while_working' && isWorking)) {
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
}, [agentState, settings.CLOSE_WARNING]);
|
||||
|
||||
return {
|
||||
showWarning,
|
||||
setShowWarning,
|
||||
};
|
||||
}
|
||||
86
frontend/src/i18n/new_translations.json
Normal file
86
frontend/src/i18n/new_translations.json
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"CONFIGURATION$CLOSE_WARNING_LABEL": {
|
||||
"en": "Close warning",
|
||||
"zh-CN": "关闭警告",
|
||||
"de": "Schließen-Warnung",
|
||||
"ko-KR": "닫기 경고",
|
||||
"no": "Lukkevarsel",
|
||||
"zh-TW": "關閉警告",
|
||||
"it": "Avviso di chiusura",
|
||||
"pt": "Aviso de fechamento",
|
||||
"es": "Aviso de cierre",
|
||||
"ar": "تحذير الإغلاق",
|
||||
"fr": "Avertissement de fermeture",
|
||||
"tr": "Kapatma uyarısı"
|
||||
},
|
||||
"CONFIGURATION$CLOSE_WARNING_PLACEHOLDER": {
|
||||
"en": "When to show close warning",
|
||||
"zh-CN": "何时显示关闭警告",
|
||||
"de": "Wann die Schließen-Warnung anzeigen",
|
||||
"ko-KR": "닫기 경고를 표시할 시기",
|
||||
"no": "Når skal lukkevarsel vises",
|
||||
"zh-TW": "何時顯示關閉警告",
|
||||
"it": "Quando mostrare l'avviso di chiusura",
|
||||
"pt": "Quando mostrar aviso de fechamento",
|
||||
"es": "Cuándo mostrar aviso de cierre",
|
||||
"ar": "متى يظهر تحذير الإغلاق",
|
||||
"fr": "Quand afficher l'avertissement de fermeture",
|
||||
"tr": "Kapatma uyarısı ne zaman gösterilsin"
|
||||
},
|
||||
"CLOSE_WARNING$DIALOG_TITLE": {
|
||||
"en": "Close OpenHands?",
|
||||
"zh-CN": "关闭 OpenHands?",
|
||||
"de": "OpenHands schließen?",
|
||||
"ko-KR": "OpenHands를 닫으시겠습니까?",
|
||||
"no": "Lukk OpenHands?",
|
||||
"zh-TW": "關閉 OpenHands?",
|
||||
"it": "Chiudere OpenHands?",
|
||||
"pt": "Fechar OpenHands?",
|
||||
"es": "¿Cerrar OpenHands?",
|
||||
"ar": "إغلاق OpenHands؟",
|
||||
"fr": "Fermer OpenHands ?",
|
||||
"tr": "OpenHands kapatılsın mı?"
|
||||
},
|
||||
"CLOSE_WARNING$DIALOG_MESSAGE": {
|
||||
"en": "The agent is currently working. Closing the window will terminate its work and any progress will be lost.",
|
||||
"zh-CN": "智能体正在工作中。关闭窗口将终止其工作,所有进度都将丢失。",
|
||||
"de": "Der Agent arbeitet gerade. Wenn Sie das Fenster schließen, wird seine Arbeit beendet und der Fortschritt geht verloren.",
|
||||
"ko-KR": "에이전트가 현재 작업 중입니다. 창을 닫으면 작업이 종료되고 모든 진행 상황이 손실됩니다.",
|
||||
"no": "Agenten jobber for øyeblikket. Å lukke vinduet vil avslutte arbeidet og all fremgang vil gå tapt.",
|
||||
"zh-TW": "智能體正在工作中。關閉視窗將終止其工作,所有進度都將丟失。",
|
||||
"it": "L'agente sta attualmente lavorando. La chiusura della finestra terminerà il suo lavoro e qualsiasi progresso andrà perso.",
|
||||
"pt": "O agente está trabalhando no momento. Fechar a janela encerrará seu trabalho e todo o progresso será perdido.",
|
||||
"es": "El agente está trabajando actualmente. Cerrar la ventana terminará su trabajo y se perderá todo el progreso.",
|
||||
"ar": "الوكيل يعمل حاليا. سيؤدي إغلاق النافذة إلى إنهاء عمله وسيتم فقد أي تقدم.",
|
||||
"fr": "L'agent est en train de travailler. Fermer la fenêtre mettra fin à son travail et tout progrès sera perdu.",
|
||||
"tr": "Ajan şu anda çalışıyor. Pencereyi kapatmak işini sonlandıracak ve tüm ilerleme kaybedilecek."
|
||||
},
|
||||
"CLOSE_WARNING$DIALOG_CONFIRM": {
|
||||
"en": "Close anyway",
|
||||
"zh-CN": "仍然关闭",
|
||||
"de": "Trotzdem schließen",
|
||||
"ko-KR": "그래도 닫기",
|
||||
"no": "Lukk likevel",
|
||||
"zh-TW": "仍然關閉",
|
||||
"it": "Chiudi comunque",
|
||||
"pt": "Fechar mesmo assim",
|
||||
"es": "Cerrar de todos modos",
|
||||
"ar": "إغلاق على أي حال",
|
||||
"fr": "Fermer quand même",
|
||||
"tr": "Yine de kapat"
|
||||
},
|
||||
"CLOSE_WARNING$DIALOG_CANCEL": {
|
||||
"en": "Cancel",
|
||||
"zh-CN": "取消",
|
||||
"de": "Abbrechen",
|
||||
"ko-KR": "취소",
|
||||
"no": "Avbryt",
|
||||
"zh-TW": "取消",
|
||||
"it": "Annulla",
|
||||
"pt": "Cancelar",
|
||||
"es": "Cancelar",
|
||||
"ar": "إلغاء",
|
||||
"fr": "Annuler",
|
||||
"tr": "İptal"
|
||||
}
|
||||
}
|
||||
@ -2013,4 +2013,91 @@
|
||||
"en": "Download as .zip",
|
||||
"es": "Descargar como .zip"
|
||||
}
|
||||
|
||||
,
|
||||
|
||||
"CONFIGURATION$CLOSE_WARNING_LABEL": {
|
||||
"en": "Close warning",
|
||||
"zh-CN": "关闭警告",
|
||||
"de": "Schließen-Warnung",
|
||||
"ko-KR": "닫기 경고",
|
||||
"no": "Lukkevarsel",
|
||||
"zh-TW": "關閉警告",
|
||||
"it": "Avviso di chiusura",
|
||||
"pt": "Aviso de fechamento",
|
||||
"es": "Aviso de cierre",
|
||||
"ar": "تحذير الإغلاق",
|
||||
"fr": "Avertissement de fermeture",
|
||||
"tr": "Kapatma uyarısı"
|
||||
},
|
||||
"CONFIGURATION$CLOSE_WARNING_PLACEHOLDER": {
|
||||
"en": "When to show close warning",
|
||||
"zh-CN": "何时显示关闭警告",
|
||||
"de": "Wann die Schließen-Warnung anzeigen",
|
||||
"ko-KR": "닫기 경고를 표시할 시기",
|
||||
"no": "Når skal lukkevarsel vises",
|
||||
"zh-TW": "何時顯示關閉警告",
|
||||
"it": "Quando mostrare l'avviso di chiusura",
|
||||
"pt": "Quando mostrar aviso de fechamento",
|
||||
"es": "Cuándo mostrar aviso de cierre",
|
||||
"ar": "متى يظهر تحذير الإغلاق",
|
||||
"fr": "Quand afficher l'avertissement de fermeture",
|
||||
"tr": "Kapatma uyarısı ne zaman gösterilsin"
|
||||
},
|
||||
"CLOSE_WARNING$DIALOG_TITLE": {
|
||||
"en": "Close OpenHands?",
|
||||
"zh-CN": "关闭 OpenHands?",
|
||||
"de": "OpenHands schließen?",
|
||||
"ko-KR": "OpenHands를 닫으시겠습니까?",
|
||||
"no": "Lukk OpenHands?",
|
||||
"zh-TW": "關閉 OpenHands?",
|
||||
"it": "Chiudere OpenHands?",
|
||||
"pt": "Fechar OpenHands?",
|
||||
"es": "¿Cerrar OpenHands?",
|
||||
"ar": "إغلاق OpenHands؟",
|
||||
"fr": "Fermer OpenHands ?",
|
||||
"tr": "OpenHands kapatılsın mı?"
|
||||
},
|
||||
"CLOSE_WARNING$DIALOG_MESSAGE": {
|
||||
"en": "The agent is currently working. Closing the window will terminate its work and any progress will be lost.",
|
||||
"zh-CN": "智能体正在工作中。关闭窗口将终止其工作,所有进度都将丢失。",
|
||||
"de": "Der Agent arbeitet gerade. Wenn Sie das Fenster schließen, wird seine Arbeit beendet und der Fortschritt geht verloren.",
|
||||
"ko-KR": "에이전트가 현재 작업 중입니다. 창을 닫으면 작업이 종료되고 모든 진행 상황이 손실됩니다.",
|
||||
"no": "Agenten jobber for øyeblikket. Å lukke vinduet vil avslutte arbeidet og all fremgang vil gå tapt.",
|
||||
"zh-TW": "智能體正在工作中。關閉視窗將終止其工作,所有進度都將丟失。",
|
||||
"it": "L'agente sta attualmente lavorando. La chiusura della finestra terminerà il suo lavoro e qualsiasi progresso andrà perso.",
|
||||
"pt": "O agente está trabalhando no momento. Fechar a janela encerrará seu trabalho e todo o progresso será perdido.",
|
||||
"es": "El agente está trabajando actualmente. Cerrar la ventana terminará su trabajo y se perderá todo el progreso.",
|
||||
"ar": "الوكيل يعمل حاليا. سيؤدي إغلاق النافذة إلى إنهاء عمله وسيتم فقد أي تقدم.",
|
||||
"fr": "L'agent est en train de travailler. Fermer la fenêtre mettra fin à son travail et tout progrès sera perdu.",
|
||||
"tr": "Ajan şu anda çalışıyor. Pencereyi kapatmak işini sonlandıracak ve tüm ilerleme kaybedilecek."
|
||||
},
|
||||
"CLOSE_WARNING$DIALOG_CONFIRM": {
|
||||
"en": "Close anyway",
|
||||
"zh-CN": "仍然关闭",
|
||||
"de": "Trotzdem schließen",
|
||||
"ko-KR": "그래도 닫기",
|
||||
"no": "Lukk likevel",
|
||||
"zh-TW": "仍然關閉",
|
||||
"it": "Chiudi comunque",
|
||||
"pt": "Fechar mesmo assim",
|
||||
"es": "Cerrar de todos modos",
|
||||
"ar": "إغلاق على أي حال",
|
||||
"fr": "Fermer quand même",
|
||||
"tr": "Yine de kapat"
|
||||
},
|
||||
"CLOSE_WARNING$DIALOG_CANCEL": {
|
||||
"en": "Cancel",
|
||||
"zh-CN": "取消",
|
||||
"de": "Abbrechen",
|
||||
"ko-KR": "취소",
|
||||
"no": "Avbryt",
|
||||
"zh-TW": "取消",
|
||||
"it": "Annulla",
|
||||
"pt": "Cancelar",
|
||||
"es": "Cancelar",
|
||||
"ar": "إلغاء",
|
||||
"fr": "Annuler",
|
||||
"tr": "İptal"
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,10 +21,13 @@ import { useLatestRepoCommit } from "#/hooks/query/use-latest-repo-commit";
|
||||
import { useAuth } from "#/context/auth-context";
|
||||
import { useUserPrefs } from "#/context/user-prefs-context";
|
||||
import { useConversationConfig } from "#/hooks/query/use-conversation-config";
|
||||
import { useCloseWarning } from "#/hooks/use-close-warning";
|
||||
import { CloseWarningDialog } from "#/components/modals/close-warning-dialog";
|
||||
|
||||
function App() {
|
||||
const { token, gitHubToken } = useAuth();
|
||||
const { settings } = useUserPrefs();
|
||||
const { showWarning, setShowWarning } = useCloseWarning();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
useConversationConfig();
|
||||
@ -113,6 +116,11 @@ function App() {
|
||||
onOpenChange={onSecurityModalOpenChange}
|
||||
securityAnalyzer={settings.SECURITY_ANALYZER}
|
||||
/>
|
||||
<CloseWarningDialog
|
||||
isOpen={showWarning}
|
||||
onClose={() => setShowWarning(false)}
|
||||
onConfirm={() => window.close()}
|
||||
/>
|
||||
</div>
|
||||
</EventHandler>
|
||||
</WsClientProvider>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export const LATEST_SETTINGS_VERSION = 3;
|
||||
export const LATEST_SETTINGS_VERSION = 4;
|
||||
|
||||
export type CloseWarningMode = 'always' | 'while_working' | 'never';
|
||||
|
||||
export type Settings = {
|
||||
LLM_MODEL: string;
|
||||
@ -8,6 +10,7 @@ export type Settings = {
|
||||
LLM_API_KEY: string;
|
||||
CONFIRMATION_MODE: boolean;
|
||||
SECURITY_ANALYZER: string;
|
||||
CLOSE_WARNING: CloseWarningMode;
|
||||
};
|
||||
|
||||
export const DEFAULT_SETTINGS: Settings = {
|
||||
@ -18,6 +21,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
LLM_API_KEY: "",
|
||||
CONFIRMATION_MODE: false,
|
||||
SECURITY_ANALYZER: "",
|
||||
CLOSE_WARNING: "while_working",
|
||||
};
|
||||
|
||||
const validKeys = Object.keys(DEFAULT_SETTINGS) as (keyof Settings)[];
|
||||
@ -53,6 +57,9 @@ export const maybeMigrateSettings = () => {
|
||||
if (currentVersion < 3) {
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
if (currentVersion < 4) {
|
||||
localStorage.setItem("CLOSE_WARNING", DEFAULT_SETTINGS.CLOSE_WARNING);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -71,6 +78,7 @@ export const getSettings = (): Settings => {
|
||||
const apiKey = localStorage.getItem("LLM_API_KEY");
|
||||
const confirmationMode = localStorage.getItem("CONFIRMATION_MODE") === "true";
|
||||
const securityAnalyzer = localStorage.getItem("SECURITY_ANALYZER");
|
||||
const closeWarning = localStorage.getItem("CLOSE_WARNING") as CloseWarningMode;
|
||||
|
||||
return {
|
||||
LLM_MODEL: model || DEFAULT_SETTINGS.LLM_MODEL,
|
||||
@ -80,6 +88,7 @@ export const getSettings = (): Settings => {
|
||||
LLM_API_KEY: apiKey || DEFAULT_SETTINGS.LLM_API_KEY,
|
||||
CONFIRMATION_MODE: confirmationMode || DEFAULT_SETTINGS.CONFIRMATION_MODE,
|
||||
SECURITY_ANALYZER: securityAnalyzer || DEFAULT_SETTINGS.SECURITY_ANALYZER,
|
||||
CLOSE_WARNING: closeWarning || DEFAULT_SETTINGS.CLOSE_WARNING,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user