##2024/10/9 21:01:09:
使用 tailwind 完善 chagpt UI 界面
网站:
https://2024-09-26-vite-react-tailwind-chatgpt-demo.pages.dev/chatgpt
提交信息:
Commits on Oct 9, 2024
update ui padding
https://github.com/qyzhizi/2024-09-26-vite-react-tailwind-chatgpt-demo/commit/f60b4c35eaeb6c46df5b54d049a6ba50c76bb7ec
update chatgpt ui sticky
https://github.com/qyzhizi/2024-09-26-vite-react-tailwind-chatgpt-demo/commit/1acbce1784c24f590a1dae99da3dea424ef1eb65
update chatgpt ui
https://github.com/qyzhizi/2024-09-26-vite-react-tailwind-chatgpt-demo/commit/a5d3e91c87b4167b4109396a6f068925bf3a4477
@@ 实现聊天 上下文
使用一个 zustand库 存储 messages 列表,通过 messageLength 设置窗口长度,保持最新的消息,丢弃旧的超出窗口长度的消息。
import { create } from 'zustand';
const messageLength = -13 // 保持数组长度
const useChatStore = create((set) => ({
messages: [],
addMessage: (message) => set((state) => {
const newMessages = [...state.messages, message];
return { messages: newMessages.slice(messageLength) };
}),
}));
export { useChatStore };
使用 zustand库 遇到一个问题:@react zustand set 函数是异步调用的,调用
addMessage({ role: 'user', content: input });韩式时,涉及的变量messages 不会立即生效,如何解决异步问题
解决方法:
addMessage({ role: 'user', content: input });
setTimeout(async () => {
const { messages } = useChatStore.getState();
const result = await axios.post('/api/azurechatgpt',
{ messages: messages },
{ headers: { 'Content-Type': 'application/json' } }
);
// Further actions
}, 0); // Ensures it runs after the state update
先使用 setTimeout 将其中的函数 稍后执行,一般是放入事件循环的队列中,等到该函数执行时,前面的 message 变量已经更新了,为了保险一点,使用const { messages } = useChatStore.getState();
获取最新的状态,单独使用 const { messages } = useChatStore.getState();
而不用 setTimeout 也行。这里把两者都写上,为了体现 setTimeout 的特性。
messages 是最新的上下文,包括用户最新的提问,这里设置了前 13 条信息,一起提交到 一个 pages function 接口(azure openai )
@@ 调整 UI:用户的输入框固定在底部
让后 用户的输入框固定在底部
<div className="chatgptroot text-center h-screen overflow-auto">
<h2 className="text-2xl mb-10">ChatGPT</h2>
<div className="messages mt-2 p-2 border rounded bg-gray-100">
{renderMessages()}
</div>
<form onSubmit={handleSubmit} className="sticky bottom-0 bg-white">
<textarea
id="auto-resize-textarea"
className="border rounded p-2 w-full resize-none"
placeholder="Your question here..."
value={input}
onChange={(e) => setInput(e.target.value)}
rows="3" // 设置初始行数
/>
<button type="submit" className="ml-2 p-2 bg-blue-500 text-white rounded">Ask</button>
</form>
{errorResponse && (
<div className="mt-4 p-2 border rounded bg-gray-100">
<h3 className="font-bold">Response:</h3>
<p>{errorResponse}</p>
</div>
)}
</div>
关键的css sticky bottom-0
在 form 元素上设置 sticky ,bottom-0 表示当 form 元素滚动到父元素底部时,不再向下滚动。这里 from 元素中包含了 textarea 元素,form 元素的父元素是 chatgptroot ,它的 css 设置也比较关键:
h-screen overflow-auto
表示占用全屏空间,verflow-auto 表示高度超出后会出现溢出,伴随滚动条。而 verflow-auto 正是 sticky 所需要的,因为sticky元素的父元素需要有滚动的空间。
@@ 调整 UI:chatgpt 返回的文字需要保留空白字符串,并自动换行
chatgpt 返回代码需要保留空白字符串,因此渲染时需要使用 pre 元素
// 渲染消息列表
const renderMessages = () => {
return messages.map((message, index) => (
<div key={index} className={`border rounded p-2 ${message.role === 'user' ? 'bg-blue-100' : 'bg-green-100'}`}>
<pre className="text-left whitespace-pre-wrap">
{message.content}
</pre>
</div>
));
};
text-left whitespace-pre-wrap
是关键
@@ 调整 UI:useEffect 实现 textarea 随文字高度自动调整textarea的高度
useEffect(() => {
const maxHeight = 300; // 你可以根据需要调整最大高度
const textarea = document.getElementById('auto-resize-textarea');
const adjustHeight = () => {
textarea.style.height = 'auto'; // 先将高度设置为 auto,以便重新计算高度
textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px'; // 根据内容设置高度,但不超过最大高度
};
// 添加事件监听器
textarea.addEventListener('input', adjustHeight);
// 初始化时调整高度(例如预填充内容)
adjustHeight();
// 清理事件监听器
return () => {
textarea.removeEventListener('input', adjustHeight);
};
}, [input]); // 依赖 input,当 input 发生变化时触发
useEffect 是 react 的功能,当 input 变化时,执行注册的函数,在注册的函数中实现高度的自动调整
@@ 调整 UI:useEffect 实现页面自动滚动到最底部
useEffect(() => {
const chatGptRootContainer = document.querySelector('.chatgptroot');
chatGptRootContainer.scrollTop = chatGptRootContainer.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}, [messages]);
监听变化的对象是:messages
前两行表示 chatgptroot 的元素滚动到最底部,好处是当提交信息或者返回信息后,自动滚动到最底部
window.scrollTo(0, document.body.scrollHeight);
表示页面滚动到最底部,因为 chatgptroot 容器外面还有页面其他内容,保证 用户输入框显示在最底部,不被遮挡。