# Conflicts:
#	frontend-web/src/components/workspace/messages/message-group.tsx
#	offline-backend-20260512/backend/packages/harness/deerflow/agents/lead_agent/prompt.py
This commit is contained in:
fengxiangdebaozi committed 2026-08-31 20:12:05 +08:00
commit 57b6a89f20
21 files changed
+441 -115

No files matched your search

@@ -22,6 +22,7 @@ import { latestInFlightWriteFilePreview } from "@/core/artifacts/write-file-in-f
import { shouldAutoOpenWritingArtifact } from "@/core/threads/writing-mode";
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";
@@ -87,7 +88,11 @@ const ChatBox: React.FC<{
threadId,
disableArtifacts = false,
}) => {
const { isMock, thread } = useThread();
const {
isMock,
thread,
knowledgeReferencesEnabled = false,
} = useThread();
const pathname = usePathname();
const threadIdRef = useRef(threadId);
const artifactsCountRef = useRef(0);
@@ -106,8 +111,13 @@ const ChatBox: React.FC<{
const writingModeEnabled = !!settings.context.writing_mode;
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) ||
knowledgeReferencesEnabled;
// 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.
@@ -121,7 +131,7 @@ const ChatBox: React.FC<{
);
const referencesEnabled = ragModeEnabled || hasFrontendRag;
const shouldFetchBackendReferenceBatches =
referencesEnabled && !hasFrontendRag;
referencesEnabled && !hasFrontendRag && !!threadId && threadId !== "new";
const { skills } = useSkills();
const canScanArtifactDirectory =
@@ -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
? "参考文献模式已开启:检索类结果会按编号引用展示"
: "参考文献模式已关闭:点击开启"
}
@@ -4,11 +4,17 @@ import { createContext, useContext } from "react";
import type { RuntimeDisplaySource } from "@/core/skill-display/runtime";
import type { AgentThreadState } from "@/core/threads";
export interface ThreadContextType {
thread: BaseStream<AgentThreadState>;
isMock?: boolean;
/**
* When set, the conversation is rendered from a public read-only share.
export interface ThreadContextType {
thread: BaseStream<AgentThreadState>;
isMock?: boolean;
/**
* True when the active conversation has selected LLMWiki knowledge bases.
* Knowledge-base Q&A should always expose its source documents, independent
* from the admin-controlled manual reference-mode button.
*/
knowledgeReferencesEnabled?: boolean;
/**
* When set, the conversation is rendered from a public read-only share.
* Artifact/image URLs then resolve to the unauthenticated
* `/api/public/shares/{code}/files/...` endpoint instead of the
* auth-gated thread artifact route.
@@ -83,16 +83,18 @@ 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(
@@ -110,9 +112,9 @@ export function MessageGroup({
[messages, writingModeEnabled],
);
const { skills } = useSkills();
const skillDisplayMap = useMemo(
() => (settings.display.ragModeEnabled ? buildSkillDisplayMap(skills) : new Map()),
[settings.display.ragModeEnabled, skills],
const skillDisplayMap = useMemo(
() => (ragModeEnabled ? buildSkillDisplayMap(skills) : new Map()),
[ragModeEnabled, skills],
);
const lastToolCallStep = useMemo(() => {
const filteredSteps = steps.filter((step) => step.type === "toolCall");
@@ -207,7 +209,7 @@ export function MessageGroup({
{...step}
isLoading={isLoading}
skillDisplayMap={skillDisplayMap}
ragModeEnabled={settings.display.ragModeEnabled}
ragModeEnabled={ragModeEnabled}
/>
),
)}
@@ -219,7 +221,7 @@ export function MessageGroup({
isLast={true}
isLoading={isLoading}
skillDisplayMap={skillDisplayMap}
ragModeEnabled={settings.display.ragModeEnabled}
ragModeEnabled={ragModeEnabled}
/>
</FlipDisplay>
)}
@@ -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,17 @@ 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 knowledgeReferencesEnabled =
threadContext?.knowledgeReferencesEnabled === true;
const ragModeEnabled =
(referenceModeEntryEnabled && settings.display.ragModeEnabled) ||
isThreadRagModeLocked(threadId) ||
knowledgeReferencesEnabled;
const hasSingleRoundRag = useMemo(
() =>
messages.some(
@@ -778,12 +786,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 +840,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">
@@ -6,6 +6,7 @@ export interface PromptPrefixSettings {
enabled: boolean;
switch_label: string;
prompt_prefix: string;
ordinary_qa_markdown_format_enabled: boolean;
}
export interface PluginDomainAgentMappingDto {
@@ -37,6 +38,7 @@ export interface PromptPrefixUpdate {
enabled?: boolean;
switch_label?: string;
prompt_prefix?: string;
ordinary_qa_markdown_format_enabled?: boolean;
}
function fromPluginManagementDto(data: {
@@ -173,10 +175,12 @@ export interface PrivateSquareUpdate {
}
export interface CitationDisplaySettings {
reference_mode_enabled: boolean;
cleanup_words: string[];
}
export interface CitationDisplayUpdate {
reference_mode_enabled?: boolean;
cleanup_words?: string[];
}
+34 -16
View File
@@ -16,9 +16,14 @@ import { rememberAskTime } from "../chat-timestamps";
import { getBackendBaseURL, getLangGraphBaseURL } from "../config";
import { useI18n } from "../i18n/hooks";
import { OptType, writeYLog } from "../log/y-log";
import type { FileInMessage } from "../messages/utils";
import type { LocalSettings } from "../settings";
import { isThreadRagModeLocked, lockThreadRagMode, useLocalSettings } from "../settings";
import type { FileInMessage } from "../messages/utils";
import type { LocalSettings } from "../settings";
import {
isThreadRagModeLocked,
lockThreadRagMode,
useLocalSettings,
} from "../settings";
import { useCitationDisplaySettings } from "../system-settings/hooks";
import { useUpdateSubtask } from "../tasks/context";
import type { UploadedFileInfo } from "../uploads";
import { promptInputFilePartToFile, uploadFiles } from "../uploads";
@@ -566,8 +571,11 @@ export function useThreadStream({
onToolStart,
onToolEnd,
}: ThreadStreamOptions) {
const { t } = useI18n();
const [localSettings] = useLocalSettings();
const { t } = useI18n();
const [localSettings] = useLocalSettings();
const { data: citationDisplay } = useCitationDisplaySettings();
const referenceModeEntryEnabled =
citationDisplay?.reference_mode_enabled ?? false;
// Track the thread ID that is currently streaming to handle thread changes during streaming
const [onStreamThreadId, setOnStreamThreadId] = useState(() => threadId);
// Ref to track current thread ID across async callbacks without causing re-renders,
@@ -1499,15 +1507,15 @@ export function useThreadStream({
...context,
...extraContext,
};
const selectedLlmWikiIds = Array.isArray(
mergedContext.llmwiki_knowledge_base_ids,
)
? mergedContext.llmwiki_knowledge_base_ids
: [];
const ragModeEnabled =
localSettings.display.ragModeEnabled ||
isThreadRagModeLocked(threadId) ||
selectedLlmWikiIds.length > 0;
const selectedLlmWikiIds = Array.isArray(
mergedContext.llmwiki_knowledge_base_ids,
)
? mergedContext.llmwiki_knowledge_base_ids
: [];
const ragModeEnabled =
(referenceModeEntryEnabled && localSettings.display.ragModeEnabled) ||
isThreadRagModeLocked(threadId) ||
selectedLlmWikiIds.length > 0;
if (ragModeEnabled) {
lockThreadRagMode(threadId);
}
@@ -1625,8 +1633,18 @@ export function useThreadStream({
sendInFlightRef.current = false;
}
},
[thread, _handleOnStart, t.uploads.uploadingFiles, context, localSettings.display.ragModeEnabled, queryClient, runOptions, confirmOrRejoin],
);
[
thread,
_handleOnStart,
t.uploads.uploadingFiles,
context,
referenceModeEntryEnabled,
localSettings.display.ragModeEnabled,
queryClient,
runOptions,
confirmOrRejoin,
],
);
const handleThreadStop = useCallback(async () => {
const tid = threadIdRef.current;
+7 -1
View File
@@ -777,7 +777,13 @@ export default function AgentChatPage({
: "ready";
return (
<ThreadContext.Provider value={{ thread: displayThread }}>
<ThreadContext.Provider
value={{
thread: displayThread,
knowledgeReferencesEnabled:
knowledgeSelectionReady && selectedKnowledgeBaseIds.length > 0,
}}
>
{/* Root: fixed-height horizontal split. ChatBox (chat + artifact
preview) lives in the left flex cell; the agent detail panel is a
separate flex sibling OUTSIDE ChatBox, so the artifact preview
+8 -1
View File
@@ -684,7 +684,14 @@ export default function ChatPage() {
!isWujieEnv();
return (
<ThreadContext.Provider value={{ thread: displayThread, isMock }}>
<ThreadContext.Provider
value={{
thread: displayThread,
isMock,
knowledgeReferencesEnabled:
knowledgeSelectionReady && selectedKnowledgeBaseIds.length > 0,
}}
>
<ChatBox threadId={threadId}>
<div
className={cn(
+12 -2
View File
@@ -39,8 +39,14 @@ import {
useLocalSettings,
useThreadSettings,
} from "@/core/settings";
import { usePluginManagementSettings } from "@/core/system-settings/hooks";
import { stopActiveThreadBeforeClarificationReply, useThreadStream } from "@/core/threads/hooks";
import {
useCitationDisplaySettings,
usePluginManagementSettings,
} from "@/core/system-settings/hooks";
import {
stopActiveThreadBeforeClarificationReply,
useThreadStream,
} from "@/core/threads/hooks";
import { textOfMessage } from "@/core/threads/utils";
import { resolveWritingModeReportPath } from "@/core/threads/writing-mode";
import { env } from "@/env";
@@ -379,6 +385,9 @@ export default function IframeChatPage() {
}, [forceDefaultAssistant, pendingIframeSubmit, selectedAgentId]);
const ragModeLocked = isThreadRagModeLocked(threadId);
const { data: citationDisplay } = useCitationDisplaySettings();
const referenceModeEntryEnabled =
citationDisplay?.reference_mode_enabled ?? false;
const ragModeEnabled =
localSettings.display.ragModeEnabled || ragModeLocked;
const contextCompressionEnabled =
@@ -525,6 +534,7 @@ export default function IframeChatPage() {
memoryInjectionEnabled: Boolean(
settings.context.memory_injection_enabled ?? true,
),
referenceModeEntryEnabled,
ragModeEnabled,
ragModeLocked,
contextCompressionEnabled,
@@ -46,6 +46,8 @@ export default function PromptPrefixSettingsPage({
const [enabled, setEnabled] = useState(false);
const [switchLabel, setSwitchLabel] = useState("");
const [promptPrefix, setPromptPrefix] = useState("");
const [ordinaryQaMarkdownFormatEnabled, setOrdinaryQaMarkdownFormatEnabled] =
useState(false);
// Only seed the draft from server data once. Re-syncing every time react-query
// refetches (window focus, route remount, etc.) would silently overwrite the
// user's in-progress edits — and made the toggle look unresponsive: a click
@@ -57,6 +59,9 @@ export default function PromptPrefixSettingsPage({
setEnabled(data.enabled);
setSwitchLabel(data.switch_label);
setPromptPrefix(data.prompt_prefix);
setOrdinaryQaMarkdownFormatEnabled(
data.ordinary_qa_markdown_format_enabled === true,
);
initializedRef.current = true;
}, [data]);
@@ -79,6 +84,8 @@ export default function PromptPrefixSettingsPage({
enabled,
switch_label: switchLabel,
prompt_prefix: promptPrefix,
ordinary_qa_markdown_format_enabled:
ordinaryQaMarkdownFormatEnabled,
});
toast.success("已保存",darkToastOpts.success);
} catch (e) {
@@ -95,10 +102,9 @@ export default function PromptPrefixSettingsPage({
}
>
<div>
<h1 className={embedded ? "text-base font-semibold" : "text-xl font-semibold"}>问答前缀配置</h1>
<h1 className={embedded ? "text-base font-semibold" : "text-xl font-semibold"}>问答提示词配置</h1>
<p className="text-muted-foreground mt-1 text-sm">
管理员可在此配置问答页输入框中的「前缀开关」。开启后,用户在新会话中第一次提问时,
会将下方配置的前缀文本和原问题拼接后再发送给模型。
管理员可在此配置问答前缀和普通问答的固定 Markdown 格式提示词。
</p>
</div>
@@ -147,6 +153,21 @@ export default function PromptPrefixSettingsPage({
/>
</div>
<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">
开启后,普通问答的长篇内容会按“# 主标题、## 一、标题、###
(一) 标题、#### 1. 标题”的固定 Markdown 格式生成。默认关闭。
</p>
</div>
<Switch
checked={ordinaryQaMarkdownFormatEnabled}
onCheckedChange={setOrdinaryQaMarkdownFormatEnabled}
aria-label="固定格式提示词"
/>
</div>
<div className="flex justify-end">
<Button onClick={handleSave} disabled={update.isPending}>
{update.isPending && (
@@ -9,6 +9,7 @@ test("includes the context compression toggle in the iframe chat toolbar", () =>
agentSelected: false,
writingModeEnabled: false,
memoryInjectionEnabled: true,
referenceModeEntryEnabled: true,
ragModeEnabled: false,
ragModeLocked: false,
contextCompressionEnabled: false,
@@ -28,11 +29,27 @@ test("includes the context compression toggle in the iframe chat toolbar", () =>
);
});
test("hides the references toggle when reference mode is disabled globally", () => {
const buttons = buildIframeModeButtons({
agentSelected: false,
writingModeEnabled: false,
memoryInjectionEnabled: true,
referenceModeEntryEnabled: false,
ragModeEnabled: true,
ragModeLocked: false,
contextCompressionEnabled: false,
proModeEnabled: false,
});
assert.equal(buttons.some((button) => button.key === "references"), false);
});
test("returns only concrete toolbar button configs", () => {
const buttons = buildIframeModeButtons({
agentSelected: true,
writingModeEnabled: false,
memoryInjectionEnabled: true,
referenceModeEntryEnabled: true,
ragModeEnabled: false,
ragModeLocked: false,
contextCompressionEnabled: false,
@@ -15,6 +15,7 @@ type BuildIframeModeButtonsInput = {
agentSelected: boolean;
writingModeEnabled: boolean;
memoryInjectionEnabled: boolean;
referenceModeEntryEnabled: boolean;
ragModeEnabled: boolean;
ragModeLocked: boolean;
contextCompressionEnabled: boolean;
@@ -25,12 +26,13 @@ export function buildIframeModeButtons({
agentSelected,
writingModeEnabled,
memoryInjectionEnabled,
referenceModeEntryEnabled,
ragModeEnabled,
ragModeLocked,
contextCompressionEnabled,
proModeEnabled,
}: BuildIframeModeButtonsInput): IframeModeButtonState[] {
return [
const buttons: IframeModeButtonState[] = [
{
key: "writing",
label: "写作",
@@ -63,4 +65,7 @@ export function buildIframeModeButtons({
active: agentSelected,
},
];
return referenceModeEntryEnabled
? buttons
: buttons.filter((button) => button.key !== "references");
}