Skip to content

Commit 66f0dd9

Browse files
committed
resolved merge conflicts
2 parents 7eec485 + 6c2442f commit 66f0dd9

File tree

16 files changed

+3155
-4579
lines changed

16 files changed

+3155
-4579
lines changed

index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@
3737
href="https://fonts.googleapis.com/css2?family=Rubik:wght@300..900&display=swap"
3838
rel="stylesheet"
3939
/>
40+
41+
<!-- Performance hints for AI provider APIs -->
42+
<link rel="dns-prefetch" href="https://api.openai.com" />
43+
<link rel="dns-prefetch" href="https://api.anthropic.com" />
44+
<link rel="dns-prefetch" href="https://generativelanguage.googleapis.com" />
45+
<link rel="dns-prefetch" href="https://api.mistral.ai" />
46+
<link rel="dns-prefetch" href="https://openrouter.ai" />
4047
</head>
4148
<body>
4249
<div id="root"></div>

package-lock.json

Lines changed: 1830 additions & 4144 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"@accordproject/markdown-common": "^0.17.2",
2323
"@accordproject/markdown-template": "^0.17.2",
2424
"@accordproject/markdown-transform": "^0.17.2",
25-
"@accordproject/template-engine": "^2.7.1",
25+
"@accordproject/template-engine": "^2.8.0",
2626
"@ant-design/icons": "^5.3.7",
2727
"@anthropic-ai/sdk": "^0.56.0",
2828
"@google/genai": "^1.8.0",
@@ -31,7 +31,7 @@
3131
"@types/styled-components": "^5.1.34",
3232
"antd": "^5.7.2",
3333
"core-js": "^3.37.1",
34-
"dompurify": "^3.3.1",
34+
"dompurify": "^3.3.2",
3535
"highlight.js": "^11.10.0",
3636
"html2pdf.js": "^0.14.0",
3737
"immer": "^10.1.1",
@@ -59,6 +59,7 @@
5959
"shepherd.js": "^13.0.3",
6060
"styled-components": "^6.1.12",
6161
"ts-debounce": "^4.0.0",
62+
"use-debounce": "^10.1.0",
6263
"uuid": "^11.1.0",
6364
"zustand": "^4.3.9"
6465
},

src/App.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import MainContainer from "./pages/MainContainer";
1111
import PlaygroundSidebar from "./components/PlaygroundSidebar";
1212
import "./styles/App.css";
1313
import AIConfigPopup from "./components/AIConfigPopup";
14-
import { loadConfigFromLocalStorage } from "./ai-assistant/chatRelay";
1514

1615
const { Content } = Layout;
1716

@@ -30,11 +29,6 @@ const App = () => {
3029
const [searchParams] = useSearchParams();
3130

3231

33-
const handleConfigSave = () => {
34-
loadConfigFromLocalStorage();
35-
setAIConfigOpen(false);
36-
};
37-
3832
useEffect(() => {
3933
const initializeApp = async () => {
4034
try {
@@ -138,7 +132,6 @@ const App = () => {
138132
<AIConfigPopup
139133
isOpen={isAIConfigOpen}
140134
onClose={() => setAIConfigOpen(false)}
141-
onSave={handleConfigSave}
142135
/>
143136
</>
144137
}
@@ -163,5 +156,5 @@ const Spinner = () => (
163156
/>
164157
</div>
165158
);
166-
159+
167160
export default App;

src/ai-assistant/chatRelay.ts

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,37 @@ import { prepareSystemPrompt } from "./prompts";
44
import { getLLMProvider } from './llmProviders';
55
import useAppStore from '../store/store';
66
import { 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

5063
export 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

93106
export 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

Comments
 (0)