@@ -4,24 +4,37 @@ import { prepareSystemPrompt } from "./prompts";
44import { getLLMProvider } from './llmProviders' ;
55import useAppStore from '../store/store' ;
66import { extractErrorMessage } from '../utils/helpers/errorUtils' ;
7+ import { loadAndDecryptApiKey } from '../utils/secureKeyStorage' ;
8+
9+ export const loadConfigFromLocalStorage = async ( ) => {
10+ const { setAIConfig, setKeyProtectionLevel } = useAppStore . getState ( ) ;
711
8- export const loadConfigFromLocalStorage = ( ) => {
9- const setAIConfig = useAppStore . getState ( ) . setAIConfig ;
10-
1112 const savedProvider = localStorage . getItem ( 'aiProvider' ) ;
1213 const savedModel = localStorage . getItem ( 'aiModel' ) ;
13- const savedApiKey = localStorage . getItem ( 'aiApiKey' ) ;
1414 const savedCustomEndpoint = localStorage . getItem ( 'aiCustomEndpoint' ) ;
1515 const savedMaxTokens = localStorage . getItem ( 'aiResMaxTokens' ) ;
16-
16+
1717 const savedIncludeTemplateMark = localStorage . getItem ( 'aiIncludeTemplateMark' ) === 'true' ;
1818 const savedIncludeConcertoModel = localStorage . getItem ( 'aiIncludeConcertoModel' ) === 'true' ;
1919 const savedIncludeData = localStorage . getItem ( 'aiIncludeData' ) === 'true' ;
20-
20+
2121 const savedShowFullPrompt = localStorage . getItem ( 'aiShowFullPrompt' ) === 'true' ;
2222 const savedEnableCodeSelectionMenu = localStorage . getItem ( 'aiEnableCodeSelectionMenu' ) !== 'false' ;
2323 const savedEnableInlineSuggestions = localStorage . getItem ( 'aiEnableInlineSuggestions' ) !== 'false' ;
2424
25+ // Load API key via secure decryption (WebAuthn) or legacy fallback
26+ let savedApiKey : string | null = null ;
27+ try {
28+ const result = await loadAndDecryptApiKey ( ) ;
29+ if ( result ) {
30+ savedApiKey = result . apiKey ;
31+ setKeyProtectionLevel ( result . protectionLevel ) ;
32+ }
33+ } catch {
34+ // WebAuthn decryption failed or was cancelled — key stays null
35+ console . warn ( 'Could not load encrypted API key. User will need to re-enter it.' ) ;
36+ }
37+
2538 if ( savedProvider && savedModel && savedApiKey ) {
2639 const config : AIConfig = {
2740 provider : savedProvider ,
@@ -34,28 +47,28 @@ export const loadConfigFromLocalStorage = () => {
3447 enableCodeSelectionMenu : savedEnableCodeSelectionMenu ,
3548 enableInlineSuggestions : savedEnableInlineSuggestions ,
3649 } ;
37-
50+
3851 if ( savedCustomEndpoint && savedProvider === 'openai-compatible' ) {
3952 config . customEndpoint = savedCustomEndpoint ;
4053 }
41-
54+
4255 if ( savedMaxTokens ) {
4356 config . maxTokens = parseInt ( savedMaxTokens ) ;
4457 }
45-
58+
4659 setAIConfig ( config ) ;
4760 }
4861} ;
4962
5063export const resetChat = ( ) => {
5164 const { setChatAbortController, setChatState } = useAppStore . getState ( ) ;
52-
65+
5366 const chatAbortController = useAppStore . getState ( ) . chatAbortController ;
5467 if ( chatAbortController ) {
5568 chatAbortController . abort ( ) ;
5669 setChatAbortController ( null ) ;
5770 }
58-
71+
5972 setChatState ( {
6073 messages : [ ] ,
6174 isLoading : false ,
@@ -70,54 +83,54 @@ export const stopMessage = () => {
7083 chatAbortController . abort ( ) ;
7184 setChatAbortController ( null ) ;
7285 }
73-
86+
7487 updateChatState ( { isLoading : false } ) ;
75-
88+
7689 const { chatState, setChatState } = useAppStore . getState ( ) ;
7790 const updatedMessages = [ ...chatState . messages ] ;
7891 const lastMessage = updatedMessages [ updatedMessages . length - 1 ] ;
79-
92+
8093 if ( lastMessage && lastMessage . role === 'assistant' && ! lastMessage . content . endsWith ( '[Stopped]' ) ) {
8194 updatedMessages [ updatedMessages . length - 1 ] = {
8295 ...lastMessage ,
8396 content : lastMessage . content + ' [Stopped]' ,
8497 } ;
8598 }
86-
99+
87100 setChatState ( {
88101 ...chatState ,
89102 messages : updatedMessages ,
90103 } ) ;
91104} ;
92105
93106export const sendMessage = async (
94- userInput : string ,
95- promptPreset : string | null ,
107+ userInput : string ,
108+ promptPreset : string | null ,
96109 editorsContent : editorsContent ,
97110 addToChat = true ,
98111 editorType ?: 'markdown' | 'concerto' | 'json' ,
99112 onChunk ?: ( chunk : string ) => void ,
100113 onError ?: ( error : Error ) => void ,
101114 onComplete ?: ( ) => void
102115) => {
103- const {
104- aiConfig,
105- chatState,
106- setChatState,
116+ const {
117+ aiConfig,
118+ chatState,
119+ setChatState,
107120 updateChatState,
108121 chatAbortController,
109122 setChatAbortController
110123 } = useAppStore . getState ( ) ;
111-
124+
112125 if ( chatAbortController ) {
113126 chatAbortController . abort ( ) ;
114127 setChatAbortController ( null ) ;
115128 }
116-
129+
117130 const newAbortController = new AbortController ( ) ;
118131 setChatAbortController ( newAbortController ) ;
119132 const signal = newAbortController . signal ;
120-
133+
121134 if ( ! aiConfig ) {
122135 const error = new Error ( 'Please configure AI settings first' ) ;
123136 if ( onError ) {
@@ -129,7 +142,7 @@ export const sendMessage = async (
129142 content : `[ERROR] ${ error . message } ` ,
130143 timestamp : new Date ( ) ,
131144 } ;
132-
145+
133146 const updatedChatState = {
134147 messages : [ ...chatState . messages , errorMessage ] ,
135148 isLoading : false ,
@@ -139,7 +152,7 @@ export const sendMessage = async (
139152 }
140153 return ;
141154 }
142-
155+
143156 let systemPrompt = "" ;
144157 if ( promptPreset === "textToTemplate" ) {
145158 systemPrompt = prepareSystemPrompt . textToTemplate ( editorsContent , aiConfig ) ;
@@ -173,9 +186,9 @@ export const sendMessage = async (
173186 content : ' ' ,
174187 timestamp : new Date ( ) ,
175188 } ;
176-
189+
177190 let updatedChatState ;
178-
191+
179192 if ( addToChat ) {
180193 updatedChatState = {
181194 messages : [ ...chatState . messages , systemMessage , userMessage , assistantMessage ] ,
@@ -188,11 +201,11 @@ export const sendMessage = async (
188201 try {
189202 const Provider = getLLMProvider ( aiConfig ) ;
190203 let fullResponse = '' ;
191-
192- const messagesForAPI = addToChat ?
193- [ ...( updatedChatState ?. messages . slice ( 0 , - 1 ) || [ ] ) ] :
204+
205+ const messagesForAPI = addToChat ?
206+ [ ...( updatedChatState ?. messages . slice ( 0 , - 1 ) || [ ] ) ] :
194207 [ systemMessage , userMessage ] ;
195-
208+
196209 const abortPromise = new Promise ( ( _ , reject ) => {
197210 signal . addEventListener ( 'abort' , ( ) => reject ( new Error ( 'Request aborted' ) ) ) ;
198211 } ) ;
@@ -203,13 +216,13 @@ export const sendMessage = async (
203216 messagesForAPI ,
204217 ( chunk ) => {
205218 if ( signal . aborted ) return ;
206-
219+
207220 fullResponse += chunk ;
208-
221+
209222 if ( onChunk ) {
210223 onChunk ( chunk ) ;
211224 }
212-
225+
213226 if ( addToChat ) {
214227 const { chatState, setChatState } = useAppStore . getState ( ) ;
215228 const updatedMessages = [ ...chatState . messages ] ;
@@ -218,7 +231,7 @@ export const sendMessage = async (
218231 ...updatedMessages [ updatedMessages . length - 1 ] ,
219232 content : fullResponse ,
220233 } ;
221-
234+
222235 setChatState ( {
223236 ...chatState ,
224237 messages : updatedMessages ,
@@ -227,7 +240,7 @@ export const sendMessage = async (
227240 } ,
228241 ( error ) => {
229242 if ( ! signal . aborted ) {
230-
243+
231244 if ( onError ) {
232245 onError ( error ) ;
233246 } else if ( addToChat ) {
@@ -236,12 +249,12 @@ export const sendMessage = async (
236249 const updatedMessages = [ ...chatState . messages ] ;
237250 const simpleErrorMessage = extractErrorMessage ( error ) ;
238251 const errorMessage = `[ERROR] ${ simpleErrorMessage } ` ;
239-
252+
240253 updatedMessages [ updatedMessages . length - 1 ] = {
241254 ...updatedMessages [ updatedMessages . length - 1 ] ,
242255 content : errorMessage ,
243256 } ;
244-
257+
245258 setChatState ( {
246259 ...chatState ,
247260 messages : updatedMessages ,
@@ -256,7 +269,7 @@ export const sendMessage = async (
256269 if ( onComplete ) {
257270 onComplete ( ) ;
258271 }
259-
272+
260273 if ( addToChat ) {
261274 updateChatState ( { isLoading : false } ) ;
262275 }
@@ -282,12 +295,12 @@ export const sendMessage = async (
282295 const { chatState, setChatState } = useAppStore . getState ( ) ;
283296 const updatedMessages = [ ...chatState . messages ] ;
284297 const formattedError = `[ERROR] ${ simpleErrorMessage } ` ;
285-
298+
286299 updatedMessages [ updatedMessages . length - 1 ] = {
287300 ...updatedMessages [ updatedMessages . length - 1 ] ,
288301 content : formattedError ,
289302 } ;
290-
303+
291304 setChatState ( {
292305 ...chatState ,
293306 messages : updatedMessages ,
@@ -299,4 +312,4 @@ export const sendMessage = async (
299312 }
300313} ;
301314
302- loadConfigFromLocalStorage ( ) ;
315+ loadConfigFromLocalStorage ( ) . catch ( console . warn ) ;
0 commit comments