@sero-ai/ui 0.1.0 → 0.2.0
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/.turbo/turbo-typecheck.log +1 -1
- package/package.json +6 -5
- package/src/components/ai-elements/prompt-input-context.tsx +252 -0
- package/src/components/ai-elements/prompt-input-elements.tsx +371 -0
- package/src/components/ai-elements/prompt-input-textarea.tsx +117 -0
- package/src/components/ai-elements/prompt-input.tsx +59 -990
- package/src/components/ai-elements/voice-selector-accent.tsx +50 -0
- package/src/components/ai-elements/voice-selector.tsx +6 -175
- package/src/components/model-selection/available-model-picker.tsx +231 -0
- package/src/components/model-selection/model-warning-list.tsx +33 -0
- package/src/components/model-selection/thinking-level-picker.tsx +55 -0
- package/src/components/ui/alert-dialog.tsx +1 -1
- package/src/components/ui/command.tsx +1 -1
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/plugin-safety-disclaimer.tsx +25 -0
- package/src/components/ui/search-input.tsx +40 -0
- package/src/index.ts +125 -7
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ChangeEvent,
|
|
5
|
+
ChangeEventHandler,
|
|
6
|
+
ClipboardEventHandler,
|
|
7
|
+
ComponentProps,
|
|
8
|
+
KeyboardEventHandler,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { InputGroupTextarea } from "../ui/input-group";
|
|
11
|
+
import { cn } from "../../lib/utils";
|
|
12
|
+
import { useCallback, useState } from "react";
|
|
13
|
+
import {
|
|
14
|
+
useOptionalPromptInputController,
|
|
15
|
+
usePromptInputAttachments,
|
|
16
|
+
} from "./prompt-input-context";
|
|
17
|
+
|
|
18
|
+
export type PromptInputTextareaProps = ComponentProps<typeof InputGroupTextarea>;
|
|
19
|
+
|
|
20
|
+
export const PromptInputTextarea = ({
|
|
21
|
+
onChange,
|
|
22
|
+
onKeyDown,
|
|
23
|
+
className,
|
|
24
|
+
placeholder = "What would you like to know?",
|
|
25
|
+
...props
|
|
26
|
+
}: PromptInputTextareaProps) => {
|
|
27
|
+
const controller = useOptionalPromptInputController();
|
|
28
|
+
const attachments = usePromptInputAttachments();
|
|
29
|
+
const [isComposing, setIsComposing] = useState(false);
|
|
30
|
+
|
|
31
|
+
const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = useCallback(
|
|
32
|
+
(e) => {
|
|
33
|
+
// Call the external onKeyDown handler first
|
|
34
|
+
onKeyDown?.(e);
|
|
35
|
+
|
|
36
|
+
// If the external handler prevented default, don't run internal logic
|
|
37
|
+
if (e.defaultPrevented) return;
|
|
38
|
+
|
|
39
|
+
if (e.key === "Enter") {
|
|
40
|
+
if (isComposing || e.nativeEvent.isComposing) return;
|
|
41
|
+
if (e.shiftKey) return;
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
|
|
44
|
+
// Check if the submit button is disabled before submitting
|
|
45
|
+
const { form } = e.currentTarget;
|
|
46
|
+
const submitButton = form?.querySelector(
|
|
47
|
+
'button[type="submit"]'
|
|
48
|
+
) as HTMLButtonElement | null;
|
|
49
|
+
if (submitButton?.disabled) return;
|
|
50
|
+
|
|
51
|
+
form?.requestSubmit();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Remove last attachment when Backspace is pressed and textarea is empty
|
|
55
|
+
if (
|
|
56
|
+
e.key === "Backspace" &&
|
|
57
|
+
e.currentTarget.value === "" &&
|
|
58
|
+
attachments.files.length > 0
|
|
59
|
+
) {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
const lastAttachment = attachments.files.at(-1);
|
|
62
|
+
if (lastAttachment) {
|
|
63
|
+
attachments.remove(lastAttachment.id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
[onKeyDown, isComposing, attachments]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const handlePaste: ClipboardEventHandler<HTMLTextAreaElement> = useCallback(
|
|
71
|
+
(event) => {
|
|
72
|
+
const items = event.clipboardData?.items;
|
|
73
|
+
if (!items) return;
|
|
74
|
+
|
|
75
|
+
const files: File[] = [];
|
|
76
|
+
for (const item of items) {
|
|
77
|
+
if (item.kind === "file") {
|
|
78
|
+
const file = item.getAsFile();
|
|
79
|
+
if (file) files.push(file);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (files.length > 0) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
attachments.add(files);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
[attachments]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const handleCompositionEnd = useCallback(() => setIsComposing(false), []);
|
|
92
|
+
const handleCompositionStart = useCallback(() => setIsComposing(true), []);
|
|
93
|
+
|
|
94
|
+
const controlledProps = controller
|
|
95
|
+
? {
|
|
96
|
+
onChange: (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
97
|
+
controller.textInput.setInput(e.currentTarget.value);
|
|
98
|
+
onChange?.(e as Parameters<ChangeEventHandler<HTMLTextAreaElement>>[0]);
|
|
99
|
+
},
|
|
100
|
+
value: controller.textInput.value,
|
|
101
|
+
}
|
|
102
|
+
: { onChange };
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<InputGroupTextarea
|
|
106
|
+
className={cn("field-sizing-content max-h-48 min-h-16", className)}
|
|
107
|
+
name="message"
|
|
108
|
+
onCompositionEnd={handleCompositionEnd}
|
|
109
|
+
onCompositionStart={handleCompositionStart}
|
|
110
|
+
onKeyDown={handleKeyDown}
|
|
111
|
+
onPaste={handlePaste}
|
|
112
|
+
placeholder={placeholder}
|
|
113
|
+
{...props}
|
|
114
|
+
{...controlledProps}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
};
|