Made-with: Cursor
This commit is contained in:
@@ -9,6 +9,8 @@ export interface ChatMessagePayload {
|
|||||||
|
|
||||||
interface StreamOptions {
|
interface StreamOptions {
|
||||||
messages: ChatMessagePayload[];
|
messages: ChatMessagePayload[];
|
||||||
|
/** 与后端 OpenAI 兼容字段一致,不传则由服务端默认模型处理 */
|
||||||
|
model?: string;
|
||||||
onToken: (token: string) => void;
|
onToken: (token: string) => void;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
@@ -84,12 +86,15 @@ export async function streamQwenChat(options: StreamOptions): Promise<void> {
|
|||||||
const timeoutSignal = createTimeoutAbortSignal(options.timeoutMs ?? 90000);
|
const timeoutSignal = createTimeoutAbortSignal(options.timeoutMs ?? 90000);
|
||||||
const mergedSignal = mergeAbortSignals([options.signal, timeoutSignal]);
|
const mergedSignal = mergeAbortSignals([options.signal, timeoutSignal]);
|
||||||
|
|
||||||
await fetchEventSource(`${API_BASE_URL}/api/qwen/chat/stream`, {
|
await fetchEventSource(`${API_BASE_URL}/api/client/v1/chat/completions/stream`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ messages: options.messages }),
|
body: JSON.stringify({
|
||||||
|
messages: options.messages,
|
||||||
|
...(options.model ? { model: options.model } : {}),
|
||||||
|
}),
|
||||||
signal: mergedSignal,
|
signal: mergedSignal,
|
||||||
openWhenHidden: true,
|
openWhenHidden: true,
|
||||||
async onopen(response) {
|
async onopen(response) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
ThunderboltOutlined,
|
ThunderboltOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Button, Collapse, Drawer, Input, Layout, Typography } from "antd";
|
import { Button, Collapse, Drawer, Input, Layout, Select, Typography } from "antd";
|
||||||
import { isAbortLikeError, streamQwenChat, type ChatMessagePayload } from "../api/qwenChat";
|
import { isAbortLikeError, streamQwenChat, type ChatMessagePayload } from "../api/qwenChat";
|
||||||
import StreamMessage from "../components/StreamMessage";
|
import StreamMessage from "../components/StreamMessage";
|
||||||
|
|
||||||
@@ -37,6 +37,14 @@ const STREAM_TIMEOUT_MS = 90000;
|
|||||||
const MAX_STREAM_RETRY = 1;
|
const MAX_STREAM_RETRY = 1;
|
||||||
const CONTEXT_WINDOW_SIZE = 8;
|
const CONTEXT_WINDOW_SIZE = 8;
|
||||||
|
|
||||||
|
/** 展示名与请求 model 字段;需与后端实际支持的模型 id 一致 */
|
||||||
|
const DEFAULT_QWEN_MODEL = "qwen3.5-flash";
|
||||||
|
const QWEN_MODEL_OPTIONS: { value: string; label: string }[] = [
|
||||||
|
{ value: "qwen3-max", label: "Qwen3-Max" },
|
||||||
|
{ value: "qwen3.6-plus", label: "Qwen3.6-Plus" },
|
||||||
|
{ value: DEFAULT_QWEN_MODEL, label: "Qwen3.5-Flash" },
|
||||||
|
];
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||||
@@ -47,6 +55,7 @@ export default function HomePage() {
|
|||||||
const [isSending, setIsSending] = useState(false);
|
const [isSending, setIsSending] = useState(false);
|
||||||
const [deepThink, setDeepThink] = useState(false);
|
const [deepThink, setDeepThink] = useState(false);
|
||||||
const [smartSearch, setSmartSearch] = useState(false);
|
const [smartSearch, setSmartSearch] = useState(false);
|
||||||
|
const [selectedModel, setSelectedModel] = useState(DEFAULT_QWEN_MODEL);
|
||||||
const messageListRef = useRef<HTMLDivElement | null>(null);
|
const messageListRef = useRef<HTMLDivElement | null>(null);
|
||||||
const abortRef = useRef<AbortController | null>(null);
|
const abortRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
@@ -136,6 +145,7 @@ export default function HomePage() {
|
|||||||
try {
|
try {
|
||||||
await streamQwenChat({
|
await streamQwenChat({
|
||||||
messages: retryMessages,
|
messages: retryMessages,
|
||||||
|
model: selectedModel,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
timeoutMs: STREAM_TIMEOUT_MS,
|
timeoutMs: STREAM_TIMEOUT_MS,
|
||||||
onToken: (token) => {
|
onToken: (token) => {
|
||||||
@@ -318,9 +328,16 @@ export default function HomePage() {
|
|||||||
className="h-6 w-6 shrink-0 object-contain md:hidden"
|
className="h-6 w-6 shrink-0 object-contain md:hidden"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
<Typography.Text className="text-[15px] font-medium text-neutral-800">
|
<Select
|
||||||
国内前端主流 CI 工具
|
className="min-w-[190px]"
|
||||||
</Typography.Text>
|
variant="borderless"
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
options={QWEN_MODEL_OPTIONS}
|
||||||
|
value={selectedModel}
|
||||||
|
onChange={setSelectedModel}
|
||||||
|
disabled={isSending}
|
||||||
|
aria-label="选择对话模型"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="rounded-full border border-[var(--ds-border)] bg-neutral-50 px-3 py-1 text-xs text-neutral-500">
|
<span className="rounded-full border border-[var(--ds-border)] bg-neutral-50 px-3 py-1 text-xs text-neutral-500">
|
||||||
快速模式
|
快速模式
|
||||||
@@ -377,7 +394,21 @@ export default function HomePage() {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.role === "assistant" && <StreamMessage content={item.content} />}
|
{item.role === "assistant" &&
|
||||||
|
(item.content ? (
|
||||||
|
<StreamMessage content={item.content} />
|
||||||
|
) : (
|
||||||
|
<div className="rounded-2xl border border-[var(--ds-border)] bg-neutral-50/80 px-4 py-3 text-[14px] text-neutral-500">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>正在回复</span>
|
||||||
|
<span className="inline-flex items-center gap-1">
|
||||||
|
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-neutral-400 [animation-delay:-0.3s]" />
|
||||||
|
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-neutral-400 [animation-delay:-0.15s]" />
|
||||||
|
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-neutral-400" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
{item.role === "user" && item.content}
|
{item.role === "user" && item.content}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user