feat: add configurable chat display modes
This commit is contained in:
1 parent
3715af3138
commit
40542db400
18 files changed
+421
-121
No files matched your search
@@ -20,6 +20,7 @@ import {
|
||||
} from "@/core/artifacts/listing";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { isThreadRagModeLocked, useLocalSettings } from "@/core/settings";
|
||||
import { useCitationDisplaySettings } from "@/core/system-settings/hooks";
|
||||
import { isSingleRoundRagMessage } from "@/core/threads/use-single-round-rag";
|
||||
import { isMultiRoundRagMessage } from "@/core/threads/use-multi-round-rag";
|
||||
import { useSkills } from "@/core/skills/hooks";
|
||||
@@ -101,8 +102,12 @@ const ChatBox: React.FC<{
|
||||
const [settings] = useLocalSettings();
|
||||
const artifactDirectoryModeEnabled =
|
||||
settings.display.artifactDirectoryModeEnabled ?? false;
|
||||
const { data: citationDisplay } = useCitationDisplaySettings();
|
||||
const referenceModeEntryEnabled =
|
||||
citationDisplay?.reference_mode_enabled ?? false;
|
||||
const ragModeEnabled =
|
||||
settings.display.ragModeEnabled || isThreadRagModeLocked(threadId);
|
||||
(referenceModeEntryEnabled && settings.display.ragModeEnabled) ||
|
||||
isThreadRagModeLocked(threadId);
|
||||
// Frontend RAG modes write their own hidden source tool messages. They use
|
||||
// the existing reference panel, but must not request or write reference
|
||||
// batches through DeerFlow's backend APIs.
|
||||
|
||||
@@ -93,14 +93,14 @@ import {
|
||||
|
||||
import { useRecommendedQuestions } from "@/core/recommended-questions/hooks";
|
||||
import { useUserPreferences } from "@/core/user-preferences";
|
||||
import {
|
||||
isThreadRagModeLocked,
|
||||
lockThreadRagMode,
|
||||
useLocalSettings,
|
||||
} from "@/core/settings";
|
||||
|
||||
|
||||
import { CustomPromptBar } from "./custom-prompt-menu";
|
||||
import {
|
||||
isThreadRagModeLocked,
|
||||
lockThreadRagMode,
|
||||
useLocalSettings,
|
||||
} from "@/core/settings";
|
||||
import { useCitationDisplaySettings } from "@/core/system-settings/hooks";
|
||||
|
||||
import { CustomPromptBar } from "./custom-prompt-menu";
|
||||
import { useThread } from "./messages/context";
|
||||
import { ModeHoverGuide } from "./mode-hover-guide";
|
||||
import { Tooltip } from "./tooltip";
|
||||
@@ -221,9 +221,12 @@ export function InputBox({
|
||||
const modelOptions = useMemo(() => {
|
||||
return singleRoundRagActive ? extraModels : models;
|
||||
}, [extraModels, models, singleRoundRagActive]);
|
||||
const { thread, isMock } = useThread();
|
||||
const { textInput } = usePromptInputController();
|
||||
const promptRootRef = useRef<HTMLDivElement | null>(null);
|
||||
const { thread, isMock } = useThread();
|
||||
const { textInput } = usePromptInputController();
|
||||
const promptRootRef = useRef<HTMLDivElement | null>(null);
|
||||
const { data: citationDisplay } = useCitationDisplaySettings();
|
||||
const referenceModeEntryEnabled =
|
||||
citationDisplay?.reference_mode_enabled ?? false;
|
||||
|
||||
const [followups, setFollowups] = useState<string[]>([]);
|
||||
const [followupsHidden, setFollowupsHidden] = useState(false);
|
||||
@@ -830,9 +833,9 @@ export function InputBox({
|
||||
<span className="text-xs font-normal">记忆</span>
|
||||
</PromptInputButton>
|
||||
</Tooltip>}
|
||||
{!frontendRagActive && !hideContextTools && <Tooltip
|
||||
content={
|
||||
ragModeEnabled
|
||||
{referenceModeEntryEnabled && !frontendRagActive && !hideContextTools && <Tooltip
|
||||
content={
|
||||
ragModeEnabled
|
||||
? "参考文献模式已开启:检索类结果会按编号引用展示"
|
||||
: "参考文献模式已关闭:点击开启"
|
||||
}
|
||||
|
||||
@@ -51,13 +51,12 @@ import {
|
||||
resolveSearchQuery,
|
||||
type SearchResultItem,
|
||||
} from "@/core/messages/search-results";
|
||||
import {
|
||||
extractReasoningContentFromMessage,
|
||||
findToolCallResult,
|
||||
} from "@/core/messages/utils";
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { useLocalSettings } from "@/core/settings";
|
||||
import { useSkills } from "@/core/skills/hooks";
|
||||
import {
|
||||
extractReasoningContentFromMessage,
|
||||
findToolCallResult,
|
||||
} from "@/core/messages/utils";
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { useSkills } from "@/core/skills/hooks";
|
||||
import {
|
||||
applyInlineCitations,
|
||||
buildSkillDisplayMap,
|
||||
@@ -81,17 +80,19 @@ import { SourceCard } from "../skill-display-sources";
|
||||
import { MarkdownContent } from "./markdown-content";
|
||||
import { SingleRoundRagCitationDialog } from "./message-list-item";
|
||||
|
||||
export function MessageGroup({
|
||||
className,
|
||||
messages,
|
||||
isLoading = false,
|
||||
displaySources = [],
|
||||
}: {
|
||||
className?: string;
|
||||
messages: Message[];
|
||||
isLoading?: boolean;
|
||||
displaySources?: RuntimeDisplaySource[];
|
||||
}) {
|
||||
export function MessageGroup({
|
||||
className,
|
||||
messages,
|
||||
isLoading = false,
|
||||
displaySources = [],
|
||||
ragModeEnabled = false,
|
||||
}: {
|
||||
className?: string;
|
||||
messages: Message[];
|
||||
isLoading?: boolean;
|
||||
displaySources?: RuntimeDisplaySource[];
|
||||
ragModeEnabled?: boolean;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const [showAbove, setShowAbove] = useState(
|
||||
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true",
|
||||
@@ -99,13 +100,12 @@ export function MessageGroup({
|
||||
const [showLastThinking, setShowLastThinking] = useState(
|
||||
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true",
|
||||
);
|
||||
const steps = useMemo(() => convertToSteps(messages), [messages]);
|
||||
const [settings] = useLocalSettings();
|
||||
const { skills } = useSkills();
|
||||
const skillDisplayMap = useMemo(
|
||||
() => (settings.display.ragModeEnabled ? buildSkillDisplayMap(skills) : new Map()),
|
||||
[settings.display.ragModeEnabled, skills],
|
||||
);
|
||||
const steps = useMemo(() => convertToSteps(messages), [messages]);
|
||||
const { skills } = useSkills();
|
||||
const skillDisplayMap = useMemo(
|
||||
() => (ragModeEnabled ? buildSkillDisplayMap(skills) : new Map()),
|
||||
[ragModeEnabled, skills],
|
||||
);
|
||||
const lastToolCallStep = useMemo(() => {
|
||||
const filteredSteps = steps.filter((step) => step.type === "toolCall");
|
||||
return filteredSteps[filteredSteps.length - 1];
|
||||
@@ -197,11 +197,11 @@ export function MessageGroup({
|
||||
<ToolCall
|
||||
key={step.id}
|
||||
{...step}
|
||||
isLoading={isLoading}
|
||||
skillDisplayMap={skillDisplayMap}
|
||||
ragModeEnabled={settings.display.ragModeEnabled}
|
||||
/>
|
||||
),
|
||||
isLoading={isLoading}
|
||||
skillDisplayMap={skillDisplayMap}
|
||||
ragModeEnabled={ragModeEnabled}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{lastToolCallStep && (
|
||||
<FlipDisplay uniqueKey={lastToolCallStep.id ?? ""}>
|
||||
@@ -209,10 +209,10 @@ export function MessageGroup({
|
||||
key={lastToolCallStep.id}
|
||||
{...lastToolCallStep}
|
||||
isLast={true}
|
||||
isLoading={isLoading}
|
||||
skillDisplayMap={skillDisplayMap}
|
||||
ragModeEnabled={settings.display.ragModeEnabled}
|
||||
/>
|
||||
isLoading={isLoading}
|
||||
skillDisplayMap={skillDisplayMap}
|
||||
ragModeEnabled={ragModeEnabled}
|
||||
/>
|
||||
</FlipDisplay>
|
||||
)}
|
||||
</ChainOfThoughtContent>
|
||||
|
||||
@@ -28,11 +28,12 @@ import {
|
||||
hasToolCalls,
|
||||
isHiddenFromUIMessage,
|
||||
} from "@/core/messages/utils";
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { isThreadRagModeLocked, useLocalSettings } from "@/core/settings";
|
||||
import { useSkills } from "@/core/skills/hooks";
|
||||
import {
|
||||
buildSkillDisplayMap,
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { isThreadRagModeLocked, useLocalSettings } from "@/core/settings";
|
||||
import { useSkills } from "@/core/skills/hooks";
|
||||
import { useCitationDisplaySettings } from "@/core/system-settings/hooks";
|
||||
import {
|
||||
buildSkillDisplayMap,
|
||||
collectRuntimeDisplaySources,
|
||||
type RuntimeDisplaySource,
|
||||
} from "@/core/skill-display/runtime";
|
||||
@@ -299,10 +300,14 @@ export function MessageList({
|
||||
}
|
||||
return map;
|
||||
}, [messages]);
|
||||
|
||||
const [settings] = useLocalSettings();
|
||||
const ragModeEnabled =
|
||||
settings.display.ragModeEnabled || isThreadRagModeLocked(threadId);
|
||||
|
||||
const [settings] = useLocalSettings();
|
||||
const { data: citationDisplay } = useCitationDisplaySettings();
|
||||
const referenceModeEntryEnabled =
|
||||
citationDisplay?.reference_mode_enabled ?? false;
|
||||
const ragModeEnabled =
|
||||
(referenceModeEntryEnabled && settings.display.ragModeEnabled) ||
|
||||
isThreadRagModeLocked(threadId);
|
||||
const hasSingleRoundRag = useMemo(
|
||||
() =>
|
||||
messages.some(
|
||||
@@ -778,12 +783,13 @@ export function MessageList({
|
||||
)) {
|
||||
if (hasReasoning(message)) {
|
||||
results.push(
|
||||
<MessageGroup
|
||||
key={"thinking-group-" + message.id}
|
||||
messages={[message]}
|
||||
isLoading={thread.isLoading}
|
||||
displaySources={resolveDisplaySources(message)}
|
||||
/>,
|
||||
<MessageGroup
|
||||
key={"thinking-group-" + message.id}
|
||||
messages={[message]}
|
||||
isLoading={thread.isLoading}
|
||||
displaySources={resolveDisplaySources(message)}
|
||||
ragModeEnabled={ragModeEnabled}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
results.push(
|
||||
@@ -831,11 +837,12 @@ export function MessageList({
|
||||
.find((message) => message.type === "ai");
|
||||
return (
|
||||
<div key={"group-" + group.id} className="w-full">
|
||||
<MessageGroup
|
||||
messages={group.messages}
|
||||
isLoading={thread.isLoading}
|
||||
displaySources={resolveDisplaySources(processingAi)}
|
||||
/>
|
||||
<MessageGroup
|
||||
messages={group.messages}
|
||||
isLoading={thread.isLoading}
|
||||
displaySources={resolveDisplaySources(processingAi)}
|
||||
ragModeEnabled={ragModeEnabled}
|
||||
/>
|
||||
<MessageTokenUsageList
|
||||
enabled={tokenUsageEnabled}
|
||||
isLoading={thread.isLoading}
|
||||
|
||||
@@ -205,6 +205,8 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
|
||||
const [effectiveLayoutMode, setLayoutMode] = useLayoutMode();
|
||||
const cleanupWords =
|
||||
citationDisplay?.cleanup_words ?? settings.display.citationCleanupWords ?? [];
|
||||
const referenceModeEntryEnabled =
|
||||
citationDisplay?.reference_mode_enabled ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
setShortcutDraft(
|
||||
@@ -845,23 +847,45 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) {
|
||||
aria-label="回答速度指示器"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-start justify-between gap-4 rounded-lg border p-4">
|
||||
<div>
|
||||
<div className="text-sm font-medium">RAG 引用模式</div>
|
||||
<p className="text-muted-foreground mt-1 max-w-md text-xs leading-relaxed">
|
||||
开启后,已配置展示适配的技能结果会在问答中渲染为编号引用或列表,并支持点击跳转。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.display.ragModeEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings("display", { ragModeEnabled: checked })
|
||||
}
|
||||
aria-label="RAG 引用模式"
|
||||
/>
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<div className="rounded-lg border p-4">
|
||||
{isAdmin && (
|
||||
<div className="flex items-start justify-between gap-4 rounded-lg border p-4">
|
||||
<div>
|
||||
<div className="text-sm font-medium">参考文献模式入口</div>
|
||||
<p className="text-muted-foreground mt-1 max-w-md text-xs leading-relaxed">
|
||||
管理员配置。开启后,普通问答和 iframe 问答输入区会显示「参考文献」按钮;默认关闭。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={referenceModeEntryEnabled}
|
||||
disabled={updateCitationDisplay.isPending}
|
||||
onCheckedChange={(checked) =>
|
||||
updateCitationDisplay.mutate({
|
||||
reference_mode_enabled: checked,
|
||||
})
|
||||
}
|
||||
aria-label="参考文献模式入口"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{referenceModeEntryEnabled && (
|
||||
<div className="flex items-start justify-between gap-4 rounded-lg border p-4">
|
||||
<div>
|
||||
<div className="text-sm font-medium">RAG 引用模式</div>
|
||||
<p className="text-muted-foreground mt-1 max-w-md text-xs leading-relaxed">
|
||||
开启后,已配置展示适配的技能结果会在问答中渲染为编号引用或列表,并支持点击跳转。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={settings.display.ragModeEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setSettings("display", { ragModeEnabled: checked })
|
||||
}
|
||||
aria-label="RAG 引用模式"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<div className="rounded-lg border p-4">
|
||||
<div className="mb-3">
|
||||
<div className="text-sm font-medium">参考文献去除词</div>
|
||||
<p className="text-muted-foreground mt-1 max-w-2xl text-xs leading-relaxed">
|
||||
|
||||
Reference in new issue
Block a user