liquidcn 0.0.1 → 0.1.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -2
- package/dist/esm/client/components/audio-visualizer.js +88 -0
- package/dist/esm/client/components/audio-visualizer.js.map +1 -0
- package/dist/esm/client/components/chat-view.js +182 -0
- package/dist/esm/client/components/chat-view.js.map +1 -0
- package/dist/esm/client/components/form-builder.js +416 -0
- package/dist/esm/client/components/form-builder.js.map +1 -0
- package/dist/esm/client/components/index.js +3 -0
- package/dist/esm/client/components/index.js.map +1 -1
- package/dist/esm/client/components/ui/dialog.js +3 -3
- package/dist/esm/client/components/ui/dialog.js.map +1 -1
- package/dist/esm/client/components/ui/index.js +2 -0
- package/dist/esm/client/components/ui/index.js.map +1 -1
- package/dist/esm/client/components/ui/label.js +9 -0
- package/dist/esm/client/components/ui/label.js.map +1 -0
- package/dist/esm/client/components/ui/resizable-navbar.js +8 -8
- package/dist/esm/client/components/ui/resizable-navbar.js.map +1 -1
- package/dist/esm/client/components/ui/select.js +5 -5
- package/dist/esm/client/components/ui/select.js.map +1 -1
- package/dist/esm/client/components/ui/slider.js +10 -0
- package/dist/esm/client/components/ui/slider.js.map +1 -0
- package/dist/esm/client/components/ui/switch.js +1 -1
- package/dist/esm/client/components/ui/switch.js.map +1 -1
- package/dist/esm/client/components/ui/tabs.js +7 -6
- package/dist/esm/client/components/ui/tabs.js.map +1 -1
- package/dist/esm/client/hooks/index.js +1 -0
- package/dist/esm/client/hooks/index.js.map +1 -1
- package/dist/esm/client/hooks/use-speech-to-text.js +54 -0
- package/dist/esm/client/hooks/use-speech-to-text.js.map +1 -0
- package/dist/esm/client/utils/audio-utils.js +83 -0
- package/dist/esm/client/utils/audio-utils.js.map +1 -0
- package/dist/esm/components/ui/badge.js +6 -6
- package/dist/esm/components/ui/badge.js.map +1 -1
- package/dist/esm/components/ui/button.js +6 -6
- package/dist/esm/components/ui/button.js.map +1 -1
- package/dist/esm/components/ui/input.js +2 -2
- package/dist/esm/components/ui/input.js.map +1 -1
- package/dist/esm/components/ui/textarea.js +2 -2
- package/dist/esm/components/ui/textarea.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/speech-to-text/index.js +3 -0
- package/dist/esm/speech-to-text/index.js.map +1 -0
- package/dist/esm/speech-to-text/server.js +63 -0
- package/dist/esm/speech-to-text/server.js.map +1 -0
- package/dist/esm/speech-to-text/types.js +2 -0
- package/dist/esm/speech-to-text/types.js.map +1 -0
- package/dist/styles.css +116 -25
- package/dist/types/client/components/audio-visualizer.d.ts +49 -0
- package/dist/types/client/components/audio-visualizer.d.ts.map +1 -0
- package/dist/types/client/components/chat-view.d.ts +55 -0
- package/dist/types/client/components/chat-view.d.ts.map +1 -0
- package/dist/types/client/components/form-builder.d.ts +211 -0
- package/dist/types/client/components/form-builder.d.ts.map +1 -0
- package/dist/types/client/components/index.d.ts +3 -0
- package/dist/types/client/components/index.d.ts.map +1 -1
- package/dist/types/client/components/ui/dialog.d.ts.map +1 -1
- package/dist/types/client/components/ui/index.d.ts +2 -0
- package/dist/types/client/components/ui/index.d.ts.map +1 -1
- package/dist/types/client/components/ui/label.d.ts +5 -0
- package/dist/types/client/components/ui/label.d.ts.map +1 -0
- package/dist/types/client/components/ui/resizable-navbar.d.ts.map +1 -1
- package/dist/types/client/components/ui/select.d.ts.map +1 -1
- package/dist/types/client/components/ui/slider.d.ts +5 -0
- package/dist/types/client/components/ui/slider.d.ts.map +1 -0
- package/dist/types/client/components/ui/switch.d.ts.map +1 -1
- package/dist/types/client/components/ui/tabs.d.ts.map +1 -1
- package/dist/types/client/hooks/index.d.ts +1 -0
- package/dist/types/client/hooks/index.d.ts.map +1 -1
- package/dist/types/client/hooks/use-speech-to-text.d.ts +51 -0
- package/dist/types/client/hooks/use-speech-to-text.d.ts.map +1 -0
- package/dist/types/client/utils/audio-utils.d.ts +20 -0
- package/dist/types/client/utils/audio-utils.d.ts.map +1 -0
- package/dist/types/components/ui/badge.d.ts.map +1 -1
- package/dist/types/components/ui/input.d.ts.map +1 -1
- package/dist/types/components/ui/textarea.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/speech-to-text/index.d.ts +7 -0
- package/dist/types/speech-to-text/index.d.ts.map +1 -0
- package/dist/types/speech-to-text/server.d.ts +35 -0
- package/dist/types/speech-to-text/server.d.ts.map +1 -0
- package/dist/types/speech-to-text/types.d.ts +94 -0
- package/dist/types/speech-to-text/types.d.ts.map +1 -0
- package/package.json +21 -10
- package/dist/cjs/client/components/index.js +0 -18
- package/dist/cjs/client/components/index.js.map +0 -1
- package/dist/cjs/client/components/ui/dialog.js +0 -48
- package/dist/cjs/client/components/ui/dialog.js.map +0 -1
- package/dist/cjs/client/components/ui/index.js +0 -24
- package/dist/cjs/client/components/ui/index.js.map +0 -1
- package/dist/cjs/client/components/ui/pretty-date.js +0 -91
- package/dist/cjs/client/components/ui/pretty-date.js.map +0 -1
- package/dist/cjs/client/components/ui/resizable-navbar.js +0 -126
- package/dist/cjs/client/components/ui/resizable-navbar.js.map +0 -1
- package/dist/cjs/client/components/ui/select.js +0 -50
- package/dist/cjs/client/components/ui/select.js.map +0 -1
- package/dist/cjs/client/components/ui/sonner.js +0 -12
- package/dist/cjs/client/components/ui/sonner.js.map +0 -1
- package/dist/cjs/client/components/ui/switch.js +0 -11
- package/dist/cjs/client/components/ui/switch.js.map +0 -1
- package/dist/cjs/client/components/ui/tabs.js +0 -53
- package/dist/cjs/client/components/ui/tabs.js.map +0 -1
- package/dist/cjs/client/hooks/index.js +0 -18
- package/dist/cjs/client/hooks/index.js.map +0 -1
- package/dist/cjs/client/hooks/use-cookie-with-fallback.js +0 -107
- package/dist/cjs/client/hooks/use-cookie-with-fallback.js.map +0 -1
- package/dist/cjs/client/index.js +0 -19
- package/dist/cjs/client/index.js.map +0 -1
- package/dist/cjs/components/index.js +0 -18
- package/dist/cjs/components/index.js.map +0 -1
- package/dist/cjs/components/ui/alert.js +0 -33
- package/dist/cjs/components/ui/alert.js.map +0 -1
- package/dist/cjs/components/ui/badge.js +0 -28
- package/dist/cjs/components/ui/badge.js.map +0 -1
- package/dist/cjs/components/ui/button.js +0 -39
- package/dist/cjs/components/ui/button.js.map +0 -1
- package/dist/cjs/components/ui/card.js +0 -48
- package/dist/cjs/components/ui/card.js.map +0 -1
- package/dist/cjs/components/ui/footer.js +0 -9
- package/dist/cjs/components/ui/footer.js.map +0 -1
- package/dist/cjs/components/ui/index.js +0 -25
- package/dist/cjs/components/ui/index.js.map +0 -1
- package/dist/cjs/components/ui/input.js +0 -12
- package/dist/cjs/components/ui/input.js.map +0 -1
- package/dist/cjs/components/ui/pretty-amount.js +0 -167
- package/dist/cjs/components/ui/pretty-amount.js.map +0 -1
- package/dist/cjs/components/ui/textarea.js +0 -12
- package/dist/cjs/components/ui/textarea.js.map +0 -1
- package/dist/cjs/index.js +0 -19
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/utils.js +0 -9
- package/dist/cjs/utils.js.map +0 -1
package/README.md
CHANGED
|
@@ -17,8 +17,10 @@ A collection of reusable, accessible React UI components built with TypeScript,
|
|
|
17
17
|
**liquidcn** is a comprehensive component library featuring:
|
|
18
18
|
|
|
19
19
|
- **UI Components**: Button, Card, Alert, Badge, Input, Textarea, Footer, PrettyAmount
|
|
20
|
-
- **Client Components**: Dialog, Select, Switch, Tabs, Sonner (Toast), PrettyDate, ResizableNavbar
|
|
21
|
-
- **
|
|
20
|
+
- **Client Components**: Dialog, Select, Switch, Tabs, Sonner (Toast), PrettyDate, ResizableNavbar, Slider, AudioVisualizer
|
|
21
|
+
- **Form Components**: FormBuilder (schema-driven forms with AI mode), ChatView (AI chat interface with voice input)
|
|
22
|
+
- **Hooks**: Custom React hooks including `useCookieWithFallback`, `useSpeechToText`
|
|
23
|
+
- **Server Utilities**: `createSpeechToTextHandler()` for voice-to-text API routes
|
|
22
24
|
- **Utilities**: `cn()` utility for className merging using clsx and tailwind-merge
|
|
23
25
|
|
|
24
26
|
Bun + Npm + Typescript + Standard Version + Flat Config Linting + Husky + Commit / Release Pipeline
|
|
@@ -159,6 +161,211 @@ export function MyFooter() {
|
|
|
159
161
|
}
|
|
160
162
|
```
|
|
161
163
|
|
|
164
|
+
### FormBuilder Component
|
|
165
|
+
|
|
166
|
+
The `FormBuilder` component renders schema-driven forms with optional AI-powered form filling. Works with `useSchemaForm` from `tanstack-effect`.
|
|
167
|
+
|
|
168
|
+
#### Basic Usage
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { useSchemaForm } from 'tanstack-effect/client'
|
|
172
|
+
import { FormBuilder, FormValidationAlert, isFormValid } from 'liquidcn/client'
|
|
173
|
+
import { Button } from 'liquidcn'
|
|
174
|
+
import { Schema } from 'effect'
|
|
175
|
+
|
|
176
|
+
const ProjectSchema = Schema.Struct({
|
|
177
|
+
projectName: Schema.String.pipe(Schema.annotations({ description: 'Name of the project' })),
|
|
178
|
+
projectType: Schema.Literal('web', 'mobile', 'desktop').pipe(
|
|
179
|
+
Schema.annotations({ description: 'Type of project' })
|
|
180
|
+
),
|
|
181
|
+
teamSize: Schema.Number.pipe(Schema.annotations({ description: 'Number of team members' })),
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
function ProjectForm() {
|
|
185
|
+
const form = useSchemaForm({
|
|
186
|
+
schema: ProjectSchema,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div>
|
|
191
|
+
<FormBuilder form={form} variant="default" />
|
|
192
|
+
<Button onClick={() => console.log(form.data)}>Submit</Button>
|
|
193
|
+
<FormValidationAlert form={form} />
|
|
194
|
+
</div>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### With AI Mode
|
|
200
|
+
|
|
201
|
+
Enable AI-powered form filling by adding the `ai` option to `useSchemaForm` and `enableAIMode` to `FormBuilder`:
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
import { useSchemaForm } from 'tanstack-effect/client'
|
|
205
|
+
import { FormBuilder, FormValidationAlert } from 'liquidcn/client'
|
|
206
|
+
import { Button } from 'liquidcn'
|
|
207
|
+
import { Schema } from 'effect'
|
|
208
|
+
|
|
209
|
+
const ProjectSchema = Schema.Struct({
|
|
210
|
+
projectName: Schema.String.pipe(Schema.annotations({ description: 'Name of the project' })),
|
|
211
|
+
projectType: Schema.Literal('web', 'mobile', 'desktop').pipe(
|
|
212
|
+
Schema.annotations({ description: 'Type of project' })
|
|
213
|
+
),
|
|
214
|
+
teamSize: Schema.Number.pipe(Schema.annotations({ description: 'Number of team members' })),
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
function ProjectForm() {
|
|
218
|
+
const form = useSchemaForm({
|
|
219
|
+
schema: ProjectSchema,
|
|
220
|
+
// Enable AI form filling
|
|
221
|
+
ai: {
|
|
222
|
+
endpoint: '/api/ai-form-fill',
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
<FormBuilder
|
|
229
|
+
form={form}
|
|
230
|
+
variant="wizard"
|
|
231
|
+
enableAIMode
|
|
232
|
+
aiPlaceholder="Describe your project..."
|
|
233
|
+
aiChatMinHeight="300px"
|
|
234
|
+
/>
|
|
235
|
+
<Button onClick={() => console.log(form.data)}>Submit</Button>
|
|
236
|
+
<FormValidationAlert form={form} />
|
|
237
|
+
</div>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
With AI mode enabled, the FormBuilder shows:
|
|
243
|
+
|
|
244
|
+
- **AI/Edit toggle buttons** - Switch between chat and manual editing
|
|
245
|
+
- **Chat interface** - Full conversation with AI including message history
|
|
246
|
+
- **Clarification prompts** - AI asks for missing required fields
|
|
247
|
+
- **Summary** - Shows what fields were filled (e.g. "Filled 3 fields: projectName, projectType, teamSize")
|
|
248
|
+
|
|
249
|
+
#### FormBuilder Props
|
|
250
|
+
|
|
251
|
+
| Prop | Type | Default | Description |
|
|
252
|
+
| ----------------- | ------------------------------------ | ----------- | ------------------------------- |
|
|
253
|
+
| `form` | `UseSchemaFormReturn` | required | Form state from `useSchemaForm` |
|
|
254
|
+
| `variant` | `'default' \| 'compact' \| 'wizard'` | `'default'` | Display variant |
|
|
255
|
+
| `enableAIMode` | `boolean` | `false` | Show AI/Edit mode toggle |
|
|
256
|
+
| `initialMode` | `'ai' \| 'edit'` | `'ai'` | Initial mode when AI is enabled |
|
|
257
|
+
| `aiPlaceholder` | `string` | - | Placeholder for AI chat input |
|
|
258
|
+
| `aiChatMinHeight` | `string` | `'300px'` | Minimum height for chat view |
|
|
259
|
+
| `pinnedFields` | `string[]` | `[]` | Fields to show at top level |
|
|
260
|
+
| `hiddenFields` | `string[]` | `[]` | Fields to hide from form |
|
|
261
|
+
|
|
262
|
+
### ChatView Component
|
|
263
|
+
|
|
264
|
+
The `ChatView` component provides a standalone chat UI for AI interactions with optional voice input:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
import { ChatView } from 'liquidcn/client'
|
|
268
|
+
|
|
269
|
+
function AIChat() {
|
|
270
|
+
const [messages, setMessages] = useState([])
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<ChatView
|
|
274
|
+
messages={messages}
|
|
275
|
+
status="idle"
|
|
276
|
+
onSend={(msg) => console.log('Send:', msg)}
|
|
277
|
+
placeholder="Ask me anything..."
|
|
278
|
+
enableVoice={true} // Enable when OPENAI_API_KEY is configured on server
|
|
279
|
+
/>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### ChatView Props
|
|
285
|
+
|
|
286
|
+
| Prop | Type | Default | Description |
|
|
287
|
+
| --------------- | ---------- | --------------------- | ------------------------------------------------ |
|
|
288
|
+
| `messages` | `array` | required | Messages in the conversation |
|
|
289
|
+
| `status` | `string` | required | AI status: 'idle', 'filling', 'clarifying', etc. |
|
|
290
|
+
| `onSend` | `function` | required | Callback when user sends a message |
|
|
291
|
+
| `placeholder` | `string` | - | Placeholder text for input |
|
|
292
|
+
| `enableVoice` | `boolean` | `false` | Enable voice input (requires server API key) |
|
|
293
|
+
| `voiceEndpoint` | `string` | `/api/speech-to-text` | Custom endpoint for speech-to-text API |
|
|
294
|
+
|
|
295
|
+
### Speech-to-Text (Voice Input)
|
|
296
|
+
|
|
297
|
+
LiquidCN provides speech-to-text capabilities for voice input in chat interfaces.
|
|
298
|
+
|
|
299
|
+
#### Server Setup
|
|
300
|
+
|
|
301
|
+
First, install the optional AI SDK dependencies:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
bun add ai @ai-sdk/openai
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Create a speech-to-text API route using the handler:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// app/api/speech-to-text/route.ts
|
|
311
|
+
import { createSpeechToTextHandler } from 'liquidcn'
|
|
312
|
+
import { auth } from '@/auth'
|
|
313
|
+
|
|
314
|
+
const handler = createSpeechToTextHandler({
|
|
315
|
+
authenticate: async () => {
|
|
316
|
+
const session = await auth()
|
|
317
|
+
return !!session?.user
|
|
318
|
+
},
|
|
319
|
+
defaultModel: 'gpt-4o-transcribe', // optional
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
export const POST = handler
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Set the `OPENAI_API_KEY` environment variable on your server.
|
|
326
|
+
|
|
327
|
+
#### Client Usage
|
|
328
|
+
|
|
329
|
+
The `useSpeechToText` hook can be used standalone:
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
import { useSpeechToText } from 'liquidcn/client'
|
|
333
|
+
|
|
334
|
+
function VoiceInput() {
|
|
335
|
+
const { transcribe, isLoading, data, error } = useSpeechToText({
|
|
336
|
+
onSuccess: (result) => console.log('Transcribed:', result.text),
|
|
337
|
+
onError: (err) => console.error('Error:', err.error),
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const handleRecord = async (audioFile: File) => {
|
|
341
|
+
await transcribe({ audio: audioFile })
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div>
|
|
346
|
+
{isLoading && <p>Transcribing...</p>}
|
|
347
|
+
{data && <p>Result: {data.text}</p>}
|
|
348
|
+
</div>
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### AudioVisualizer Component
|
|
354
|
+
|
|
355
|
+
Display audio waveform during recording:
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
import { AudioVisualizer } from 'liquidcn/client'
|
|
359
|
+
|
|
360
|
+
;<AudioVisualizer
|
|
361
|
+
isRecording={isRecording}
|
|
362
|
+
mediaRecorder={mediaRecorder}
|
|
363
|
+
width={280}
|
|
364
|
+
height={60}
|
|
365
|
+
barColor="#a78bfa"
|
|
366
|
+
/>
|
|
367
|
+
```
|
|
368
|
+
|
|
162
369
|
## Developing
|
|
163
370
|
|
|
164
371
|
Install Dependencies:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
export function AudioVisualizer({ isRecording, mediaRecorder, width = 280, height = 60, barColor = '#a78bfa', barWidth = 3, gap = 2, }) {
|
|
5
|
+
const canvasRef = React.useRef(null);
|
|
6
|
+
const animationRef = React.useRef(null);
|
|
7
|
+
const analyserRef = React.useRef(null);
|
|
8
|
+
const audioContextRef = React.useRef(null);
|
|
9
|
+
const barCount = Math.floor(width / (barWidth + gap));
|
|
10
|
+
const drawIdle = React.useCallback(() => {
|
|
11
|
+
const canvas = canvasRef.current;
|
|
12
|
+
if (!canvas)
|
|
13
|
+
return;
|
|
14
|
+
const ctx = canvas.getContext('2d');
|
|
15
|
+
if (!ctx)
|
|
16
|
+
return;
|
|
17
|
+
ctx.clearRect(0, 0, width, height);
|
|
18
|
+
for (let i = 0; i < barCount; i++) {
|
|
19
|
+
const barHeight = Math.max(4, 4 + Math.sin(i * 0.3) * 3 + Math.sin(i * 0.7) * 2);
|
|
20
|
+
const x = i * (barWidth + gap);
|
|
21
|
+
const y = (height - barHeight) / 2;
|
|
22
|
+
ctx.fillStyle = 'rgba(167, 139, 250, 0.2)';
|
|
23
|
+
ctx.beginPath();
|
|
24
|
+
ctx.roundRect(x, y, barWidth, barHeight, barWidth / 2);
|
|
25
|
+
ctx.fill();
|
|
26
|
+
}
|
|
27
|
+
}, [width, height, barWidth, gap, barCount]);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
if (!isRecording || !mediaRecorder || mediaRecorder.state === 'inactive') {
|
|
30
|
+
drawIdle();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const audioContext = new AudioContext();
|
|
34
|
+
audioContextRef.current = audioContext;
|
|
35
|
+
const analyser = audioContext.createAnalyser();
|
|
36
|
+
analyser.fftSize = 256;
|
|
37
|
+
analyserRef.current = analyser;
|
|
38
|
+
const stream = mediaRecorder.stream;
|
|
39
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
40
|
+
source.connect(analyser);
|
|
41
|
+
const bufferLength = analyser.frequencyBinCount;
|
|
42
|
+
const dataArray = new Uint8Array(bufferLength);
|
|
43
|
+
const canvas = canvasRef.current;
|
|
44
|
+
if (!canvas)
|
|
45
|
+
return;
|
|
46
|
+
const ctx = canvas.getContext('2d');
|
|
47
|
+
if (!ctx)
|
|
48
|
+
return;
|
|
49
|
+
const draw = () => {
|
|
50
|
+
if (!analyserRef.current)
|
|
51
|
+
return;
|
|
52
|
+
animationRef.current = requestAnimationFrame(draw);
|
|
53
|
+
analyserRef.current.getByteFrequencyData(dataArray);
|
|
54
|
+
ctx.clearRect(0, 0, width, height);
|
|
55
|
+
const step = Math.floor(bufferLength / barCount);
|
|
56
|
+
for (let i = 0; i < barCount; i++) {
|
|
57
|
+
const dataIndex = i * step;
|
|
58
|
+
const value = dataArray[dataIndex] || 0;
|
|
59
|
+
const currentBarHeight = Math.max(4, (value / 255) * height * 0.9);
|
|
60
|
+
const x = i * (barWidth + gap);
|
|
61
|
+
const y = (height - currentBarHeight) / 2;
|
|
62
|
+
ctx.fillStyle = barColor;
|
|
63
|
+
ctx.beginPath();
|
|
64
|
+
ctx.roundRect(x, y, barWidth, currentBarHeight, barWidth / 2);
|
|
65
|
+
ctx.fill();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
draw();
|
|
69
|
+
return () => {
|
|
70
|
+
if (animationRef.current) {
|
|
71
|
+
cancelAnimationFrame(animationRef.current);
|
|
72
|
+
animationRef.current = null;
|
|
73
|
+
}
|
|
74
|
+
analyserRef.current = null;
|
|
75
|
+
if (audioContextRef.current) {
|
|
76
|
+
audioContextRef.current.close();
|
|
77
|
+
audioContextRef.current = null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}, [isRecording, mediaRecorder, width, height, barColor, barWidth, gap, barCount, drawIdle]);
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
if (!isRecording) {
|
|
83
|
+
drawIdle();
|
|
84
|
+
}
|
|
85
|
+
}, [isRecording, drawIdle]);
|
|
86
|
+
return _jsx("canvas", { ref: canvasRef, width: width, height: height, style: { width, height } });
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=audio-visualizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-visualizer.js","sourceRoot":"","sources":["../../../../src/client/components/audio-visualizer.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkD9B,MAAM,UAAU,eAAe,CAAC,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,GAAG,GAAG,EACX,MAAM,GAAG,EAAE,EACX,QAAQ,GAAG,SAAS,EACpB,QAAQ,GAAG,CAAC,EACZ,GAAG,GAAG,CAAC,GACc;IACrB,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAoB,IAAI,CAAC,CAAA;IACvD,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAgB,IAAI,CAAC,CAAA;IACtD,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAsB,IAAI,CAAC,CAAA;IAC3D,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM,CAAsB,IAAI,CAAC,CAAA;IAE/D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAA;IAGrD,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,MAAM;YAAE,OAAM;QAEnB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,CAAC,GAAG;YAAE,OAAM;QAEhB,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;QAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;YAChF,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAA;YAC9B,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;YAElC,GAAG,CAAC,SAAS,GAAG,0BAA0B,CAAA;YAC1C,GAAG,CAAC,SAAS,EAAE,CAAA;YACf,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAA;YACtD,GAAG,CAAC,IAAI,EAAE,CAAA;QACZ,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;IAG5C,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,WAAW,IAAI,CAAC,aAAa,IAAI,aAAa,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAEzE,QAAQ,EAAE,CAAA;YACV,OAAM;QACR,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,YAAY,EAAE,CAAA;QACvC,eAAe,CAAC,OAAO,GAAG,YAAY,CAAA;QACtC,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,EAAE,CAAA;QAC9C,QAAQ,CAAC,OAAO,GAAG,GAAG,CAAA;QACtB,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAA;QAE9B,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAA;QACnC,MAAM,MAAM,GAAG,YAAY,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAA;QAC3D,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAExB,MAAM,YAAY,GAAG,QAAQ,CAAC,iBAAiB,CAAA;QAC/C,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAA;QAE9C,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;QAChC,IAAI,CAAC,MAAM;YAAE,OAAM;QAEnB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,CAAC,GAAG;YAAE,OAAM;QAEhB,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,IAAI,CAAC,WAAW,CAAC,OAAO;gBAAE,OAAM;YAEhC,YAAY,CAAC,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAA;YAClD,WAAW,CAAC,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAA;YAEnD,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;YAElC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,QAAQ,CAAC,CAAA;YAEhD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;gBAClC,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,CAAA;gBAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;gBACvC,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,CAAA;gBAElE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAA;gBAC9B,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAA;gBAEzC,GAAG,CAAC,SAAS,GAAG,QAAQ,CAAA;gBACxB,GAAG,CAAC,SAAS,EAAE,CAAA;gBACf,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAA;gBAC7D,GAAG,CAAC,IAAI,EAAE,CAAA;YACZ,CAAC;QACH,CAAC,CAAA;QAED,IAAI,EAAE,CAAA;QAEN,OAAO,GAAG,EAAE;YACV,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;gBACzB,oBAAoB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;gBAC1C,YAAY,CAAC,OAAO,GAAG,IAAI,CAAA;YAC7B,CAAC;YACD,WAAW,CAAC,OAAO,GAAG,IAAI,CAAA;YAC1B,IAAI,eAAe,CAAC,OAAO,EAAE,CAAC;gBAC5B,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;gBAC/B,eAAe,CAAC,OAAO,GAAG,IAAI,CAAA;YAChC,CAAC;QACH,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAA;IAG5F,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,QAAQ,EAAE,CAAA;QACZ,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE3B,OAAO,iBAAQ,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAI,CAAA;AAC3F,CAAC"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Info, Loader2, Mic, MicOff, Send, Sparkles } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { Button } from "../../components/ui/button.js";
|
|
6
|
+
import { Textarea } from "../../components/ui/textarea.js";
|
|
7
|
+
import { cn } from "../../utils.js";
|
|
8
|
+
import { useSpeechToText } from "../hooks/use-speech-to-text.js";
|
|
9
|
+
import { convertToWav, getExtensionForMimeType, getSupportedMimeType } from "../utils/audio-utils.js";
|
|
10
|
+
import { AudioVisualizer } from "./audio-visualizer.js";
|
|
11
|
+
function renderMarkdown(text) {
|
|
12
|
+
const lines = text.split("\n");
|
|
13
|
+
return lines.map((line, lineIndex) => {
|
|
14
|
+
const bulletMatch = line.match(/^(\s*)(•|-)\s*(.*)$/);
|
|
15
|
+
let content;
|
|
16
|
+
if (bulletMatch) {
|
|
17
|
+
const [, indent, , bulletContent] = bulletMatch;
|
|
18
|
+
content = (_jsxs("span", { children: [indent, "\u2022 ", renderInlineMarkdown(bulletContent)] }));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
content = renderInlineMarkdown(line);
|
|
22
|
+
}
|
|
23
|
+
return (_jsxs(React.Fragment, { children: [lineIndex > 0 && _jsx("br", {}), content] }, lineIndex));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function renderInlineMarkdown(text) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
let remaining = text;
|
|
29
|
+
let keyCounter = 0;
|
|
30
|
+
while (remaining.length > 0) {
|
|
31
|
+
const boldMatch = remaining.match(/\*\*([^*]+)\*\*/);
|
|
32
|
+
const italicMatch = remaining.match(/(?<!\*)\*([^*]+)\*(?!\*)/);
|
|
33
|
+
let firstMatch = null;
|
|
34
|
+
if (boldMatch && italicMatch) {
|
|
35
|
+
if ((boldMatch.index ?? Infinity) <= (italicMatch.index ?? Infinity)) {
|
|
36
|
+
firstMatch = { match: boldMatch, type: "bold" };
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
firstMatch = { match: italicMatch, type: "italic" };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (boldMatch) {
|
|
43
|
+
firstMatch = { match: boldMatch, type: "bold" };
|
|
44
|
+
}
|
|
45
|
+
else if (italicMatch) {
|
|
46
|
+
firstMatch = { match: italicMatch, type: "italic" };
|
|
47
|
+
}
|
|
48
|
+
if (firstMatch && firstMatch.match.index !== undefined) {
|
|
49
|
+
if (firstMatch.match.index > 0) {
|
|
50
|
+
parts.push(remaining.slice(0, firstMatch.match.index));
|
|
51
|
+
}
|
|
52
|
+
if (firstMatch.type === "bold") {
|
|
53
|
+
parts.push(_jsx("strong", { className: "font-semibold", children: firstMatch.match[1] }, keyCounter++));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
parts.push(_jsx("em", { className: "italic", children: firstMatch.match[1] }, keyCounter++));
|
|
57
|
+
}
|
|
58
|
+
remaining = remaining.slice(firstMatch.match.index + firstMatch.match[0].length);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
parts.push(remaining);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return parts.length === 1 ? parts[0] : _jsx(_Fragment, { children: parts });
|
|
66
|
+
}
|
|
67
|
+
function MessageBubble({ message, isUser }) {
|
|
68
|
+
return (_jsx("div", { className: cn("flex w-full", isUser ? "justify-end" : "justify-start"), children: _jsxs("div", { className: cn("max-w-[85%] rounded-2xl px-4 py-2.5 text-sm", isUser
|
|
69
|
+
? "bg-primary text-primary-foreground rounded-br-md"
|
|
70
|
+
: "bg-muted text-muted-foreground rounded-bl-md"), children: [_jsx("div", { className: "whitespace-pre-wrap wrap-break-word", children: renderMarkdown(message.content) }), message.timestamp && (_jsx("span", { className: "mt-1 block text-[10px] opacity-60", children: new Date(message.timestamp).toLocaleTimeString([], {
|
|
71
|
+
hour: "2-digit",
|
|
72
|
+
minute: "2-digit",
|
|
73
|
+
}) }))] }) }));
|
|
74
|
+
}
|
|
75
|
+
export function ChatView({ messages, status, onSend, className, placeholder = "Describe what you want to fill in...", enableVoice = false, voiceEndpoint = "/api/speech-to-text", maxHeight, }) {
|
|
76
|
+
const [input, setInput] = React.useState("");
|
|
77
|
+
const messagesContainerRef = React.useRef(null);
|
|
78
|
+
const textareaRef = React.useRef(null);
|
|
79
|
+
const [isRecording, setIsRecording] = React.useState(false);
|
|
80
|
+
const [isProcessingAudio, setIsProcessingAudio] = React.useState(false);
|
|
81
|
+
const [mediaRecorder, setMediaRecorder] = React.useState(null);
|
|
82
|
+
const audioChunksRef = React.useRef([]);
|
|
83
|
+
const { transcribe, isLoading: isTranscribing } = useSpeechToText({
|
|
84
|
+
endpoint: voiceEndpoint,
|
|
85
|
+
onSuccess: (data) => {
|
|
86
|
+
if (data.text.trim()) {
|
|
87
|
+
setInput((prev) => (prev ? `${prev} ${data.text}` : data.text));
|
|
88
|
+
textareaRef.current?.focus();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (messagesContainerRef.current) {
|
|
94
|
+
messagesContainerRef.current.scrollTo({
|
|
95
|
+
top: messagesContainerRef.current.scrollHeight,
|
|
96
|
+
behavior: "smooth",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}, [messages]);
|
|
100
|
+
React.useEffect(() => {
|
|
101
|
+
if (messagesContainerRef.current) {
|
|
102
|
+
messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
|
|
103
|
+
}
|
|
104
|
+
}, []);
|
|
105
|
+
const startRecording = React.useCallback(async () => {
|
|
106
|
+
try {
|
|
107
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
108
|
+
const mimeType = getSupportedMimeType();
|
|
109
|
+
const recorder = mimeType
|
|
110
|
+
? new MediaRecorder(stream, { mimeType })
|
|
111
|
+
: new MediaRecorder(stream);
|
|
112
|
+
audioChunksRef.current = [];
|
|
113
|
+
recorder.ondataavailable = (event) => {
|
|
114
|
+
if (event.data.size > 0) {
|
|
115
|
+
audioChunksRef.current.push(event.data);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
recorder.onstop = async () => {
|
|
119
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
120
|
+
setMediaRecorder(null);
|
|
121
|
+
if (audioChunksRef.current.length > 0) {
|
|
122
|
+
setIsProcessingAudio(true);
|
|
123
|
+
try {
|
|
124
|
+
const actualMimeType = recorder.mimeType || mimeType || "audio/webm";
|
|
125
|
+
const audioBlob = new Blob(audioChunksRef.current, { type: actualMimeType });
|
|
126
|
+
let audioFile;
|
|
127
|
+
try {
|
|
128
|
+
const wavBlob = await convertToWav(audioBlob);
|
|
129
|
+
audioFile = new File([wavBlob], "recording.wav", { type: "audio/wav" });
|
|
130
|
+
}
|
|
131
|
+
catch (conversionError) {
|
|
132
|
+
console.warn("WAV conversion failed, using original format:", conversionError);
|
|
133
|
+
const extension = getExtensionForMimeType(actualMimeType);
|
|
134
|
+
audioFile = new File([audioBlob], `recording.${extension}`, { type: actualMimeType });
|
|
135
|
+
}
|
|
136
|
+
await transcribe({ audio: audioFile });
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
setIsProcessingAudio(false);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
recorder.start();
|
|
144
|
+
setMediaRecorder(recorder);
|
|
145
|
+
setIsRecording(true);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error("Failed to start recording:", error);
|
|
149
|
+
}
|
|
150
|
+
}, [transcribe]);
|
|
151
|
+
const stopRecording = React.useCallback(() => {
|
|
152
|
+
if (mediaRecorder && mediaRecorder.state !== "inactive") {
|
|
153
|
+
mediaRecorder.stop();
|
|
154
|
+
}
|
|
155
|
+
setIsRecording(false);
|
|
156
|
+
}, [mediaRecorder]);
|
|
157
|
+
const toggleRecording = React.useCallback(() => {
|
|
158
|
+
if (isRecording) {
|
|
159
|
+
stopRecording();
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
startRecording();
|
|
163
|
+
}
|
|
164
|
+
}, [isRecording, startRecording, stopRecording]);
|
|
165
|
+
const handleSend = () => {
|
|
166
|
+
if (input.trim() && status !== "filling") {
|
|
167
|
+
onSend(input.trim());
|
|
168
|
+
setInput("");
|
|
169
|
+
textareaRef.current?.focus();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const handleKeyDown = (e) => {
|
|
173
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
handleSend();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const isLoading = status === "filling";
|
|
179
|
+
const hasMessages = messages.length > 0;
|
|
180
|
+
const isVoiceProcessing = isRecording || isProcessingAudio || isTranscribing;
|
|
181
|
+
return (_jsxs("div", { className: cn("flex flex-col", className), children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto p-4 space-y-3", style: maxHeight ? { maxHeight, overflowY: "auto" } : undefined, children: [!hasMessages && (_jsxs("div", { className: "flex flex-col items-center justify-center h-full text-center text-muted-foreground", children: [_jsx(Sparkles, { className: "h-8 w-8 mb-3 text-primary/50" }), _jsx("p", { className: "text-sm font-medium", children: "AI Form Assistant" }), _jsx("p", { className: "text-xs mt-1", children: "Describe what you want to fill and I'll help you complete the form." })] })), messages.map((message, index) => (_jsx(MessageBubble, { message: message, isUser: message.role === "user" }, index))), isLoading && (_jsx("div", { className: "flex w-full justify-start", children: _jsx("div", { className: "rounded-2xl rounded-bl-md bg-muted px-4 py-3", children: _jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }) }))] }), hasMessages && !isLoading && (_jsxs("div", { className: "flex items-center justify-center gap-2 px-4 py-2 border-t bg-muted/30 text-muted-foreground", children: [_jsx(Info, { className: "h-3.5 w-3.5 shrink-0" }), _jsxs("span", { className: "text-xs", children: ["Switch to ", _jsx("strong", { children: "Edit" }), " tab to review, then ", _jsx("strong", { children: "Save" }), " to apply changes"] })] })), enableVoice && isRecording && (_jsxs("div", { className: "flex items-center justify-center gap-3 px-4 py-2 border-t bg-muted/50", children: [_jsx(AudioVisualizer, { isRecording: isRecording, mediaRecorder: mediaRecorder, width: 200, height: 40, barColor: "hsl(var(--primary))" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Recording..." })] })), enableVoice && (isProcessingAudio || isTranscribing) && !isRecording && (_jsxs("div", { className: "flex items-center justify-center gap-2 px-4 py-2 border-t bg-muted/50", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin text-primary" }), _jsx("span", { className: "text-xs text-muted-foreground", children: isProcessingAudio ? "Processing audio..." : "Transcribing..." })] })), _jsx("div", { className: "border-t p-4", children: _jsxs("div", { className: "flex items-center gap-2", children: [enableVoice && (_jsx(Button, { onClick: toggleRecording, disabled: isLoading || isTranscribing, size: "icon", variant: isRecording ? "destructive" : "outline", className: "shrink-0", title: isRecording ? "Stop recording" : "Start voice input", children: isRecording ? _jsx(MicOff, { className: "h-4 w-4" }) : _jsx(Mic, { className: "h-4 w-4" }) })), _jsx(Textarea, { ref: textareaRef, value: input, onChange: (e) => setInput(e.target.value), onKeyDown: handleKeyDown, placeholder: placeholder, disabled: isLoading || isVoiceProcessing, className: "min-h-11 max-h-32 resize-none" }), _jsx(Button, { onClick: handleSend, disabled: !input.trim() || isLoading || isVoiceProcessing, size: "icon", className: "shrink-0", children: isLoading ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin" })) : (_jsx(Send, { className: "h-4 w-4" })) })] }) })] }));
|
|
182
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-view.js","sourceRoot":"","sources":["../../../../src/client/components/chat-view.tsx"],"names":[],"mappings":"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"}
|