Skip to content

Commit 44295d2

Browse files
committed
refactors
1 parent 2896481 commit 44295d2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+3200
-2442
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
"@radix-ui/react-dialog": "^1.1.15",
3434
"@radix-ui/react-dropdown-menu": "^2.1.12",
3535
"@radix-ui/react-toast": "^1.2.2",
36+
"@react-three/drei": "^10.7.7",
37+
"@react-three/fiber": "^9.5.0",
3638
"@sentry/react": "^8.35.0",
3739
"@sentry/vite-plugin": "^2.22.6",
3840
"@tailwindcss/typography": "^0.5.13",
@@ -84,6 +86,7 @@
8486
"resend": "^6.6.0",
8587
"shiki": "^1.4.0",
8688
"tailwind-merge": "^1.14.0",
89+
"three": "^0.182.0",
8790
"unified": "^11.0.5",
8891
"unist-util-visit": "^5.0.0",
8992
"uploadthing": "^7.7.4",
@@ -103,6 +106,7 @@
103106
"@types/react": "^19.2.0",
104107
"@types/react-dom": "^19.2.0",
105108
"@types/remove-markdown": "^0.3.4",
109+
"@types/three": "^0.182.0",
106110
"autoprefixer": "^10.4.18",
107111
"dotenv-cli": "^8.0.0",
108112
"drizzle-kit": "^0.31.7",

pnpm-lock.yaml

Lines changed: 467 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/AILibraryHero.tsx

Lines changed: 6 additions & 297 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ import { LinkProps } from '@tanstack/react-router'
33
import type { Library } from '~/libraries'
44
import { useIsDark } from '~/hooks/useIsDark'
55
import { ChatPanel } from './ChatPanel'
6-
import {
7-
useAILibraryHeroAnimationStore,
8-
AnimationPhase,
9-
} from '~/stores/aiLibraryHeroAnimation'
6+
import { AnimationPhase } from '~/stores/aiLibraryHeroAnimation'
7+
import { useAILibraryHeroAnimation } from '~/hooks/useAILibraryHeroAnimation'
108
import { AILibraryHeroCard } from './AILibraryHeroCard'
119
import { AILibraryHeroBox } from './AILibraryHeroBox'
1210
import { AILibraryHeroServiceCard } from './AILibraryHeroServiceCard'
@@ -30,7 +28,6 @@ import {
3028
BOX_FONT_SIZE,
3129
BOX_FONT_WEIGHT,
3230
SERVICE_WIDTH,
33-
SERVICE_GUTTER,
3431
SERVICE_LOCATIONS,
3532
SERVICE_Y_OFFSET,
3633
SERVICE_Y_CENTER,
@@ -44,9 +41,6 @@ import {
4441
SERVER_CARD_HEIGHT,
4542
} from '~/stores/aiLibraryHeroAnimation'
4643

47-
// Get the store instance for accessing getState in closures
48-
const getStoreState = () => useAILibraryHeroAnimationStore.getState()
49-
5044
type AILibraryHeroProps = {
5145
project: Library
5246
cta?: {
@@ -57,48 +51,14 @@ type AILibraryHeroProps = {
5751
actions?: React.ReactNode
5852
}
5953

60-
const FRAMEWORKS = ['vanilla', 'react', 'solid', '?'] as const
61-
const SERVICES = ['ollama', 'openai', 'anthropic', 'gemini'] as const
62-
const SERVERS = ['typescript', 'php', 'python', '?'] as const
63-
64-
const MESSAGES = [
65-
{
66-
user: 'What makes TanStack AI different?',
67-
assistant:
68-
'TanStack AI is completely agnostic - server agnostic, client agnostic, and service agnostic. Use any backend (TypeScript, PHP, Python), any client (vanilla JS, React, Solid), and any AI service (OpenAI, Anthropic, Gemini, Ollama). We provide the libraries and standards, you choose your stack.',
69-
},
70-
{
71-
user: 'Do you support tools?',
72-
assistant:
73-
'Yes! We have full support for both client and server tooling, including tool approvals. You can execute tools on either side with complete type safety and control.',
74-
},
75-
{
76-
user: 'What about thinking models?',
77-
assistant:
78-
"We fully support thinking and reasoning models. All thinking and reasoning tokens are sent to the client, giving you complete visibility into the model's reasoning process.",
79-
},
80-
{
81-
user: 'How type-safe is it?',
82-
assistant:
83-
'We have total type safety across providers, models, and model options. Every interaction is fully typed from end to end, catching errors at compile time.',
84-
},
85-
{
86-
user: 'What about developer experience?',
87-
assistant:
88-
'We have next-generation dev tools that show you everything happening with your AI connection in real-time. Debug, inspect, and optimize with complete visibility.',
89-
},
90-
{
91-
user: 'Is this a service I have to pay for?',
92-
assistant:
93-
"No! TanStack AI is pure open source software. We don't have a service to promote or charge for. This is an ecosystem of libraries and standards connecting you with the services you choose - completely community supported.",
94-
},
95-
]
96-
9754
export function AILibraryHero({}: AILibraryHeroProps) {
9855
const isDark = useIsDark()
9956
const strokeColor = isDark ? 'rgba(255, 255, 255, 0.8)' : 'rgba(0, 0, 0, 0.6)'
10057
const textColor = isDark ? '#ffffff' : '#000000'
10158

59+
// Use the animation hook - handles all animation state and orchestration
60+
const { store } = useAILibraryHeroAnimation()
61+
10262
const {
10363
phase,
10464
selectedFramework,
@@ -111,258 +71,7 @@ export function AILibraryHero({}: AILibraryHeroProps) {
11171
messages,
11272
typingUserMessage,
11373
connectionPulseDirection,
114-
setPhase,
115-
setSelectedFramework,
116-
setSelectedService,
117-
setSelectedServer,
118-
setRotatingFramework,
119-
setRotatingServer,
120-
setRotatingService,
121-
setServiceOffset,
122-
addMessage,
123-
updateCurrentAssistantMessage,
124-
setCurrentMessageStreaming,
125-
clearMessages,
126-
setTypingUserMessage,
127-
clearTypingUserMessage,
128-
setConnectionPulseDirection,
129-
addTimeout,
130-
clearTimeouts,
131-
} = useAILibraryHeroAnimationStore()
132-
133-
React.useEffect(() => {
134-
const addTimeoutHelper = (fn: () => void, delay: number) => {
135-
const timeout = setTimeout(fn, delay)
136-
addTimeout(timeout)
137-
return timeout
138-
}
139-
140-
const getRandomIndex = (length: number, exclude?: number) => {
141-
let index
142-
do {
143-
index = Math.floor(Math.random() * length)
144-
} while (exclude !== undefined && index === exclude)
145-
return index
146-
}
147-
148-
const selectFrameworkServiceServer = (onComplete: () => void) => {
149-
// Phase 2: DESELECTING
150-
setPhase(AnimationPhase.DESELECTING)
151-
addTimeoutHelper(() => {
152-
// Phase 3: SELECTING_FRAMEWORK
153-
setPhase(AnimationPhase.SELECTING_FRAMEWORK)
154-
const targetFramework = getRandomIndex(FRAMEWORKS.length)
155-
let currentIndex = Math.floor(Math.random() * FRAMEWORKS.length)
156-
const rotationCount = 8 + Math.floor(Math.random() * 4) // 8-11 rotations
157-
158-
const rotateFramework = (iteration: number) => {
159-
if (iteration < rotationCount - 1) {
160-
setRotatingFramework(currentIndex)
161-
currentIndex = (currentIndex + 1) % FRAMEWORKS.length
162-
const delay =
163-
iteration < rotationCount - 4
164-
? 100
165-
: 150 + (iteration - (rotationCount - 4)) * 50
166-
addTimeoutHelper(() => rotateFramework(iteration + 1), delay)
167-
} else {
168-
// Final iteration - ensure we land on target
169-
setRotatingFramework(targetFramework)
170-
addTimeoutHelper(() => {
171-
setSelectedFramework(targetFramework)
172-
setRotatingFramework(null)
173-
addTimeoutHelper(() => {
174-
// Phase 4: SELECTING_SERVICE
175-
setPhase(AnimationPhase.SELECTING_SERVICE)
176-
// Always pick a different service so it has to scroll
177-
const currentSelectedService = getStoreState().selectedService
178-
const targetService = getRandomIndex(
179-
SERVICES.length,
180-
currentSelectedService ?? undefined,
181-
)
182-
let currentServiceIndex = Math.floor(
183-
Math.random() * SERVICES.length,
184-
)
185-
const serviceRotationCount = 6 + Math.floor(Math.random() * 3)
186-
187-
const rotateService = (iteration: number) => {
188-
if (iteration < serviceRotationCount - 1) {
189-
setRotatingService(currentServiceIndex)
190-
currentServiceIndex =
191-
(currentServiceIndex + 1) % SERVICES.length
192-
const delay =
193-
iteration < serviceRotationCount - 3
194-
? 120
195-
: 180 + (iteration - (serviceRotationCount - 3)) * 60
196-
addTimeoutHelper(() => rotateService(iteration + 1), delay)
197-
} else {
198-
// Final iteration - ensure we land on target
199-
setRotatingService(targetService)
200-
addTimeoutHelper(() => {
201-
setSelectedService(targetService)
202-
setRotatingService(null)
203-
const targetX =
204-
0 -
205-
SERVICE_WIDTH / 2 -
206-
SERVICE_GUTTER / 2 -
207-
targetService * (SERVICE_WIDTH + SERVICE_GUTTER)
208-
setServiceOffset(targetX)
209-
210-
addTimeoutHelper(() => {
211-
// Phase 5: SELECTING_SERVER
212-
setPhase(AnimationPhase.SELECTING_SERVER)
213-
const targetServer = getRandomIndex(SERVERS.length)
214-
let currentServerIndex = Math.floor(
215-
Math.random() * SERVERS.length,
216-
)
217-
const serverRotationCount =
218-
8 + Math.floor(Math.random() * 4)
219-
220-
const rotateServer = (iteration: number) => {
221-
if (iteration < serverRotationCount - 1) {
222-
setRotatingServer(currentServerIndex)
223-
currentServerIndex =
224-
(currentServerIndex + 1) % SERVERS.length
225-
const delay =
226-
iteration < serverRotationCount - 4
227-
? 100
228-
: 150 +
229-
(iteration - (serverRotationCount - 4)) * 50
230-
addTimeoutHelper(
231-
() => rotateServer(iteration + 1),
232-
delay,
233-
)
234-
} else {
235-
// Final iteration - ensure we land on target
236-
setRotatingServer(targetServer)
237-
addTimeoutHelper(() => {
238-
setSelectedServer(targetServer)
239-
setRotatingServer(null)
240-
addTimeoutHelper(() => {
241-
// Selection complete, call callback
242-
onComplete()
243-
}, 800)
244-
}, 1000)
245-
}
246-
}
247-
rotateServer(0)
248-
}, 1000)
249-
}, 800)
250-
}
251-
}
252-
rotateService(0)
253-
}, 800)
254-
}, 500)
255-
}
256-
}
257-
rotateFramework(0)
258-
}, 500)
259-
}
260-
261-
const processNextMessage = (messageIndex: number) => {
262-
if (messageIndex >= MESSAGES.length) {
263-
// All messages shown, clear and restart
264-
clearMessages()
265-
addTimeoutHelper(() => {
266-
// Phase 1: STARTING (initial state)
267-
setPhase(AnimationPhase.STARTING)
268-
addTimeoutHelper(() => {
269-
// Start first message with selection
270-
selectFrameworkServiceServer(() => {
271-
processNextMessage(0)
272-
})
273-
}, 1000)
274-
}, 1000)
275-
return
276-
}
277-
278-
const message = MESSAGES[messageIndex]
279-
280-
// Phase 6: SHOWING_CHAT - Type user message in input field first
281-
setPhase(AnimationPhase.SHOWING_CHAT)
282-
clearTypingUserMessage()
283-
284-
// Type the user message character by character in the input field
285-
let typingIndex = 0
286-
const typeUserMessage = () => {
287-
if (typingIndex < message.user.length) {
288-
setTypingUserMessage(message.user.slice(0, typingIndex + 1))
289-
typingIndex++
290-
const delay = 30 + Math.floor(Math.random() * 40) // 30-70ms per character
291-
addTimeoutHelper(typeUserMessage, delay)
292-
} else {
293-
// Typing complete, wait a moment then clear input and show as bubble
294-
addTimeoutHelper(() => {
295-
clearTypingUserMessage()
296-
addMessage(message.user)
297-
addTimeoutHelper(() => {
298-
// Phase 7: PULSING_CONNECTIONS
299-
setPhase(AnimationPhase.PULSING_CONNECTIONS)
300-
setConnectionPulseDirection('down')
301-
addTimeoutHelper(() => {
302-
// Phase 8: STREAMING_RESPONSE
303-
setPhase(AnimationPhase.STREAMING_RESPONSE)
304-
setConnectionPulseDirection('up')
305-
const fullMessage = message.assistant
306-
setCurrentMessageStreaming(true)
307-
let currentIndex = 0
308-
309-
const streamChunk = () => {
310-
if (currentIndex < fullMessage.length) {
311-
// Random chunk size between 2 and 8 characters
312-
const chunkSize = 2 + Math.floor(Math.random() * 7)
313-
const nextIndex = Math.min(
314-
currentIndex + chunkSize,
315-
fullMessage.length,
316-
)
317-
updateCurrentAssistantMessage(
318-
fullMessage.slice(0, nextIndex),
319-
)
320-
currentIndex = nextIndex
321-
// Random delay between 20ms and 80ms
322-
const delay = 20 + Math.floor(Math.random() * 60)
323-
addTimeoutHelper(streamChunk, delay)
324-
} else {
325-
setCurrentMessageStreaming(false)
326-
addTimeoutHelper(() => {
327-
// Phase 9: HOLDING - brief pause before next message
328-
setPhase(AnimationPhase.HOLDING)
329-
addTimeoutHelper(() => {
330-
// Select new combination for next message
331-
selectFrameworkServiceServer(() => {
332-
// Move to next message
333-
processNextMessage(messageIndex + 1)
334-
})
335-
}, 2000)
336-
}, 500)
337-
}
338-
}
339-
streamChunk()
340-
}, 2000)
341-
}, 500)
342-
}, 300)
343-
}
344-
}
345-
typeUserMessage()
346-
}
347-
348-
const startAnimationSequence = () => {
349-
// Phase 1: STARTING (initial state)
350-
setPhase(AnimationPhase.STARTING)
351-
addTimeoutHelper(() => {
352-
// Start first message with selection
353-
selectFrameworkServiceServer(() => {
354-
processNextMessage(0)
355-
})
356-
}, 1000)
357-
}
358-
359-
startAnimationSequence()
360-
361-
return () => {
362-
clearTimeouts()
363-
}
364-
// eslint-disable-next-line react-hooks/exhaustive-deps
365-
}, [])
74+
} = store
36675

36776
const getOpacity = (
36877
index: number,

src/components/BackgroundGradient.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function BackgroundGradient() {
1919
}
2020
}
2121

22-
const library = findLibrary(libraryId as any)
22+
const library = findLibrary(libraryId)
2323

2424
return (
2525
<div

0 commit comments

Comments
 (0)