fix: model arguments fixed
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { User, Loader2, ArrowUp, ChevronDown, Check, Square, Plus, Database, Wand2, Zap, CheckCircle2, Table, XCircle, Settings, ExternalLink, FileText, Download, Eye } from "lucide-react";
|
||||
import { User, Loader2, ArrowUp, ChevronDown, Check, Square, Plus, Database, Wand2, Zap, CheckCircle2, Table, XCircle, Settings, ExternalLink, FileText, Download, Eye, Copy } from "lucide-react";
|
||||
import { api } from "@/lib/api";
|
||||
import { type ChartSpec } from "@/store/visualizationStore";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
@@ -25,6 +25,11 @@ interface Message {
|
||||
progressLogs?: string[];
|
||||
routeInfo?: string;
|
||||
reasoningContent?: string;
|
||||
usage?: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
artifacts?: MessageArtifact[];
|
||||
}
|
||||
|
||||
@@ -183,6 +188,8 @@ export function ChatInterface() {
|
||||
const [selectedSkillIds, setSelectedSkillIds] = useState<string[]>([]);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [artifactPreview, setArtifactPreview] = useState<ArtifactPreviewTarget | null>(null);
|
||||
const [collapsedThinkingByMessage, setCollapsedThinkingByMessage] = useState<Record<string, boolean>>({});
|
||||
const [thinkingCopiedByMessage, setThinkingCopiedByMessage] = useState<Record<string, boolean>>({});
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const location = useLocation();
|
||||
const { currentProject } = useProjectStore();
|
||||
@@ -372,6 +379,8 @@ export function ChatInterface() {
|
||||
role: m.role as 'user' | 'assistant',
|
||||
content: cleanContent,
|
||||
viz: m.viz ? buildMessageViz(m.viz) : undefined,
|
||||
reasoningContent: typeof m.reasoning_content === "string" ? m.reasoning_content : undefined,
|
||||
usage: m.usage,
|
||||
artifacts: normalizeArtifacts(m.artifacts),
|
||||
};
|
||||
});
|
||||
@@ -489,6 +498,22 @@ export function ChatInterface() {
|
||||
|
||||
const selectedDataSourceName = availableDataSources.find(ds => ds.id === selectedDataSource)?.name || "";
|
||||
const selectedSkills = availableSkills.filter(skill => selectedSkillIds.includes(skill.id));
|
||||
const isThinkingCollapsed = (messageId: string) => collapsedThinkingByMessage[messageId] ?? true;
|
||||
const toggleThinkingCollapsed = (messageId: string) => {
|
||||
setCollapsedThinkingByMessage((prev) => ({ ...prev, [messageId]: !(prev[messageId] ?? true) }));
|
||||
};
|
||||
const copyThinkingContent = async (messageId: string, content: string) => {
|
||||
if (!content.trim()) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(content);
|
||||
setThinkingCopiedByMessage((prev) => ({ ...prev, [messageId]: true }));
|
||||
window.setTimeout(() => {
|
||||
setThinkingCopiedByMessage((prev) => ({ ...prev, [messageId]: false }));
|
||||
}, 1200);
|
||||
} catch (e) {
|
||||
console.error("Failed to copy thinking content", e);
|
||||
}
|
||||
};
|
||||
|
||||
const renderActiveSelections = () => {
|
||||
if (!selectedDataSource && selectedSkills.length === 0) return null;
|
||||
@@ -613,9 +638,7 @@ export function ChatInterface() {
|
||||
if (msg.id !== assistantId) return msg;
|
||||
|
||||
if (isReasoningToken) {
|
||||
// 对于流式推理内容,拼接而不是创建新条目
|
||||
const currentReasoning = msg.reasoningContent || "";
|
||||
return { ...msg, reasoningContent: currentReasoning + text };
|
||||
return msg;
|
||||
} else {
|
||||
// 对于普通的阶段性日志,取消 8 条限制,允许滚动查看所有历史
|
||||
const current = msg.progressLogs || [];
|
||||
@@ -673,6 +696,35 @@ export function ChatInterface() {
|
||||
let hasDonePayload = false;
|
||||
let rafPending = false;
|
||||
let renderedText = "";
|
||||
let reasoningBuffer = "";
|
||||
let reasoningRafPending = false;
|
||||
|
||||
const flushReasoning = (force = false) => {
|
||||
if (!reasoningBuffer) return;
|
||||
if (force) {
|
||||
const content = reasoningBuffer;
|
||||
reasoningBuffer = "";
|
||||
setMessagesForSession(targetSessionKey, (prev) =>
|
||||
prev.map((msg) =>
|
||||
msg.id === assistantId ? { ...msg, reasoningContent: (msg.reasoningContent || "") + content } : msg
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (reasoningRafPending) return;
|
||||
reasoningRafPending = true;
|
||||
requestAnimationFrame(() => {
|
||||
reasoningRafPending = false;
|
||||
if (!reasoningBuffer) return;
|
||||
const content = reasoningBuffer;
|
||||
reasoningBuffer = "";
|
||||
setMessagesForSession(targetSessionKey, (prev) =>
|
||||
prev.map((msg) =>
|
||||
msg.id === assistantId ? { ...msg, reasoningContent: (msg.reasoningContent || "") + content } : msg
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const flushAssistant = (force = false) => {
|
||||
if (streamedText === renderedText && !force) return;
|
||||
@@ -717,6 +769,7 @@ export function ChatInterface() {
|
||||
type: string;
|
||||
content?: string;
|
||||
is_reasoning?: boolean;
|
||||
tool_hint?: boolean;
|
||||
sql?: string;
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
@@ -724,6 +777,12 @@ export function ChatInterface() {
|
||||
reason?: string;
|
||||
chart?: { chart_spec?: ChartSpec | null; reasoning?: string; can_visualize?: boolean; chart_type?: string } | null;
|
||||
artifacts?: unknown;
|
||||
reasoning_content?: string;
|
||||
usage?: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
};
|
||||
|
||||
if (payload.type === "delta" && payload.content) {
|
||||
@@ -743,9 +802,13 @@ export function ChatInterface() {
|
||||
}
|
||||
|
||||
if (payload.type === "progress" && payload.content) {
|
||||
// 如果 progress 内容带有空格或者换行,并且不是典型的系统提示词,很可能这是 reasoning_content
|
||||
// 为了安全起见,我们在后端应该加上 is_reasoning 标记,这里我们通过启发式或者统一拼接
|
||||
pushProgressLog(payload.content, payload.is_reasoning || false);
|
||||
if (payload.is_reasoning || payload.tool_hint) {
|
||||
const nextLine = payload.content.endsWith("\n") ? payload.content : `${payload.content}\n`;
|
||||
reasoningBuffer += nextLine;
|
||||
flushReasoning(false);
|
||||
} else {
|
||||
pushProgressLog(payload.content, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.type === "final") {
|
||||
@@ -753,12 +816,22 @@ export function ChatInterface() {
|
||||
if (typeof payload.content === "string") {
|
||||
streamedText = payload.content;
|
||||
}
|
||||
if (typeof payload.reasoning_content === "string") {
|
||||
reasoningBuffer = "";
|
||||
setMessagesForSession(targetSessionKey, (prev) =>
|
||||
prev.map((msg) =>
|
||||
msg.id === assistantId ? { ...msg, reasoningContent: payload.reasoning_content } : msg
|
||||
)
|
||||
);
|
||||
} else {
|
||||
flushReasoning(true);
|
||||
}
|
||||
flushAssistant(true);
|
||||
pushProgressLog(t('answerGenerationCompleted'));
|
||||
const messageArtifacts = normalizeArtifacts(payload.artifacts);
|
||||
setMessagesForSession(targetSessionKey, (prev) =>
|
||||
prev.map((msg) =>
|
||||
msg.id === assistantId ? { ...msg, content: typeof payload.content === "string" ? payload.content : msg.content || "", awaitingFirstToken: false, viz: streamedViz ?? msg.viz, artifacts: messageArtifacts.length > 0 ? messageArtifacts : msg.artifacts } : msg
|
||||
msg.id === assistantId ? { ...msg, content: typeof payload.content === "string" ? payload.content : msg.content || "", awaitingFirstToken: false, viz: streamedViz ?? msg.viz, usage: payload.usage, artifacts: messageArtifacts.length > 0 ? messageArtifacts : msg.artifacts } : msg
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -783,6 +856,7 @@ export function ChatInterface() {
|
||||
}
|
||||
}
|
||||
|
||||
flushReasoning(true);
|
||||
flushAssistant(true);
|
||||
if (!streamedText && (hasFinalPayload || hasDonePayload)) {
|
||||
setMessagesForSession(targetSessionKey, (prev) =>
|
||||
@@ -1046,6 +1120,14 @@ export function ChatInterface() {
|
||||
const isMessageGenerating = isLoading && msgIdx === messages.length - 1;
|
||||
const { markdown, reportHtml } = splitReportHtml(msg.content);
|
||||
const externalReportUrl = extractExternalReport(msg.content);
|
||||
const fallbackThinkingLines = Array.from(new Set(
|
||||
(msg.progressLogs || []).filter((log) =>
|
||||
log &&
|
||||
log !== t('requestSubmittedRouting') &&
|
||||
log !== t('answerGenerationCompleted')
|
||||
)
|
||||
));
|
||||
const displayedThinkingContent = (msg.reasoningContent || "").trim() || fallbackThinkingLines.join("\n");
|
||||
return (
|
||||
<div
|
||||
key={msg.id}
|
||||
@@ -1065,13 +1147,53 @@ export function ChatInterface() {
|
||||
>
|
||||
{msg.role === "assistant" ? (
|
||||
<>
|
||||
{msg.reasoningContent && (
|
||||
<div className="mb-3 rounded-xl border border-zinc-200 bg-zinc-50/50 p-3 text-sm text-zinc-600 font-mono whitespace-pre-wrap leading-relaxed shadow-inner max-h-[300px] overflow-y-auto">
|
||||
<div className="flex items-center gap-2 mb-2 text-xs font-semibold text-zinc-500 uppercase tracking-wider">
|
||||
<Settings className={`h-3.5 w-3.5 ${msg.awaitingFirstToken ? 'animate-spin' : ''}`} />
|
||||
{t('thinkingProcess')}
|
||||
</div>
|
||||
{msg.reasoningContent}
|
||||
{displayedThinkingContent && (
|
||||
<div className="mb-3 rounded-xl border border-zinc-200 bg-zinc-50/50 p-3 text-sm text-zinc-600 font-mono whitespace-pre-wrap leading-relaxed shadow-inner">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleThinkingCollapsed(msg.id)}
|
||||
className="w-full flex items-center justify-between gap-2 mb-2 text-xs font-semibold text-zinc-500 uppercase tracking-wider hover:text-zinc-700 transition-colors"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<Settings className={`h-3.5 w-3.5 ${msg.awaitingFirstToken ? 'animate-spin' : ''}`} />
|
||||
{t('thinkingProcess')}
|
||||
</span>
|
||||
<span className="flex items-center gap-2 normal-case text-[11px]">
|
||||
{msg.usage?.total_tokens ? (
|
||||
<span>{t('thinkingTokens', { count: msg.usage.total_tokens })}</span>
|
||||
) : msg.reasoningContent ? (
|
||||
<span>{t('thinkingCharCount', { count: msg.reasoningContent.length })}</span>
|
||||
) : null}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
void copyThinkingContent(msg.id, displayedThinkingContent);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
void copyThinkingContent(msg.id, displayedThinkingContent);
|
||||
}
|
||||
}}
|
||||
className="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 hover:bg-zinc-200/70 transition-colors"
|
||||
>
|
||||
{thinkingCopiedByMessage[msg.id] ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
|
||||
<span>{thinkingCopiedByMessage[msg.id] ? t('copied') : t('copy')}</span>
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{isThinkingCollapsed(msg.id) ? t('expandThinking') : t('collapseThinking')}
|
||||
<ChevronDown className={cn("h-3.5 w-3.5 transition-transform", isThinkingCollapsed(msg.id) ? "-rotate-90" : "rotate-0")} />
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{!isThinkingCollapsed(msg.id) && (
|
||||
<div className="max-h-[280px] overflow-y-auto pr-1">
|
||||
{displayedThinkingContent}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{msg.progressLogs && msg.progressLogs.length > 0 ? (
|
||||
|
||||
@@ -140,10 +140,8 @@ function Section({
|
||||
onRename,
|
||||
onTogglePinned,
|
||||
onToggleArchived,
|
||||
onBatchDelete,
|
||||
activeKey,
|
||||
isSelectionMode,
|
||||
setIsSelectionMode,
|
||||
selectedKeys,
|
||||
setSelectedKeys
|
||||
}: {
|
||||
@@ -153,10 +151,8 @@ function Section({
|
||||
onRename: (key: string, currentTitle: string) => void;
|
||||
onTogglePinned: (key: string, pinned: boolean) => void;
|
||||
onToggleArchived: (key: string, archived: boolean) => void;
|
||||
onBatchDelete: (keys: string[]) => void;
|
||||
activeKey: string | null;
|
||||
isSelectionMode: boolean;
|
||||
setIsSelectionMode: (val: boolean) => void;
|
||||
selectedKeys: string[];
|
||||
setSelectedKeys: (val: string[] | ((prev: string[]) => string[])) => void;
|
||||
}) {
|
||||
@@ -684,13 +680,11 @@ function SidebarBody() {
|
||||
items={activeSessions}
|
||||
onSelect={handleSelectSession}
|
||||
onDelete={handleDeleteSession}
|
||||
onBatchDelete={handleBatchDelete}
|
||||
onRename={openRenameDialog}
|
||||
onTogglePinned={handleTogglePinned}
|
||||
onToggleArchived={handleToggleArchived}
|
||||
activeKey={activeSessionKey}
|
||||
isSelectionMode={activeSelectionMode}
|
||||
setIsSelectionMode={setActiveSelectionMode}
|
||||
selectedKeys={activeSelectedKeys}
|
||||
setSelectedKeys={setActiveSelectedKeys}
|
||||
/>
|
||||
@@ -714,13 +708,11 @@ function SidebarBody() {
|
||||
items={archivedSessions}
|
||||
onSelect={handleSelectSession}
|
||||
onDelete={handleDeleteSession}
|
||||
onBatchDelete={handleBatchDelete}
|
||||
onRename={openRenameDialog}
|
||||
onTogglePinned={handleTogglePinned}
|
||||
onToggleArchived={handleToggleArchived}
|
||||
activeKey={activeSessionKey}
|
||||
isSelectionMode={archivedSelectionMode}
|
||||
setIsSelectionMode={setArchivedSelectionMode}
|
||||
selectedKeys={archivedSelectedKeys}
|
||||
setSelectedKeys={setArchivedSelectedKeys}
|
||||
/>
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"processing": "Processing...",
|
||||
"processCompleted": "Completed",
|
||||
"thinkingProcess": "Thinking Process",
|
||||
"thinkingTokens": "{{count}} tokens",
|
||||
"thinkingCharCount": "{{count}} chars",
|
||||
"expandThinking": "Expand",
|
||||
"collapseThinking": "Collapse",
|
||||
"modelThinking": "Model is thinking, please wait...",
|
||||
"openReportInNewTab": "Open report in new tab",
|
||||
"artifactPreview": "File Preview",
|
||||
|
||||
@@ -49,6 +49,10 @@
|
||||
"processing": "正在处理中",
|
||||
"processCompleted": "处理完成",
|
||||
"thinkingProcess": "思考过程",
|
||||
"thinkingTokens": "{{count}} tokens",
|
||||
"thinkingCharCount": "{{count}} 字",
|
||||
"expandThinking": "展开",
|
||||
"collapseThinking": "收起",
|
||||
"modelThinking": "模型思考中,请稍候...",
|
||||
"openReportInNewTab": "在新标签页中打开分析报告",
|
||||
"artifactPreview": "文件预览",
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { X, Type, AlignLeft, AlignCenter, AlignRight, Bold, Italic, Underline } from "lucide-react";
|
||||
import { X, Type, Bold, Italic, Underline } from "lucide-react";
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line } from 'recharts';
|
||||
import { VegaChart } from "@/components/VegaChart";
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
|
||||
@@ -87,10 +87,8 @@ export function Login() {
|
||||
</h1>
|
||||
<div className="absolute right-0 bottom-0 translate-y-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-9 w-9 rounded-full bg-white/50 backdrop-blur-sm shadow-sm border border-zinc-200/50 text-zinc-500 hover:text-zinc-900 hover:bg-white transition-all">
|
||||
<Languages className="h-4 w-4" />
|
||||
</Button>
|
||||
<DropdownMenuTrigger className="h-9 w-9 rounded-full bg-white/50 backdrop-blur-sm shadow-sm border border-zinc-200/50 text-zinc-500 hover:text-zinc-900 hover:bg-white transition-all inline-flex items-center justify-center">
|
||||
<Languages className="h-4 w-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-32">
|
||||
<DropdownMenuItem onClick={() => i18n.changeLanguage('zh')} className={i18n.language === 'zh' ? 'bg-zinc-100' : ''}>
|
||||
@@ -186,4 +184,4 @@ export function Login() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user