Merge branch 'ji' of http://192.168.0.200/knowledge-base-bj/deerflow-server into ji
This commit is contained in:
commit
9704eff9f7
42 files changed
+1973
-10517
No files matched your search
@@ -250,6 +250,7 @@ export default function AgentChatPage() {
|
||||
className="w-full"
|
||||
isNewThread={isLandingState}
|
||||
threadId={threadId}
|
||||
assistantId={agent_id}
|
||||
autoFocus={isLandingState}
|
||||
status={
|
||||
thread.error
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { type PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||
import { usePromptPrefixSettings } from "@/core/system-settings/hooks";
|
||||
import { useUserPreferences } from "@/core/user-preferences";
|
||||
import { ArtifactTrigger, useArtifacts } from "@/components/workspace/artifacts";
|
||||
import {
|
||||
ChatBox,
|
||||
@@ -50,6 +51,9 @@ export default function ChatPage() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const { tokenUsageEnabled } = useModels();
|
||||
const { data: promptPrefixSettings } = usePromptPrefixSettings();
|
||||
// Personal opt-out: when set, hide the admin prompt-prefix switch entirely and
|
||||
// never apply the prefix — the user uses only their own prompts.
|
||||
const { hide_admin_prompt_prefix: hideAdminPrompt } = useUserPreferences();
|
||||
const [promptPrefixOn, setPromptPrefixOn] = useState(false);
|
||||
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
|
||||
const { agents: selectorAgents, isLoading: selectorLoading } =
|
||||
@@ -159,6 +163,7 @@ export default function ChatPage() {
|
||||
isNewThread &&
|
||||
!hasStartedConversation &&
|
||||
promptPrefixOn &&
|
||||
!hideAdminPrompt &&
|
||||
promptPrefixSettings?.enabled === true;
|
||||
setHasStartedConversation(true);
|
||||
window.dispatchEvent(
|
||||
@@ -189,6 +194,7 @@ export default function ChatPage() {
|
||||
hasStartedConversation,
|
||||
promptPrefixOn,
|
||||
promptPrefixSettings,
|
||||
hideAdminPrompt,
|
||||
selectedAgentId,
|
||||
router,
|
||||
settings.context.writing_mode,
|
||||
@@ -217,6 +223,8 @@ export default function ChatPage() {
|
||||
|
||||
const promptPrefixProp = useMemo(() => {
|
||||
if (!promptPrefixSettings?.enabled) return undefined;
|
||||
// Personal opt-out hides the admin prompt-prefix switch entirely.
|
||||
if (hideAdminPrompt) return undefined;
|
||||
// The prefix toggle is offered only on the opening turn of a new thread.
|
||||
if (!isNewThread || hasStartedConversation) return undefined;
|
||||
return {
|
||||
@@ -224,7 +232,13 @@ export default function ChatPage() {
|
||||
on: promptPrefixOn,
|
||||
onToggle: () => setPromptPrefixOn((v) => !v),
|
||||
};
|
||||
}, [promptPrefixSettings, isNewThread, hasStartedConversation, promptPrefixOn]);
|
||||
}, [
|
||||
promptPrefixSettings,
|
||||
hideAdminPrompt,
|
||||
isNewThread,
|
||||
hasStartedConversation,
|
||||
promptPrefixOn,
|
||||
]);
|
||||
|
||||
const messageListPaddingBottom = showFollowups
|
||||
? MESSAGE_LIST_DEFAULT_PADDING_BOTTOM +
|
||||
|
||||
@@ -19,6 +19,11 @@ import {
|
||||
} from "@/components/ai-elements/prompt-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -361,8 +366,19 @@ export default function NewAgentPage() {
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
<main className="grid min-h-0 flex-1 grid-cols-1 md:grid-cols-[minmax(0,1fr)_420px]">
|
||||
<section className="flex min-h-0 flex-col border-r">
|
||||
<ResizablePanelGroup
|
||||
id="new-agent-config-panels"
|
||||
orientation="horizontal"
|
||||
defaultLayout={{ chat: 62, config: 38 }}
|
||||
className="min-h-0 flex-1"
|
||||
>
|
||||
<ResizablePanel
|
||||
id="chat"
|
||||
defaultSize={62}
|
||||
minSize={40}
|
||||
className="min-h-0"
|
||||
>
|
||||
<section className="flex size-full min-h-0 flex-col">
|
||||
<div className="min-h-0 flex-1">
|
||||
<MessageList
|
||||
className="size-full"
|
||||
@@ -428,8 +444,18 @@ export default function NewAgentPage() {
|
||||
</PromptInput>
|
||||
</div>
|
||||
</section>
|
||||
</ResizablePanel>
|
||||
|
||||
<aside className="bg-background min-h-0 overflow-y-auto">
|
||||
<ResizableHandle withHandle />
|
||||
|
||||
<ResizablePanel
|
||||
id="config"
|
||||
defaultSize={38}
|
||||
minSize={24}
|
||||
maxSize={60}
|
||||
className="min-h-0"
|
||||
>
|
||||
<aside className="bg-background size-full min-h-0 overflow-y-auto">
|
||||
<div className="space-y-5 p-4">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold">智能体配置</h2>
|
||||
@@ -606,7 +632,8 @@ export default function NewAgentPage() {
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
</ArtifactsProvider>
|
||||
</ThreadContext.Provider>
|
||||
|
||||
@@ -114,13 +114,15 @@ const emptyForm = {
|
||||
timezone: "Asia/Shanghai",
|
||||
prompt: "",
|
||||
execution_agent_name: "",
|
||||
// Two independent output switches (may both be on): require a Markdown file
|
||||
// and/or render an HTML page. The form no longer splits into "普通任务 / HTML
|
||||
// 页面" tabs.
|
||||
require_markdown: false,
|
||||
// HTML page task fields. ``task_kind`` "agent" keeps the original behavior.
|
||||
// Data gathering is delegated to the agent's own search skills — no explicit
|
||||
// search query / data source. ``html_model_name`` empty means "use the first
|
||||
// available model" — never a hardcoded GLM-5.
|
||||
task_kind: "agent" as "agent" | "html_page",
|
||||
html_page_prompt: "",
|
||||
require_html: false,
|
||||
// HTML-only extra fields, shown when ``require_html`` is on. The main
|
||||
// ``prompt`` above doubles as the page requirement, so there is no separate
|
||||
// page-prompt field. ``html_model_name`` empty means "use the first available
|
||||
// model" — never a hardcoded GLM-5.
|
||||
html_negative_prompt: "",
|
||||
html_reference_favorite_id: "",
|
||||
html_model_name: "",
|
||||
@@ -244,11 +246,15 @@ export default function ScheduledTasksPage() {
|
||||
description: task.description ?? "",
|
||||
schedule: task.schedule_text,
|
||||
timezone: task.timezone,
|
||||
prompt: task.prompt,
|
||||
// The main prompt doubles as the HTML page requirement; legacy HTML tasks
|
||||
// stored that under ``html_config.page_prompt``, so prefer it on edit.
|
||||
prompt: html.page_prompt || task.prompt,
|
||||
execution_agent_name: task.execution_agent_name ?? "",
|
||||
require_markdown: !!task.execution_context?.require_markdown,
|
||||
task_kind: task.execution_context?.task_kind === "html_page" ? "html_page" : "agent",
|
||||
html_page_prompt: html.page_prompt ?? "",
|
||||
// Backward compatible: legacy HTML tasks only carry ``task_kind``.
|
||||
require_html:
|
||||
!!task.execution_context?.require_html ||
|
||||
task.execution_context?.task_kind === "html_page",
|
||||
html_negative_prompt: html.negative_prompt ?? "",
|
||||
html_reference_favorite_id: html.reference_favorite_id ?? "",
|
||||
html_model_name: html.model_name ?? "",
|
||||
@@ -270,20 +276,26 @@ export default function ScheduledTasksPage() {
|
||||
setScheduleConfig(DEFAULT_SCHEDULE_CONFIG);
|
||||
};
|
||||
|
||||
const isHtmlTask = form.task_kind === "html_page";
|
||||
const isHtmlTask = form.require_html;
|
||||
// The model actually shown/used: the user's choice, else the first configured
|
||||
// model. Never a hardcoded GLM-5 — it follows config order and the selection.
|
||||
const effectiveHtmlModel = form.html_model_name || availableModels[0]?.name || "";
|
||||
|
||||
// Build the execution_context for the current form. HTML tasks persist their
|
||||
// config under ``html_config``; regular tasks send an explicit ``agent`` kind
|
||||
// so switching a task back from HTML clears the old config.
|
||||
// Build the execution_context for the current form. ``require_html`` and
|
||||
// ``require_markdown`` are independent toggles (both may be on). HTML tasks
|
||||
// persist their extra config under ``html_config`` and reuse the main prompt
|
||||
// as the page requirement; ``task_kind`` is kept in sync only for backward
|
||||
// compatibility with older readers.
|
||||
const buildExecutionContext = (): ScheduledTaskExecutionContext => {
|
||||
if (!isHtmlTask) return { task_kind: "agent", html_config: undefined };
|
||||
if (!isHtmlTask) {
|
||||
return { require_html: false, task_kind: "agent", html_config: undefined };
|
||||
}
|
||||
return {
|
||||
require_html: true,
|
||||
task_kind: "html_page",
|
||||
html_config: {
|
||||
page_prompt: form.html_page_prompt.trim() || null,
|
||||
// The main prompt doubles as the page requirement.
|
||||
page_prompt: form.prompt.trim() || null,
|
||||
negative_prompt: form.html_negative_prompt.trim() || null,
|
||||
reference_favorite_id: form.html_reference_favorite_id || null,
|
||||
// Send the user's selection; fall back to the first configured model,
|
||||
@@ -297,11 +309,10 @@ export default function ScheduledTasksPage() {
|
||||
try {
|
||||
const cronSchedule = buildCron(scheduleConfig);
|
||||
const executionContext = buildExecutionContext();
|
||||
// HTML tasks may leave the agent prompt empty — fall back to the page
|
||||
// prompt so the (non-null) prompt column always has something useful.
|
||||
const effectivePrompt = isHtmlTask
|
||||
? form.prompt.trim() || form.html_page_prompt.trim() || "生成 HTML 页面"
|
||||
: form.prompt;
|
||||
// The (non-null) prompt column always needs something useful; HTML tasks
|
||||
// may leave it sparse, so fall back to a generic instruction.
|
||||
const effectivePrompt =
|
||||
form.prompt.trim() || (isHtmlTask ? "生成 HTML 页面" : form.prompt);
|
||||
if (creating) {
|
||||
const task = await actions.create.mutateAsync({
|
||||
name: form.name || "定时任务",
|
||||
@@ -309,8 +320,8 @@ export default function ScheduledTasksPage() {
|
||||
schedule: cronSchedule,
|
||||
timezone: form.timezone || "Asia/Shanghai",
|
||||
prompt: effectivePrompt,
|
||||
execution_agent_name: isHtmlTask ? null : form.execution_agent_name || null,
|
||||
require_markdown: isHtmlTask ? false : form.require_markdown,
|
||||
execution_agent_name: form.execution_agent_name || null,
|
||||
require_markdown: form.require_markdown,
|
||||
execution_context: executionContext,
|
||||
});
|
||||
setSelectedTaskId(task.task_id);
|
||||
@@ -324,8 +335,8 @@ export default function ScheduledTasksPage() {
|
||||
schedule: cronSchedule,
|
||||
timezone: form.timezone,
|
||||
prompt: effectivePrompt,
|
||||
execution_agent_name: isHtmlTask ? null : form.execution_agent_name || null,
|
||||
require_markdown: isHtmlTask ? false : form.require_markdown,
|
||||
execution_agent_name: form.execution_agent_name || null,
|
||||
require_markdown: form.require_markdown,
|
||||
execution_context: executionContext,
|
||||
},
|
||||
});
|
||||
@@ -502,7 +513,8 @@ export default function ScheduledTasksPage() {
|
||||
<Globe className="size-3" />已发布
|
||||
</Badge>
|
||||
)}
|
||||
{task.execution_context?.task_kind === "html_page" && (
|
||||
{(task.execution_context?.require_html ||
|
||||
task.execution_context?.task_kind === "html_page") && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="shrink-0 border-sky-500/30 bg-sky-500/10 text-xs text-sky-600 dark:text-sky-400"
|
||||
@@ -953,20 +965,6 @@ export default function ScheduledTasksPage() {
|
||||
}
|
||||
placeholder="任务描述(可选,将显示在公开页标题下方)"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-muted-foreground text-xs">任务类型</p>
|
||||
<Tabs
|
||||
value={form.task_kind}
|
||||
onValueChange={(v) =>
|
||||
setForm((value) => ({ ...value, task_kind: v as "agent" | "html_page" }))
|
||||
}
|
||||
>
|
||||
<TabsList className="w-full">
|
||||
<TabsTrigger value="agent" className="flex-1">普通任务</TabsTrigger>
|
||||
<TabsTrigger value="html_page" className="flex-1">HTML 页面</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-muted-foreground text-xs">执行频率</p>
|
||||
<ScheduleBuilder value={scheduleConfig} onChange={setScheduleConfig} />
|
||||
@@ -979,77 +977,83 @@ export default function ScheduledTasksPage() {
|
||||
placeholder="时区,例如:Asia/Shanghai"
|
||||
/>
|
||||
|
||||
{!isHtmlTask && (
|
||||
<>
|
||||
<Select
|
||||
value={form.execution_agent_name || "__default__"}
|
||||
onValueChange={(v) =>
|
||||
setForm((value) => ({
|
||||
...value,
|
||||
execution_agent_name: v === "__default__" ? "" : v,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full min-w-0 max-w-full overflow-hidden [&>span]:min-w-0 [&>span]:flex-1 [&>span]:truncate">
|
||||
<SelectValue placeholder="选择执行智能体" />
|
||||
</SelectTrigger>
|
||||
<SelectContent
|
||||
position="popper"
|
||||
className="w-[var(--radix-select-trigger-width)] max-w-[var(--radix-select-trigger-width)]"
|
||||
>
|
||||
<SelectItem value="__default__">默认助手(不指定)</SelectItem>
|
||||
{agents.map((agent) => (
|
||||
<SelectItem key={agent.id} value={agent.id}>
|
||||
<span className="block truncate">
|
||||
{desensitize(agent.name)}
|
||||
{agent.description ? ` — ${desensitize(agent.description)}` : ""}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Textarea
|
||||
className="min-h-40"
|
||||
value={form.prompt}
|
||||
onChange={(event) =>
|
||||
setForm((value) => ({ ...value, prompt: event.target.value }))
|
||||
}
|
||||
placeholder="任务 Prompt"
|
||||
/>
|
||||
<div className="flex items-center justify-between rounded-md border p-3">
|
||||
<div className="min-w-0 pr-3">
|
||||
<p className="text-sm font-medium">必须生成 Markdown 文件</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
开启后会提示大模型生成 Markdown(.md) 文件;系统会校验本次是否生成了 .md 文件且文件正文超过 100 字,未达到则记为失败。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={form.require_markdown}
|
||||
onCheckedChange={(v) =>
|
||||
setForm((value) => ({ ...value, require_markdown: v }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Select
|
||||
value={form.execution_agent_name || "__default__"}
|
||||
onValueChange={(v) =>
|
||||
setForm((value) => ({
|
||||
...value,
|
||||
execution_agent_name: v === "__default__" ? "" : v,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full min-w-0 max-w-full overflow-hidden [&>span]:min-w-0 [&>span]:flex-1 [&>span]:truncate">
|
||||
<SelectValue placeholder="选择执行智能体" />
|
||||
</SelectTrigger>
|
||||
<SelectContent
|
||||
position="popper"
|
||||
className="w-[var(--radix-select-trigger-width)] max-w-[var(--radix-select-trigger-width)]"
|
||||
>
|
||||
<SelectItem value="__default__">默认助手(不指定)</SelectItem>
|
||||
{agents.map((agent) => (
|
||||
<SelectItem key={agent.id} value={agent.id}>
|
||||
<span className="block truncate">
|
||||
{desensitize(agent.name)}
|
||||
{agent.description ? ` — ${desensitize(agent.description)}` : ""}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Textarea
|
||||
className="min-h-40"
|
||||
value={form.prompt}
|
||||
onChange={(event) =>
|
||||
setForm((value) => ({ ...value, prompt: event.target.value }))
|
||||
}
|
||||
placeholder={
|
||||
isHtmlTask
|
||||
? "任务 / 页面生成要求,例如:检索新能源车最新舆情,生成适合领导快速浏览的态势页"
|
||||
: "任务 Prompt"
|
||||
}
|
||||
/>
|
||||
<div className="flex items-center justify-between rounded-md border p-3">
|
||||
<div className="min-w-0 pr-3">
|
||||
<p className="text-sm font-medium">必须生成 Markdown 文件</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
开启后会提示大模型生成 Markdown(.md) 文件;系统会校验本次是否生成了 .md 文件且文件正文超过 100 字,未达到则记为失败。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={form.require_markdown}
|
||||
onCheckedChange={(v) =>
|
||||
setForm((value) => ({ ...value, require_markdown: v }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-md border p-3">
|
||||
<div className="min-w-0 pr-3">
|
||||
<p className="text-sm font-medium">必须生成 HTML 页面</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
开启后会先用智能体检索整理真实数据,再渲染为完整 HTML 页面(可在执行记录中预览)。可与“必须生成 Markdown”同时开启。
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={form.require_html}
|
||||
onCheckedChange={(v) =>
|
||||
setForm((value) => ({ ...value, require_html: v }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isHtmlTask && (
|
||||
<>
|
||||
<Textarea
|
||||
className="min-h-24"
|
||||
value={form.html_page_prompt}
|
||||
onChange={(event) =>
|
||||
setForm((value) => ({ ...value, html_page_prompt: event.target.value }))
|
||||
}
|
||||
placeholder="页面生成要求,例如:检索新能源车最新舆情,生成适合领导快速浏览的态势页"
|
||||
/>
|
||||
<Textarea
|
||||
className="min-h-16"
|
||||
value={form.html_negative_prompt}
|
||||
onChange={(event) =>
|
||||
setForm((value) => ({ ...value, html_negative_prompt: event.target.value }))
|
||||
}
|
||||
placeholder="限制条件(可选),例如:不要使用深色背景,不要出现外部链接脚本"
|
||||
placeholder="HTML 限制条件(可选),例如:不要使用深色背景,不要出现外部链接脚本"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-muted-foreground text-xs">参考收藏页面(可选)</p>
|
||||
@@ -1127,14 +1131,6 @@ export default function ScheduledTasksPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Textarea
|
||||
className="min-h-16"
|
||||
value={form.prompt}
|
||||
onChange={(event) =>
|
||||
setForm((value) => ({ ...value, prompt: event.target.value }))
|
||||
}
|
||||
placeholder="补充说明(可选):进一步约束智能体如何检索数据、页面包含哪些内容"
|
||||
/>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
执行流程:智能体运用自身检索技能整理真实数据 → 选定模型渲染为完整 HTML → 自动安全清洗与结构校验 → 可在执行记录中预览。
|
||||
</p>
|
||||
|
||||
Reference in new issue
Block a user