From d82183b9cd5ee3c338ae31ec3c18ac226bd2a753 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Tue, 6 May 2025 14:51:22 -1000 Subject: [PATCH] Support filtering apps (#116) --- .../app-library/ExampleLibraryApps.tsx | 33 +++++++++++-------- app/components/chat/BaseChat.module.scss | 17 ++++++++++ app/components/chat/BaseChat.tsx | 30 ++++++++++++++++- app/components/chat/Messages.client.tsx | 2 +- app/lib/persistence/apps.ts | 31 ++++++++++++++--- app/lib/stores/auth.ts | 2 ++ 6 files changed, 95 insertions(+), 20 deletions(-) diff --git a/app/components/app-library/ExampleLibraryApps.tsx b/app/components/app-library/ExampleLibraryApps.tsx index 51128e17..4364ee5b 100644 --- a/app/components/app-library/ExampleLibraryApps.tsx +++ b/app/components/app-library/ExampleLibraryApps.tsx @@ -16,7 +16,11 @@ const formatDate = (date: Date) => { }).format(date); }; -export const ExampleLibraryApps = () => { +interface ExampleLibraryAppsProps { + filterText: string; +} + +export const ExampleLibraryApps = ({ filterText }: ExampleLibraryAppsProps) => { const [numApps, setNumApps] = useState(6); const [apps, setApps] = useState([]); const [loading, setLoading] = useState(true); @@ -53,11 +57,16 @@ export const ExampleLibraryApps = () => { })(); }, [selectedAppId]); + useEffect(() => { + setApps([]); + setNumApps(6); + }, [filterText]); + useEffect(() => { async function fetchRecentApps() { try { setLoading(true); - const recentApps = await getRecentApps(numApps); + const recentApps = await getRecentApps(numApps, filterText); setApps(recentApps); setError(null); } catch (err) { @@ -68,10 +77,8 @@ export const ExampleLibraryApps = () => { } } - if (apps.length < numApps) { - fetchRecentApps(); - } - }, [numApps]); + fetchRecentApps(); + }, [numApps, filterText]); if (error) { return
{error}
; @@ -148,7 +155,7 @@ export const ExampleLibraryApps = () => { return (
-

{app.title}

+

{app.title}

-
+
Created: {new Date(app.createdAt).toLocaleString()} @@ -189,13 +196,13 @@ export const ExampleLibraryApps = () => { Database: {app.outcome.hasDatabase ? 'Present' : 'None'}
-
Test Results
+
Test Results
{testResults && (
{testResults.map((result) => (
{ {result.recordingId ? ( {result.title} ) : ( -
{result.title}
+
{result.title}
)}
))}
)} - {!testResults &&
Loading...
} + {!testResults &&
Loading...
}
); diff --git a/app/components/chat/BaseChat.module.scss b/app/components/chat/BaseChat.module.scss index 4908e34e..e70e81ce 100644 --- a/app/components/chat/BaseChat.module.scss +++ b/app/components/chat/BaseChat.module.scss @@ -45,3 +45,20 @@ fill: url(#shine-gradient); mix-blend-mode: overlay; } + +.filterInput { + margin-bottom: 1rem; + width: 100%; + + input { + width: 100%; + padding: 0.5rem; + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: 1rem; + + &::placeholder { + color: var(--text-secondary); + } + } +} diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index be4e568a..299c7df1 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -78,6 +78,8 @@ export const BaseChat = React.forwardRef( const [recognition, setRecognition] = useState(null); const [transcript, setTranscript] = useState(''); const [rejectFormOpen, setRejectFormOpen] = useState(false); + const [pendingFilterText, setPendingFilterText] = useState(''); + const [filterText, setFilterText] = useState(''); useEffect(() => { console.log(transcript); @@ -455,7 +457,33 @@ export const BaseChat = React.forwardRef(
Browse these auto-generated apps for a place to start
- +
+ { + if (event.key === 'Enter') { + setFilterText(pendingFilterText); + } + }} + onChange={(event) => { + setPendingFilterText(event.target.value); + }} + style={{ + width: '200px', + padding: '0.5rem', + marginTop: '0.5rem', + border: '1px solid #ccc', + borderRadius: '4px', + fontSize: '0.9rem', + textAlign: 'left', + }} + /> +
+ )}
diff --git a/app/components/chat/Messages.client.tsx b/app/components/chat/Messages.client.tsx index 0b5f183e..0f561d74 100644 --- a/app/components/chat/Messages.client.tsx +++ b/app/components/chat/Messages.client.tsx @@ -43,7 +43,7 @@ export const Messages = React.forwardRef((props: data-testid="message" key={index} className={classNames( - 'flex gap-4 p-6 w-full rounded-[calc(0.75rem-1px)] mt-4 bg-bolt-elements-messages-background', + 'flex gap-4 p-6 w-full rounded-[calc(0.75rem-1px)] mt-4 bg-bolt-elements-messages-background text-bolt-elements-textPrimary', )} >
diff --git a/app/lib/persistence/apps.ts b/app/lib/persistence/apps.ts index e91b0de3..689027b8 100644 --- a/app/lib/persistence/apps.ts +++ b/app/lib/persistence/apps.ts @@ -2,6 +2,7 @@ import { getSupabase } from '~/lib/supabase/client'; import type { Message } from './message'; +import { pingTelemetry } from '~/lib/hooks/pingTelemetry'; export interface BuildAppOutcome { testsPassed?: boolean; @@ -85,12 +86,23 @@ function databaseRowToBuildAppResult(row: any): BuildAppResult { }; } +function appMatchesFilter(app: BuildAppSummary, filterText: string): boolean { + // Always filter out apps that didn't get up and running. + if (!app.title || !app.imageDataURL) { + return false; + } + + const text = `${app.title} ${app.prompt}`.toLowerCase(); + const words = filterText.toLowerCase().split(' '); + return words.every((word) => text.includes(word)); +} + /** * Get all apps created within the last X hours * @param hours Number of hours to look back * @returns Array of BuildAppResult objects */ -async function getAppsCreatedInLastXHours(hours: number): Promise { +async function getAppsCreatedInLastXHours(hours: number, filterText: string): Promise { try { // Calculate the timestamp for X hours ago const hoursAgo = new Date(); @@ -109,19 +121,19 @@ async function getAppsCreatedInLastXHours(hours: number): Promise app.title && app.imageDataURL); + return data.map(databaseRowToBuildAppSummary).filter((app) => appMatchesFilter(app, filterText)); } catch (error) { console.error('Failed to get recent apps:', error); throw error; } } -const HOUR_RANGES = [1, 2, 3, 6, 12, 24]; +const HOUR_RANGES = [1, 2, 3, 6, 12, 24, 72]; -export async function getRecentApps(numApps: number): Promise { +export async function getRecentApps(numApps: number, filterText: string): Promise { let apps: BuildAppSummary[] = []; for (const range of HOUR_RANGES) { - apps = await getAppsCreatedInLastXHours(range); + apps = await getAppsCreatedInLastXHours(range, filterText); if (apps.length >= numApps) { return apps.slice(0, numApps); } @@ -131,7 +143,16 @@ export async function getRecentApps(numApps: number): Promise export async function getAppById(id: string): Promise { console.log('GetAppByIdStart', id); + + // In local testing we've seen problems where this query hangs. + const timeout = setTimeout(() => { + pingTelemetry('GetAppByIdTimeout', {}); + }, 5000); + const { data, error } = await getSupabase().from('apps').select('*').eq('id', id).single(); + + clearTimeout(timeout); + console.log('GetAppByIdDone', id); if (error) { diff --git a/app/lib/stores/auth.ts b/app/lib/stores/auth.ts index 95a05caa..be86e0c4 100644 --- a/app/lib/stores/auth.ts +++ b/app/lib/stores/auth.ts @@ -5,6 +5,7 @@ import { logStore } from './logs'; import { useEffect, useState } from 'react'; import { isAuthenticated } from '~/lib/supabase/client'; import { database } from '~/lib/persistence/chats'; +import { pingTelemetry } from '~/lib/hooks/pingTelemetry'; export const userStore = atom(null); export const sessionStore = atom(null); @@ -36,6 +37,7 @@ export async function initializeAuth() { // Handle this by using a timeout to ensure we don't wait indefinitely. const timeoutPromise = new Promise<{ data: { session: Session | null }; error?: AuthError }>((resolve) => { setTimeout(() => { + pingTelemetry('AuthTimeout', {}); resolve({ data: { session: null }, error: new AuthError('Timed out initializing auth'),