Skip to content

Commit 7488bd7

Browse files
committed
feat(app): ai chat
1 parent 5386911 commit 7488bd7

File tree

9 files changed

+167
-55
lines changed

9 files changed

+167
-55
lines changed

app/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
"@react-three/xr": "catalog:three",
2929
"@recast-navigation/core": "catalog:yuka",
3030
"@recast-navigation/three": "catalog:yuka",
31+
"@xsai/generate-text": "catalog:xsai",
32+
"@xsai/shared": "catalog:xsai",
33+
"@xsai/shared-chat": "catalog:xsai",
3134
"foxact": "catalog:",
3235
"react": "catalog:react",
3336
"react-dom": "catalog:react",
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { Message } from '@xsai/shared-chat'
2+
3+
import { Container } from '@react-three/uikit'
4+
import { Button, Input } from '@react-three/uikit-default'
5+
import { SendIcon } from '@react-three/uikit-lucide'
6+
import { generateText } from '@xsai/generate-text'
7+
import { useState } from 'react'
8+
9+
import { useLLMProvider } from '~/hooks/use-providers'
10+
11+
export const NavbarChat = () => {
12+
const [value, setValue] = useState('')
13+
const [llmProvider] = useLLMProvider()
14+
15+
const [disabled, setDisabled] = useState(false)
16+
17+
const [msg, setMsg] = useState<Message[]>([])
18+
19+
const handleSubmit = async () => {
20+
setDisabled(true)
21+
22+
const { messages, text } = await generateText({
23+
...llmProvider,
24+
messages: [
25+
...msg,
26+
{ content: value, role: 'user' },
27+
],
28+
})
29+
30+
console.warn('Response:', text)
31+
speechSynthesis.speak(new SpeechSynthesisUtterance(text))
32+
33+
setMsg(messages)
34+
setValue('')
35+
setDisabled(false)
36+
}
37+
38+
return (
39+
<Container gap={8} justifyContent="center">
40+
<Input
41+
disabled={disabled}
42+
marginX="auto"
43+
maxWidth={284}
44+
onValueChange={value => setValue(value)}
45+
placeholder="Write a message..."
46+
value={value}
47+
/>
48+
<Button
49+
data-test-id="send-message"
50+
disabled={disabled}
51+
// eslint-disable-next-line ts/no-misused-promises
52+
onClick={handleSubmit}
53+
size="icon"
54+
variant="secondary"
55+
>
56+
<SendIcon height={16} width={16} />
57+
</Button>
58+
</Container>
59+
)
60+
}

app/src/components/ui/navbar.tsx

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ import {
33
Fullscreen,
44
Text,
55
} from '@react-three/uikit'
6-
import { Button, Input } from '@react-three/uikit-default'
7-
import { SendIcon, SettingsIcon } from '@react-three/uikit-lucide'
6+
import { Button } from '@react-three/uikit-default'
7+
import { SettingsIcon } from '@react-three/uikit-lucide'
88
import { IfInSessionMode, useXRStore } from '@react-three/xr'
9-
import { useState } from 'react'
109

1110
import { useNavigate } from '~/router'
1211

12+
import { NavbarChat } from './navbar-chat'
1313
import { ToggleColorSchemeButton } from './toggle-color-scheme-button'
1414

1515
export const Navbar = () => {
1616
const store = useXRStore()
1717
const navigate = useNavigate()
18-
const [value, setValue] = useState('')
1918

2019
return (
2120
<IfInSessionMode deny={['immersive-ar', 'immersive-vr']}>
@@ -27,23 +26,7 @@ export const Navbar = () => {
2726
pointerEvents="listener"
2827
>
2928
<Container flexDirection="column" gap={8} lg={{ flexDirection: 'row' }}>
30-
<Container gap={8} justifyContent="center">
31-
<Input
32-
marginX="auto"
33-
maxWidth={284}
34-
onValueChange={value => setValue(value)}
35-
placeholder="Write a message..."
36-
value={value}
37-
/>
38-
<Button
39-
data-test-id="send-message"
40-
onClick={() => setValue('')}
41-
size="icon"
42-
variant="secondary"
43-
>
44-
<SendIcon height={16} width={16} />
45-
</Button>
46-
</Container>
29+
<NavbarChat />
4730
<Container gap={8} justifyContent="center">
4831
<Button
4932
data-test-id="enter-vr"
Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,47 @@
1-
import type { ContainerProperties } from '@react-three/uikit'
1+
import type { CardProperties } from '@react-three/uikit-default'
22

3-
import { Container, Text } from '@react-three/uikit'
4-
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@react-three/uikit-default'
3+
import { Text } from '@react-three/uikit'
4+
import { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Input } from '@react-three/uikit-default'
5+
import { useState } from 'react'
56

6-
export const SettingsProviders = (props: ContainerProperties) => (
7-
<Container flexDirection="column" {...props}>
8-
<Accordion>
9-
<AccordionItem value="item-1">
10-
<AccordionTrigger>
11-
<Text>Is it accessible?</Text>
12-
</AccordionTrigger>
13-
<AccordionContent>
14-
<Text>Yes. It adheres to the WAI-ARIA design pattern.</Text>
15-
</AccordionContent>
16-
</AccordionItem>
17-
<AccordionItem value="item-2">
18-
<AccordionTrigger>
19-
<Text>Is it styled?</Text>
20-
</AccordionTrigger>
21-
<AccordionContent>
22-
<Text>Yes. It comes with default styles that matches the other components&apos; aesthetic.</Text>
23-
</AccordionContent>
24-
</AccordionItem>
25-
<AccordionItem value="item-3">
26-
<AccordionTrigger>
27-
<Text>Is it animated?</Text>
28-
</AccordionTrigger>
29-
<AccordionContent>
30-
<Text>Yes. It&apos;s animated by default, but you can disable it if you prefer.</Text>
31-
</AccordionContent>
32-
</AccordionItem>
33-
</Accordion>
34-
</Container>
7+
import { useLLMProvider } from '~/hooks/use-providers'
8+
9+
const LLMProvider = (props: CardProperties) => {
10+
const [llmProvider, setLLMProvider] = useLLMProvider()
11+
12+
const [baseURL, setBaseURL] = useState(llmProvider.baseURL)
13+
const [apiKey, setApiKey] = useState(llmProvider.apiKey)
14+
const [model, setModel] = useState(llmProvider.model)
15+
16+
return (
17+
<Card {...props}>
18+
<CardHeader>
19+
<CardTitle>
20+
<Text>LLM</Text>
21+
</CardTitle>
22+
<CardDescription>
23+
<Text>Creates a model response for the given chat conversation.</Text>
24+
</CardDescription>
25+
</CardHeader>
26+
<CardContent flexDirection="column" gap={16}>
27+
<Input onValueChange={setBaseURL} placeholder="baseURL, e.g. https://api.openai.com/v1/" value={baseURL} />
28+
<Input onValueChange={setApiKey} placeholder="apiKey (optional), e.g. sk-******" value={apiKey} />
29+
<Input onValueChange={setModel} placeholder="model, e.g. gpt-4o" value={model} />
30+
</CardContent>
31+
<CardFooter>
32+
<Button
33+
data-test-id="llm-provider-submit"
34+
flexDirection="row"
35+
onClick={() => setLLMProvider({ apiKey, baseURL, model })}
36+
width="100%"
37+
>
38+
<Text>Submit</Text>
39+
</Button>
40+
</CardFooter>
41+
</Card>
42+
)
43+
}
44+
45+
export const SettingsProviders = (props: CardProperties) => (
46+
<LLMProvider {...props} />
3547
)

app/src/components/vrm/galatea.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ export const Galatea = () => {
3636
const isWalk = useOrcustAutomatonState(galateaEntity)
3737

3838
useEffect(() => {
39-
console.warn('State changed, isWalk:', isWalk)
40-
4139
if (initialized.current == null) {
4240
actions[isWalk ? 'walk' : 'idle']!
4341
.reset()

app/src/hooks/use-providers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useLocalStorage } from 'foxact/use-local-storage'
2+
3+
export const useLLMProvider = () => useLocalStorage<{
4+
apiKey: string
5+
baseURL: string
6+
model: string
7+
}>('n3p6/providers/llm', {
8+
apiKey: '',
9+
baseURL: 'http://localhost:11434/v1/',
10+
model: '',
11+
})

cspell.config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ words:
4040
- verts
4141
- vrma
4242
- vrmc
43+
- xsai
4344
- yuka
4445
ignoreWords: []
4546
import: []

pnpm-lock.yaml

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

pnpm-workspace.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ catalogs:
3333
three: ^0.176.0
3434
three-stdlib: ^2.36.0
3535

36+
xsai:
37+
'@xsai/generate-text': &xsai ^0.2.2
38+
'@xsai/shared': *xsai
39+
'@xsai/shared-chat': *xsai
40+
3641
yuka:
3742
'@recast-navigation/core': &recast-navigation ^0.39.0
3843
'@recast-navigation/three': *recast-navigation

0 commit comments

Comments
 (0)