Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 17 additions & 31 deletions app/(main)/chats/[id]/chat-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import ArrowRightIcon from "@/components/icons/arrow-right";
import Spinner from "@/components/spinner";
import assert from "assert";
import TextareaAutosize from "@/components/textarea-autosize";
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState, useTransition } from "react";
import { createMessage } from "../../actions";
Expand All @@ -23,10 +23,6 @@ export default function ChatBox({
const didFocusOnce = useRef(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const [prompt, setPrompt] = useState("");
const textareaResizePrompt = prompt
.split("\n")
.map((text) => (text === "" ? "a" : text))
.join("\n");

useEffect(() => {
if (!textareaRef.current) return;
Expand Down Expand Up @@ -72,32 +68,22 @@ export default function ChatBox({
>
<fieldset className="w-full" disabled={disabled}>
<div className="relative flex rounded-lg border-4 border-gray-300 bg-white">
<div className="relative w-full">
<div className="w-full p-2">
<p className="invisible min-h-[48px] w-full whitespace-pre-wrap">
{textareaResizePrompt}
</p>
</div>
<textarea
ref={textareaRef}
placeholder="Follow up"
autoFocus={!disabled}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
required
name="prompt"
className="peer absolute inset-0 w-full resize-none bg-transparent p-2 placeholder-gray-500 focus:outline-none disabled:opacity-50"
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
const target = event.target;
if (!(target instanceof HTMLTextAreaElement)) return;
target.closest("form")?.requestSubmit();
}
}}
/>
</div>
<div className="pointer-events-none absolute inset-0 rounded peer-focus:outline peer-focus:outline-offset-0 peer-focus:outline-blue-500" />
<TextareaAutosize
ref={textareaRef}
placeholder="Follow up"
autoFocus={!disabled}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
required
name="prompt"
className="w-full bg-transparent p-2 placeholder-gray-500 focus:outline-none disabled:opacity-50"
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
event.currentTarget.closest("form")?.requestSubmit();
}
}}
/>

<div className="absolute bottom-1.5 right-1.5 flex has-[:disabled]:opacity-50">
<div className="pointer-events-none absolute inset-0 -bottom-[1px] rounded bg-blue-700" />
Expand Down
46 changes: 17 additions & 29 deletions app/(main)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useS3Upload } from "next-s3-upload";
import UploadIcon from "@/components/icons/upload-icon";
import { XCircleIcon } from "@heroicons/react/20/solid";
import { MODELS, SUGGESTED_PROMPTS } from "@/lib/constants";
import TextareaAutosize from "@/components/textarea-autosize";

export default function Home() {
const { setStreamPromise } = use(Context);
Expand Down Expand Up @@ -49,11 +50,6 @@ export default function Home() {
setScreenshotLoading(false);
};

const textareaResizePrompt = prompt
.split("\n")
.map((text) => (text === "" ? "a" : text))
.join("\n");

return (
<div className="relative flex grow flex-col">
<div className="absolute inset-0 flex justify-center">
Expand Down Expand Up @@ -162,30 +158,22 @@ export default function Home() {
</button>
</div>
)}
<div className="relative">
<div className="p-3">
<p className="invisible w-full whitespace-pre-wrap">
{textareaResizePrompt}
</p>
</div>
<textarea
placeholder="Build me a budgeting app..."
required
name="prompt"
rows={1}
className="peer absolute inset-0 w-full resize-none bg-transparent p-3 placeholder-gray-500 focus-visible:outline-none disabled:opacity-50"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
const target = event.target;
if (!(target instanceof HTMLTextAreaElement)) return;
target.closest("form")?.requestSubmit();
}
}}
/>
</div>

<TextareaAutosize
placeholder="Build me a budgeting app..."
required
name="prompt"
rows={1}
className="w-full bg-transparent p-3 placeholder-gray-500 focus-visible:outline-none disabled:opacity-50"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
event.currentTarget.closest("form")?.requestSubmit();
}
}}
/>
</div>
<div className="absolute bottom-2 left-2 right-2.5 flex items-center justify-between">
<div className="flex items-center gap-3">
Expand Down
42 changes: 42 additions & 0 deletions components/textarea-autosize.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import { ComponentProps, useEffect, useRef, useState } from "react";

export default function TextareaAutosize({
value,
defaultValue,
onChange,
ref,
...rest
}: ComponentProps<"textarea">) {
const internalRef = useRef<HTMLTextAreaElement>(null);
const [internalValue, setInternalValue] = useState(defaultValue || "");

const activeValue = value ?? internalValue;
const activeRef = ref && typeof ref !== "function" ? ref : internalRef;

useEffect(() => {
if (activeRef.current) {
// Reset height to auto to get the correct scrollHeight
activeRef.current.style.height = "auto";
// Set the height to the scrollHeight
activeRef.current.style.height = `${activeRef.current.scrollHeight}px`;
}
}, [activeRef, activeValue]);

return (
<textarea
value={activeValue}
onChange={
typeof value === "undefined"
? (e) => {
setInternalValue(e.target.value);
}
: onChange
}
ref={activeRef}
style={{ resize: "none" }}
{...rest}
/>
);
}