mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-15 21:43:45 +08:00
feat: thinking style optimize
This commit is contained in:
@@ -147,6 +147,7 @@ import {
|
||||
WebTranscriptionApi,
|
||||
} from "../utils/speech";
|
||||
import { FileInfo } from "../client/platforms/utils";
|
||||
import { ThinkingContent } from "./thinking-content";
|
||||
|
||||
const ttsPlayer = createTTSPlayer();
|
||||
|
||||
@@ -2151,6 +2152,7 @@ function _Chat() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!isUser && <ThinkingContent message={message} />}
|
||||
<div className={styles["chat-message-item"]}>
|
||||
<Markdown
|
||||
key={message.streaming ? "loading" : "done"}
|
||||
|
||||
106
app/components/thinking-content.module.scss
Normal file
106
app/components/thinking-content.module.scss
Normal file
@@ -0,0 +1,106 @@
|
||||
.thinking-container {
|
||||
position: relative;
|
||||
border: var(--border-in-light);
|
||||
border-radius: 10px;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: var(--card-shadow);
|
||||
|
||||
.thinking-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--white);
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: var(--border-in-light);
|
||||
|
||||
.thinking-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 12px;
|
||||
color: var(--black);
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.thinking-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 12px;
|
||||
color: var(--black);
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.thinking-content-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thinking-content {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
color: var(--black);
|
||||
overflow-wrap: break-word;
|
||||
scroll-behavior: smooth;
|
||||
max-height: 50px;
|
||||
overflow-y: hidden;
|
||||
position: relative;
|
||||
transition:
|
||||
max-height 0.3s ease,
|
||||
overflow-y 0.3s ease;
|
||||
padding: 10px;
|
||||
|
||||
.thinking-content-text {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
overflow-y: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.thinking-content-top,
|
||||
.thinking-content-bottom {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 30px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.thinking-content-top {
|
||||
top: 0;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--white) 0%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.thinking-content-bottom {
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
var(--white) 0%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
67
app/components/thinking-content.tsx
Normal file
67
app/components/thinking-content.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import clsx from "clsx";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { ChatMessage } from "../store";
|
||||
import Locale from "../locales";
|
||||
|
||||
import styles from "./thinking-content.module.scss";
|
||||
import MaxIcon from "../icons/max.svg";
|
||||
import MinIcon from "../icons/min.svg";
|
||||
|
||||
export function ThinkingContent({ message }: { message: ChatMessage }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const thinkingContentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const thinkingContent = message.reasoningContent;
|
||||
const isThinking =
|
||||
message.streaming && thinkingContent && thinkingContent.length > 0;
|
||||
|
||||
// Auto-scroll to bottom of thinking container
|
||||
useEffect(() => {
|
||||
if (isThinking && thinkingContentRef.current) {
|
||||
requestAnimationFrame(() => {
|
||||
if (thinkingContentRef.current) {
|
||||
thinkingContentRef.current.scrollTop =
|
||||
thinkingContentRef.current.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [thinkingContent, isThinking, expanded]);
|
||||
|
||||
if (!thinkingContent) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
styles["thinking-container"],
|
||||
expanded && styles["expanded"],
|
||||
)}
|
||||
>
|
||||
<div className={styles["thinking-header"]}>
|
||||
<div className={styles["thinking-title"]}>
|
||||
{Locale.Chat.Thinking.Title}
|
||||
</div>
|
||||
<div
|
||||
className={styles["thinking-toggle"]}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
{expanded ? <MinIcon /> : <MaxIcon />}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["thinking-content-wrapper"]}>
|
||||
{!expanded && <div className={styles["thinking-content-top"]}></div>}
|
||||
<div
|
||||
className={clsx(
|
||||
styles["thinking-content"],
|
||||
expanded && styles["expanded"],
|
||||
)}
|
||||
ref={thinkingContentRef}
|
||||
>
|
||||
<div className={styles["thinking-content-text"]}>
|
||||
{thinkingContent}
|
||||
</div>
|
||||
</div>
|
||||
{!expanded && <div className={styles["thinking-content-bottom"]}></div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user