feat:增加重置功能
This commit is contained in:
130
src/App.js
130
src/App.js
@@ -23,6 +23,8 @@ export default function App() {
|
|||||||
const [isSending, setIsSending] = useState(false);
|
const [isSending, setIsSending] = useState(false);
|
||||||
const [isBotTyping, setIsBotTyping] = useState(false);
|
const [isBotTyping, setIsBotTyping] = useState(false);
|
||||||
|
|
||||||
|
const [rethinkTargetIdx, setRethinkTargetIdx] = useState(null);
|
||||||
|
|
||||||
const bottomRef = useRef(null);
|
const bottomRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -67,6 +69,49 @@ export default function App() {
|
|||||||
setIsSending(false);
|
setIsSending(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRethink(targetIdx) {
|
||||||
|
if (isSending || isBotTyping) return;
|
||||||
|
const target = messages[targetIdx];
|
||||||
|
if (!target || target.role !== "assistant") return;
|
||||||
|
|
||||||
|
setRethinkTargetIdx(targetIdx);
|
||||||
|
setIsBotTyping(true);
|
||||||
|
|
||||||
|
// 把目标消息内容替换成占位文本
|
||||||
|
setMessages((prev) => {
|
||||||
|
const next = [...prev];
|
||||||
|
next[targetIdx] = { ...next[targetIdx], content: "正在重新思考…" };
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 用“该条 assistant 之前的历史” + 重新思考指令
|
||||||
|
const before = messages.slice(0, targetIdx);
|
||||||
|
const historyForModel = [
|
||||||
|
{ role: "system", content: "你是一个简洁的助手,用中文回答。" },
|
||||||
|
...before.map(({ role, content }) => ({ role, content })),
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: "我对你刚才的回答不满意。请重新思考并改进:更准确、更有条理;如有错误请纠正;尽量避免重复原句。",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let newReply = "";
|
||||||
|
try {
|
||||||
|
newReply = await requestBotReply(historyForModel);
|
||||||
|
} catch (e) {
|
||||||
|
newReply = "重新思考失败了,请稍后再试。";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 覆盖原来的那条 assistant 内容
|
||||||
|
setMessages((prev) => {
|
||||||
|
const next = [...prev];
|
||||||
|
next[targetIdx] = { ...next[targetIdx], content: newReply };
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
setIsBotTyping(false);
|
||||||
|
setRethinkTargetIdx(null);
|
||||||
|
}
|
||||||
|
|
||||||
function handleKeyDown(e) {
|
function handleKeyDown(e) {
|
||||||
if (e.key === "Enter" && !e.shiftKey) {
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -78,26 +123,29 @@ export default function App() {
|
|||||||
<div className="min-h-screen bg-gradient-to-b from-background to-content2/50 p-4">
|
<div className="min-h-screen bg-gradient-to-b from-background to-content2/50 p-4">
|
||||||
<div className="mx-auto max-w-3xl">
|
<div className="mx-auto max-w-3xl">
|
||||||
<Card className="shadow-xl">
|
<Card className="shadow-xl">
|
||||||
{/* 顶部 */}
|
<CardHeader>
|
||||||
<CardHeader className="flex items-center">
|
<span className="font-semibold">Chatbot Demo</span>
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="font-semibold">Chatbot Demo</span>
|
|
||||||
<span className="text-xs text-default-500">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
{/* 聊天区域 */}
|
|
||||||
<CardBody className="p-0">
|
<CardBody className="p-0">
|
||||||
<ScrollShadow className="h-[520px] px-4 py-4">
|
<ScrollShadow className="h-[520px] px-4 py-4">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-4">
|
||||||
{messages.map((m) => {
|
{messages.map((m, idx) => {
|
||||||
const isUser = m.role === "user";
|
const isUser = m.role === "user";
|
||||||
|
const isAssistant = m.role === "assistant";
|
||||||
|
const isInitialGreeting = idx === 0;
|
||||||
|
const canRethink = isAssistant && !isInitialGreeting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex ${isUser ? "justify-end" : "justify-start"}`} >
|
<div
|
||||||
<div className={`flex max-w-[80%] ${isUser ? "flex-row-reverse" : ""}`} >
|
key={idx}
|
||||||
|
className={`flex ${isUser ? "justify-end" : "justify-start"}`}
|
||||||
|
>
|
||||||
|
{/* 以气泡为参照:按钮放在气泡“下面的右侧” */}
|
||||||
|
<div className="relative max-w-[80%]">
|
||||||
|
{/* 气泡 */}
|
||||||
<div
|
<div
|
||||||
className={`rounded-2xl px-4 py-3 text-sm leading-relaxed ${isUser
|
className={`rounded-2xl px-4 py-3 text-sm leading-relaxed ${isUser
|
||||||
? "bg-primary text-primary-foreground"
|
? "bg-primary text-primary-foreground"
|
||||||
@@ -106,18 +154,51 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
{m.content}
|
{m.content}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 在气泡下方*/}
|
||||||
|
{canRethink && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="重新思考"
|
||||||
|
disabled={isSending || isBotTyping}
|
||||||
|
onClick={() => handleRethink(idx)}
|
||||||
|
className="
|
||||||
|
absolute
|
||||||
|
right-0
|
||||||
|
top-full
|
||||||
|
mt-2
|
||||||
|
h-7
|
||||||
|
w-7
|
||||||
|
rounded-full
|
||||||
|
border
|
||||||
|
border-default-200
|
||||||
|
bg-background
|
||||||
|
text-default-500
|
||||||
|
text-sm
|
||||||
|
shadow-sm
|
||||||
|
hover:bg-content2
|
||||||
|
hover:text-foreground
|
||||||
|
disabled:opacity-40
|
||||||
|
disabled:cursor-not-allowed
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span className={rethinkTargetIdx === idx ? "inline-block animate-spin" : ""}>
|
||||||
|
↻
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{isBotTyping && (
|
|
||||||
|
{/* 全局“正在回复…” */}
|
||||||
|
{isBotTyping && rethinkTargetIdx === null && (
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center gap-2 rounded-2xl bg-content2 px-4 py-2 text-sm text-default-600">
|
||||||
<div className="flex items-center gap-2 rounded-2xl bg-content2 px-4 py-2 text-sm text-default-600">
|
<Spinner size="sm" />
|
||||||
<Spinner size="sm" />
|
正在回复…
|
||||||
正在回复…
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -130,35 +211,32 @@ export default function App() {
|
|||||||
|
|
||||||
{/* 输入区 */}
|
{/* 输入区 */}
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
{/* 这个容器视觉上就是“一个输入框” */}
|
|
||||||
<div className="rounded-xl border border-default-200 bg-background shadow-sm">
|
<div className="rounded-xl border border-default-200 bg-background shadow-sm">
|
||||||
{/* 上半部分:真正可滚动/可拖拉的 textarea */}
|
|
||||||
<div className="px-3 pt-3">
|
<div className="px-3 pt-3">
|
||||||
<textarea
|
<textarea
|
||||||
value={inputText}
|
value={inputText}
|
||||||
onChange={(e) => setInputText(e.target.value)}
|
onChange={(e) => setInputText(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="输入消息,Enter 发送,Shift+Enter 换行"
|
placeholder="输入消息,Enter 发送,Shift+Enter 换行"
|
||||||
disabled={isSending}
|
disabled={isSending || isBotTyping}
|
||||||
rows={2}
|
rows={2}
|
||||||
className="
|
className="
|
||||||
w-full bg-transparent outline-none
|
w-full bg-transparent outline-none
|
||||||
text-sm leading-relaxed text-foreground
|
text-sm leading-relaxed text-foreground
|
||||||
placeholder:text-default-400
|
placeholder:text-default-400
|
||||||
resize-y
|
resize-none
|
||||||
min-h-[72px] max-h-[220px]
|
min-h-[72px] max-h-[220px]
|
||||||
overflow-auto
|
overflow-auto
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 下半部分:输入框“内部”的工具栏,按钮在右下角 */}
|
<div className="flex justify-end px-3 py-2">
|
||||||
<div className="flex items-center justify-end gap-2 px-3 py-2">
|
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onPress={handleSend}
|
onPress={handleSend}
|
||||||
isDisabled={isSending || !inputText.trim()}
|
isDisabled={isSending || isBotTyping || !inputText.trim()}
|
||||||
>
|
>
|
||||||
{isSending ? "发送中…" : "发送"}
|
{isSending ? "发送中…" : "发送"}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -170,4 +248,4 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user