Skip to content

Commit f97a28f

Browse files
necrodomejchris
andauthored
proxy for llm requests (#174)
* WIP * LLM instruction for callai prooxy * fix tests. * fix: update auth flow test to check for relative connectUrl path * fix: update titleGenerator to use environment variable for CALLAI endpoint * fix: remove X-VIBES-Token header from token requests * revert prompts * Simplify proxy configuration * chore: update package dependencies and lockfile to latest versions * fix: update API_BASE_URL to use new production domain * feat: add APP_HOST_BASE_URL env var and update app URL construction * style: format appUrl assignment with line break for better readability --------- Co-authored-by: J Chris Anderson <[email protected]>
1 parent 776d7d6 commit f97a28f

40 files changed

+2194
-5112
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111

1212
# VS Code settings
1313
.vscode/settings.json
14+
.claude/settings.local.json

__mocks__/useAuth.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ export const mockUseAuth = vi.fn().mockImplementation(() => defaultAuthenticated
4040
export const setMockAuthState = (state: Partial<AuthContextType>) => {
4141
mockUseAuth.mockImplementation(() => ({
4242
...defaultAuthenticatedState,
43-
needsLogin: false,
44-
setNeedsLogin: vi.fn(),
4543
...state,
4644
}));
4745
};

app/components/NeedsLoginModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useAuthPopup } from '../hooks/useAuthPopup';
66

77
/**
88
* A modal that appears when the user needs to login to get more credits
9-
* This listens for the 'needsLoginTriggered' event from useSimpleChat
9+
* This listens for the needsLogin state from AuthContext
1010
*/
1111
export function NeedsLoginModal() {
1212
const [isOpen, setIsOpen] = useState(false);

app/components/ResultPreview/IframeContent.tsx

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import React, { useEffect, useRef, useState } from 'react';
2-
import type { IframeFiles } from './ResultPreviewTypes';
31
import Editor from '@monaco-editor/react';
4-
import { useApiKey } from '~/hooks/useApiKey';
2+
import React, { useEffect, useRef } from 'react';
3+
import type { IframeFiles } from './ResultPreviewTypes';
4+
// API key import removed - proxy handles authentication
5+
import { CALLAI_ENDPOINT } from '../../config/env';
6+
import { normalizeComponentExports } from '../../utils/normalizeComponentExports';
7+
import { DatabaseListView } from './DataView';
58
import { setupMonacoEditor } from './setupMonacoEditor';
69
import { transformImports } from './transformImports';
7-
import { DatabaseListView } from './DataView';
8-
import { normalizeComponentExports } from '../../utils/normalizeComponentExports';
910

1011
// Import the iframe template using Vite's ?raw import option
1112
import iframeTemplateRaw from './templates/iframe-template.html?raw';
@@ -27,8 +28,7 @@ const IframeContent: React.FC<IframeContentProps> = ({
2728
isDarkMode,
2829
sessionId,
2930
}) => {
30-
const { ensureApiKey } = useApiKey();
31-
const [apiKey, setApiKey] = useState('');
31+
// API key no longer needed - proxy handles authentication
3232
const iframeRef = useRef<HTMLIFrameElement>(null);
3333
// Theme state is now received from parent via props
3434
const contentLoadedRef = useRef(false);
@@ -79,19 +79,11 @@ const IframeContent: React.FC<IframeContentProps> = ({
7979

8080
// This effect is now managed at the ResultPreview component level
8181

82-
// Get API key on component mount
83-
useEffect(() => {
84-
const getApiKey = async () => {
85-
const keyData = await ensureApiKey();
86-
setApiKey(keyData.key);
87-
};
88-
89-
getApiKey();
90-
}, [ensureApiKey]);
82+
// API key management removed - proxy handles authentication
9183

92-
// Update iframe when code is ready and API key is available
84+
// Update iframe when code is ready
9385
useEffect(() => {
94-
if (codeReady && apiKey && iframeRef.current) {
86+
if (codeReady && iframeRef.current) {
9587
// Skip if content hasn't changed
9688
if (contentLoadedRef.current && appCode === lastContentRef.current) {
9789
return;
@@ -110,7 +102,8 @@ const IframeContent: React.FC<IframeContentProps> = ({
110102

111103
// Use the template and replace placeholders
112104
const htmlContent = iframeTemplateRaw
113-
.replace('{{API_KEY}}', apiKey)
105+
.replaceAll('{{API_KEY}}', 'sk-vibes-proxy-managed')
106+
.replaceAll('{{CALLAI_ENDPOINT}}', CALLAI_ENDPOINT)
114107
.replace('{{APP_CODE}}', transformedCode)
115108
.replace('{{SESSION_ID}}', sessionIdValue);
116109

@@ -132,7 +125,7 @@ const IframeContent: React.FC<IframeContentProps> = ({
132125
window.removeEventListener('message', handleMessage);
133126
};
134127
}
135-
}, [appCode, apiKey, codeReady]);
128+
}, [appCode, codeReady]);
136129

137130
// Determine which view to show based on URL path - gives more stable behavior on refresh
138131
const getViewFromPath = () => {

app/components/ResultPreview/ResultPreview.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,9 @@ function ResultPreview({
4242
const handleMessage = ({ data }: MessageEvent) => {
4343
if (data) {
4444
if (data.type === 'preview-ready' || data.type === 'preview-loaded') {
45-
// respond with the API key
46-
const storedKey = localStorage.getItem('vibes-openrouter-key');
47-
if (storedKey) {
48-
const keyData = JSON.parse(storedKey);
49-
const iframe = document.querySelector('iframe') as HTMLIFrameElement;
50-
iframe?.contentWindow?.postMessage({ type: 'callai-api-key', key: keyData.key }, '*');
51-
setMobilePreviewShown(true);
52-
onPreviewLoaded();
53-
}
45+
// No API key needed - proxy handles authentication
46+
setMobilePreviewShown(true);
47+
onPreviewLoaded();
5448
} else if (data.type === 'streaming' && data.state !== undefined) {
5549
if (setIsIframeFetching) {
5650
setIsIframeFetching(data.state);

app/components/ResultPreview/templates/iframe-template.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@
238238
<script>
239239
window.CALLAI_API_KEY = '{{API_KEY}}';
240240
window.SESSION_ID = '{{SESSION_ID}}';
241+
window.CALLAI_CHAT_URL = '{{CALLAI_ENDPOINT}}';
242+
window.CALLAI_IMG_URL = '{{CALLAI_ENDPOINT}}';
241243
</script>
242244
<script type="importmap">
243245
{
@@ -409,8 +411,6 @@
409411
</script>
410412

411413
<script type="text/babel" data-type="module">
412-
window.CALLAI_IMG_URL = 'https://vibesdiy.app';
413-
414414
import ReactDOMClient from 'react-dom/client';
415415

416416
// App runs normally without our interference, but the iframe loads our script to modify use-fireproof

app/components/SessionSidebar.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { dark, light } from './colorways';
1717
*/
1818
function SessionSidebar({ isVisible, onClose }: SessionSidebarProps) {
1919
const sidebarRef = useRef<HTMLDivElement>(null);
20-
const { isAuthenticated, isLoading, needsLogin, setNeedsLogin } = useAuth();
20+
const { isAuthenticated, isLoading } = useAuth();
2121
const { isPolling, pollError, initiateLogin } = useAuthPopup();
2222
const { isDarkMode } = useTheme();
2323

@@ -169,7 +169,6 @@ function SessionSidebar({ isVisible, onClose }: SessionSidebarProps) {
169169
type="button"
170170
onClick={async () => {
171171
await initiateLogin();
172-
setNeedsLogin(false);
173172
onClose();
174173
}}
175174
style={{
@@ -182,7 +181,7 @@ function SessionSidebar({ isVisible, onClose }: SessionSidebarProps) {
182181
color: rando.diyText,
183182
}}
184183
>
185-
Log in {needsLogin ? 'for credits' : ''}
184+
Log in
186185
</span>
187186
</button>
188187
</li>

app/config/env.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,20 @@ export const APP_MODE = import.meta.env.MODE || 'production'; // typically 'deve
4444

4545
// Fireproof Connect & Auth
4646
export const CONNECT_URL =
47-
import.meta.env.VITE_CONNECT_URL || 'https://dev.connect.fireproof.direct/token';
47+
import.meta.env.VITE_CONNECT_URL || 'https://connect.fireproof.direct/token';
4848
export const CONNECT_API_URL =
49-
import.meta.env.VITE_CONNECT_API_URL || 'https://dev.connect.fireproof.direct/api';
50-
export const CLOUD_SESSION_TOKEN_PUBLIC_KEY = import.meta.env.VITE_CLOUD_SESSION_TOKEN_PUBLIC || '';
49+
import.meta.env.VITE_CONNECT_API_URL || 'https://connect.fireproof.direct/api';
50+
export const CLOUD_SESSION_TOKEN_PUBLIC_KEY =
51+
import.meta.env.VITE_CLOUD_SESSION_TOKEN_PUBLIC ||
52+
'zeWndr5LEoaySgKSo2aZniYqWtx2vKfVz4dd5GQwAuby3fPKcNyLp6mFpf9nCRFYbUcPiN2YT1ZApJ6f3WipiVjuMvyP1JYgHwkaoxDBpJiLoz1grRYkbao9ntukNNo2TQ4uSznUmNPrr4ZxjihoavHwB1zLhLNp5Qj78fBkjgEMA';
5153

5254
// Vibes Service API
53-
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://vibesdiy.app';
55+
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://vibes-diy-api.com';
56+
57+
export const APP_HOST_BASE_URL = import.meta.env.VITE_APP_HOST_BASE_URL || 'https://vibesdiy.app';
58+
59+
// CallAI Endpoint
60+
export const CALLAI_ENDPOINT = import.meta.env.VITE_CALLAI_ENDPOINT || API_BASE_URL;
5461

5562
// Chat History Database
5663
export const SETTINGS_DBNAME =

app/config/provisioning.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

app/contexts/AuthContext.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface AuthContextType {
1616
isLoading: boolean;
1717
userPayload: TokenPayload | null; // Changed from userEmail
1818
needsLogin: boolean;
19-
setNeedsLogin: (value: boolean) => void;
19+
setNeedsLogin: (value: boolean, reason: string) => void;
2020
checkAuthStatus: () => Promise<void>;
2121
processToken: (token: string | null) => Promise<void>;
2222
}
@@ -37,7 +37,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
3737
const [token, setToken] = useState<string | null>(null);
3838
const [userPayload, setUserPayload] = useState<TokenPayload | null>(null); // Changed state
3939
const [isLoading, setIsLoading] = useState<boolean>(true);
40-
const [needsLogin, setNeedsLogin] = useState<boolean>(false);
40+
const [needsLogin, setNeedsLoginState] = useState<boolean>(false);
4141

4242
// Updated function to process token using verifyToken
4343
const processToken = useCallback(async (newToken: string | null) => {
@@ -47,7 +47,6 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
4747
// Valid token and payload
4848
setToken(newToken);
4949
setUserPayload(payload.payload); // Store the full payload
50-
setNeedsLogin(false); // user is authenticated
5150
} else {
5251
// Token is invalid or expired
5352
localStorage.removeItem('auth_token');
@@ -70,7 +69,6 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
7069
} catch (error) {
7170
console.error('Error reading auth token from storage:', error);
7271
await processToken(null); // Ensure state is cleared on error
73-
setNeedsLogin(true); // trigger login requirement on error
7472
} finally {
7573
setIsLoading(false);
7674
}
@@ -98,7 +96,6 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
9896
} catch (error) {
9997
console.error('Error processing token from popup message:', error);
10098
await processToken(null); // Clear state on error
101-
setNeedsLogin(true); // trigger login requirement on error
10299
} finally {
103100
setIsLoading(false);
104101
}
@@ -114,8 +111,31 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
114111

115112
const isAuthenticated = !!token && !!userPayload; // Check both token and payload
116113

114+
// Function to set needsLogin with a reason
115+
const setNeedsLogin = useCallback(
116+
(value: boolean, reason: string) => {
117+
console.log(`Setting needsLogin to ${value} due to: ${reason}`);
118+
setNeedsLoginState(value);
119+
120+
// If user is already authenticated, don't set needsLogin to true
121+
if (value && isAuthenticated) {
122+
console.log('User is already authenticated, not setting needsLogin');
123+
setNeedsLoginState(false);
124+
}
125+
},
126+
[isAuthenticated]
127+
);
128+
129+
// Reset needsLogin when user becomes authenticated
130+
useEffect(() => {
131+
if (isAuthenticated && needsLogin) {
132+
console.log('User authenticated, resetting needsLogin');
133+
setNeedsLoginState(false);
134+
}
135+
}, [isAuthenticated, needsLogin]);
136+
117137
// Value provided by the context
118-
const value = {
138+
const value: AuthContextType = {
119139
token,
120140
isAuthenticated,
121141
isLoading,

0 commit comments

Comments
 (0)