- Add chat components (header, composer, conversation, message, thinking response, identity) - Add chat hooks (use-chat-agent, use-chat-composer) - Add chat lib (constants, transforms, types) - Wire agent model config through env (provider, name, api, base url, key, context window, max tokens) - Refresh web styling and routes
59 lines
1.4 KiB
TypeScript
59 lines
1.4 KiB
TypeScript
import type { ChangeEvent, FormEvent, KeyboardEvent } from "react";
|
|
import { useRef, useState } from "react";
|
|
|
|
interface UseChatComposerOptions {
|
|
busy: boolean;
|
|
onSend: (message: string) => Promise<void>;
|
|
}
|
|
|
|
export const useChatComposer = ({ busy, onSend }: UseChatComposerOptions) => {
|
|
const [draft, setDraft] = useState("");
|
|
const [isFocused, setIsFocused] = useState(false);
|
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
const canSend = draft.trim().length > 0 && !busy;
|
|
|
|
const handleSubmit = async (event?: FormEvent) => {
|
|
event?.preventDefault();
|
|
const message = draft.trim();
|
|
if (!message || busy) {
|
|
return;
|
|
}
|
|
|
|
setDraft("");
|
|
try {
|
|
await onSend(message);
|
|
} finally {
|
|
textareaRef.current?.focus();
|
|
}
|
|
};
|
|
|
|
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
if (
|
|
event.key === "Enter" &&
|
|
!event.shiftKey &&
|
|
!event.nativeEvent.isComposing
|
|
) {
|
|
event.preventDefault();
|
|
void handleSubmit();
|
|
}
|
|
};
|
|
|
|
const inputProps = {
|
|
onBlur: () => setIsFocused(false),
|
|
onChange: (event: ChangeEvent<HTMLTextAreaElement>) =>
|
|
setDraft(event.target.value),
|
|
onFocus: () => setIsFocused(true),
|
|
onKeyDown: handleKeyDown,
|
|
ref: textareaRef,
|
|
value: draft,
|
|
};
|
|
|
|
return {
|
|
canSend,
|
|
draft,
|
|
handleSubmit,
|
|
inputProps,
|
|
isFocused,
|
|
};
|
|
};
|