Made-with: Cursor
This commit is contained in:
@@ -9,6 +9,8 @@ export interface ChatMessagePayload {
|
||||
|
||||
interface StreamOptions {
|
||||
messages: ChatMessagePayload[];
|
||||
/** 与后端 OpenAI 兼容字段一致,不传则由服务端默认模型处理 */
|
||||
model?: string;
|
||||
onToken: (token: string) => void;
|
||||
signal?: AbortSignal;
|
||||
timeoutMs?: number;
|
||||
@@ -84,12 +86,15 @@ export async function streamQwenChat(options: StreamOptions): Promise<void> {
|
||||
const timeoutSignal = createTimeoutAbortSignal(options.timeoutMs ?? 90000);
|
||||
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",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ messages: options.messages }),
|
||||
body: JSON.stringify({
|
||||
messages: options.messages,
|
||||
...(options.model ? { model: options.model } : {}),
|
||||
}),
|
||||
signal: mergedSignal,
|
||||
openWhenHidden: true,
|
||||
async onopen(response) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ThunderboltOutlined,
|
||||
UserOutlined,
|
||||
} 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 StreamMessage from "../components/StreamMessage";
|
||||
|
||||
@@ -37,6 +37,14 @@ const STREAM_TIMEOUT_MS = 90000;
|
||||
const MAX_STREAM_RETRY = 1;
|
||||
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() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||
@@ -47,6 +55,7 @@ export default function HomePage() {
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [deepThink, setDeepThink] = useState(false);
|
||||
const [smartSearch, setSmartSearch] = useState(false);
|
||||
const [selectedModel, setSelectedModel] = useState(DEFAULT_QWEN_MODEL);
|
||||
const messageListRef = useRef<HTMLDivElement | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
@@ -136,6 +145,7 @@ export default function HomePage() {
|
||||
try {
|
||||
await streamQwenChat({
|
||||
messages: retryMessages,
|
||||
model: selectedModel,
|
||||
signal: controller.signal,
|
||||
timeoutMs: STREAM_TIMEOUT_MS,
|
||||
onToken: (token) => {
|
||||
@@ -318,9 +328,16 @@ export default function HomePage() {
|
||||
className="h-6 w-6 shrink-0 object-contain md:hidden"
|
||||
decoding="async"
|
||||
/>
|
||||
<Typography.Text className="text-[15px] font-medium text-neutral-800">
|
||||
国内前端主流 CI 工具
|
||||
</Typography.Text>
|
||||
<Select
|
||||
className="min-w-[190px]"
|
||||
variant="borderless"
|
||||
popupMatchSelectWidth={false}
|
||||
options={QWEN_MODEL_OPTIONS}
|
||||
value={selectedModel}
|
||||
onChange={setSelectedModel}
|
||||
disabled={isSending}
|
||||
aria-label="选择对话模型"
|
||||
/>
|
||||
</div>
|
||||
<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}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user