sync update from LAN repo (source master-yx @ cf505220)
This commit is contained in:
1 parent
e243ec3a46
commit
19d3cb96dc
114 files changed
+14787
-451
No files matched your search
@@ -64,6 +64,10 @@ import {
|
||||
useThreadSettings,
|
||||
} from "@/core/settings";
|
||||
import { useThreadStream, stopActiveThreadBeforeClarificationReply } from "@/core/threads/hooks";
|
||||
import {
|
||||
myProjectConversationTypeFromSearchParams,
|
||||
myProjectThreadMetadata,
|
||||
} from "@/core/threads/project-metadata";
|
||||
import { useResearchWritingFeed } from "@/core/threads/use-research-writing-feed";
|
||||
import { clearResearchReportStream } from "@/core/threads/use-research-report-stream";
|
||||
import { resolveWritingModeReportPath } from "@/core/threads/writing-mode";
|
||||
@@ -286,16 +290,28 @@ export default function ChatPage() {
|
||||
}, [selectedAgentId, selectorAgents, mineAgents, squareAgents, selectorLoading]);
|
||||
|
||||
const { showNotification } = useNotification();
|
||||
const projectConversationType =
|
||||
myProjectConversationTypeFromSearchParams(searchParams);
|
||||
const newThreadMetadata = useMemo<Record<string, unknown> | undefined>(() => {
|
||||
const metadata: Record<string, unknown> = projectConversationType
|
||||
? myProjectThreadMetadata(projectConversationType)
|
||||
: {};
|
||||
if (knowledgeSelectionReady) {
|
||||
metadata.llmwiki_knowledge_base_ids = selectedKnowledgeBaseIds;
|
||||
metadata.assistant_knowledge_base_ids = selectedAssistantKnowledgeBaseIds;
|
||||
}
|
||||
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
||||
}, [
|
||||
knowledgeSelectionReady,
|
||||
projectConversationType,
|
||||
selectedAssistantKnowledgeBaseIds,
|
||||
selectedKnowledgeBaseIds,
|
||||
]);
|
||||
|
||||
const [thread, sendMessage, isUploading] = useThreadStream({
|
||||
threadId: isNewThread ? undefined : threadId,
|
||||
context: streamContext,
|
||||
threadMetadata: knowledgeSelectionReady
|
||||
? {
|
||||
llmwiki_knowledge_base_ids: selectedKnowledgeBaseIds,
|
||||
assistant_knowledge_base_ids: selectedAssistantKnowledgeBaseIds,
|
||||
}
|
||||
: undefined,
|
||||
threadMetadata: newThreadMetadata,
|
||||
isMock,
|
||||
onStart: (createdThreadId) => {
|
||||
knowledgeSelectionThread.current = createdThreadId;
|
||||
@@ -462,7 +478,9 @@ export default function ChatPage() {
|
||||
try {
|
||||
let targetThreadId = threadId;
|
||||
if (isNewThread) {
|
||||
const created = await getAPIClient(isMock).threads.create({});
|
||||
const created = await getAPIClient(isMock).threads.create({
|
||||
metadata: newThreadMetadata ?? {},
|
||||
});
|
||||
targetThreadId = created.thread_id;
|
||||
setThreadId(targetThreadId);
|
||||
setIsNewThread(false);
|
||||
@@ -522,13 +540,8 @@ export default function ChatPage() {
|
||||
try {
|
||||
const created = await getAPIClient(isMock).threads.create({
|
||||
metadata: {
|
||||
...newThreadMetadata,
|
||||
agent_id: selectedAgentId,
|
||||
...(knowledgeSelectionReady
|
||||
? {
|
||||
llmwiki_knowledge_base_ids: selectedKnowledgeBaseIds,
|
||||
assistant_knowledge_base_ids: selectedAssistantKnowledgeBaseIds,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
sessionStorage.setItem(
|
||||
@@ -547,7 +560,7 @@ export default function ChatPage() {
|
||||
);
|
||||
saveLastAgentId(selectedAgentId);
|
||||
router.push(
|
||||
`/page/workspace/agents/${selectedAgentId}/chats/${created.thread_id}`,
|
||||
`${routeBase}/agents/${encodeURIComponent(selectedAgentId)}/chats/${encodeURIComponent(created.thread_id)}`,
|
||||
);
|
||||
} catch (err) {
|
||||
agentHandoffPendingRef.current = false;
|
||||
@@ -606,6 +619,7 @@ export default function ChatPage() {
|
||||
selectedAssistantKnowledgeBaseIds,
|
||||
selectedKnowledgeBaseIds,
|
||||
knowledgeSelectionReady,
|
||||
newThreadMetadata,
|
||||
agentHandoffPending,
|
||||
isMock,
|
||||
router,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ChevronDown,
|
||||
CircleHelp,
|
||||
Clock3,
|
||||
FolderKanban,
|
||||
LoaderCircle,
|
||||
MessageSquareText,
|
||||
PanelLeftClose,
|
||||
@@ -20,7 +21,12 @@ import {
|
||||
} from "lucide-react";
|
||||
import { type FormEvent, useCallback, useMemo, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
import {
|
||||
MyProjectThreadBadge,
|
||||
MyProjectThreadIcon,
|
||||
} from "@/components/workspace/my-project-thread-indicator";
|
||||
import { useAgent, useAgents } from "@/core/agents";
|
||||
import { getAccountDisplayName } from "@/core/auth";
|
||||
import {
|
||||
@@ -29,28 +35,41 @@ import {
|
||||
useScheduledTaskRuns,
|
||||
useScheduledTasks,
|
||||
} from "@/core/scheduled-tasks";
|
||||
import { useLocalSettings } from "@/core/settings";
|
||||
import {
|
||||
useAppearanceSettings,
|
||||
useCitationDisplaySettings,
|
||||
} from "@/core/system-settings/hooks";
|
||||
import { useThreadStream, useThreads } from "@/core/threads/hooks";
|
||||
import {
|
||||
myProjectConversationTypeOfThread,
|
||||
myProjectConversationTypeFromSearchParams,
|
||||
myProjectThreadMetadata,
|
||||
} from "@/core/threads/project-metadata";
|
||||
import { textOfMessage, titleOfThread } from "@/core/threads/utils";
|
||||
import { uuid } from "@/core/utils/uuid";
|
||||
import {
|
||||
OFFICIAL_DEERFLOW_HOME_ROUTE,
|
||||
OFFICIAL_DEERFLOW_ROUTE_PREFIX,
|
||||
} from "@/core/official-deerflow/routes";
|
||||
import StrategyProjectsPage from "@/strategy-selection/StrategyProjectsPage";
|
||||
import StrategySelectionPage from "@/strategy-selection/StrategySelectionPage";
|
||||
|
||||
import "./official-deerflow-entry.css";
|
||||
|
||||
type OfficialPage = "chat" | "agents" | "agent-chat" | "scheduled" | "settings";
|
||||
type OfficialPage =
|
||||
| "chat"
|
||||
| "projects"
|
||||
| "agents"
|
||||
| "agent-chat"
|
||||
| "scheduled"
|
||||
| "settings";
|
||||
|
||||
function pageFromPath(pathname: string): OfficialPage {
|
||||
const path = pathname.slice(OFFICIAL_DEERFLOW_ROUTE_PREFIX.length);
|
||||
if (path.startsWith("/workspace/agents/") && path.includes("/chats")) {
|
||||
return "agent-chat";
|
||||
}
|
||||
if (path.startsWith("/workspace/projects")) return "projects";
|
||||
if (path.startsWith("/workspace/agents")) return "agents";
|
||||
if (path.startsWith("/workspace/scheduled-tasks")) return "scheduled";
|
||||
if (path.startsWith("/workspace/settings")) return "settings";
|
||||
@@ -78,6 +97,11 @@ function parseScheduledTaskId(pathname: string) {
|
||||
return match ? decodeURIComponent(match[1]!) : null;
|
||||
}
|
||||
|
||||
function parseProjectId(pathname: string) {
|
||||
const match = pathname.match(/\/workspace\/projects\/([^/]+)/);
|
||||
return match ? decodeURIComponent(match[1]!) : null;
|
||||
}
|
||||
|
||||
function readableError(error: unknown, fallback: string) {
|
||||
return error instanceof Error && error.message ? error.message : fallback;
|
||||
}
|
||||
@@ -159,20 +183,36 @@ function ChatWorkspace({
|
||||
routeThreadId: string;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [sendError, setSendError] = useState<string | null>(null);
|
||||
const isNewThread = routeThreadId === "new";
|
||||
const projectConversationType = isNewThread
|
||||
? myProjectConversationTypeFromSearchParams(
|
||||
new URLSearchParams(location.search),
|
||||
)
|
||||
: null;
|
||||
// A new conversation uses the same client-generated id approach as the
|
||||
// existing ChatPage. The backend owns the persisted id once streaming starts.
|
||||
const [draftThreadId] = useState<string>(() => uuid() as string);
|
||||
const activeThreadId = isNewThread ? draftThreadId : routeThreadId;
|
||||
const { agent } = useAgent(agentId, { enabled: !!agentId });
|
||||
const [localSettings] = useLocalSettings();
|
||||
const [thread, sendMessage, isUploading] = useThreadStream({
|
||||
threadId: isNewThread ? undefined : routeThreadId,
|
||||
assistantId: agentId ?? undefined,
|
||||
threadMetadata: projectConversationType
|
||||
? myProjectThreadMetadata(projectConversationType)
|
||||
: undefined,
|
||||
context: {
|
||||
...localSettings.context,
|
||||
// This compact official shell has no model/mode controls of its own.
|
||||
// Do not inherit a hidden selection saved by the customized workspace
|
||||
// (for example an obsolete or quota-exhausted model); an omitted model
|
||||
// lets the backend resolve the current system default or the agent's
|
||||
// globally configured model.
|
||||
model_name: undefined,
|
||||
mode: undefined,
|
||||
reasoning_effort: undefined,
|
||||
writing_mode: false,
|
||||
...(agentId ? { agent_id: agentId } : {}),
|
||||
},
|
||||
onStart: (createdThreadId) => {
|
||||
@@ -658,6 +698,10 @@ export default function OfficialDeerFlowEntry() {
|
||||
() => parseScheduledTaskId(location.pathname),
|
||||
[location.pathname],
|
||||
);
|
||||
const projectId = useMemo(
|
||||
() => parseProjectId(location.pathname),
|
||||
[location.pathname],
|
||||
);
|
||||
const { data: threads = [], isLoading: threadsLoading, error: threadsError } =
|
||||
useThreads({
|
||||
limit: 20,
|
||||
@@ -666,10 +710,12 @@ export default function OfficialDeerFlowEntry() {
|
||||
select: ["thread_id", "updated_at", "values", "metadata"],
|
||||
});
|
||||
const go = (path: string) => navigate(path);
|
||||
const projectBasePath = `${OFFICIAL_DEERFLOW_ROUTE_PREFIX}/workspace/projects`;
|
||||
|
||||
const nav = [
|
||||
{ icon: PlusSquare, label: "新对话", path: OFFICIAL_DEERFLOW_HOME_ROUTE, pages: ["chat"] },
|
||||
{ icon: MessageSquareText, label: "对话", path: `${OFFICIAL_DEERFLOW_ROUTE_PREFIX}/workspace/chats/new`, pages: ["chat"] },
|
||||
{ icon: FolderKanban, label: "我的项目", path: projectBasePath, pages: ["projects"] },
|
||||
{ icon: Bot, label: "智能体", path: `${OFFICIAL_DEERFLOW_ROUTE_PREFIX}/workspace/agents`, pages: ["agents", "agent-chat"] },
|
||||
{ icon: CalendarClock, label: "定时任务", path: `${OFFICIAL_DEERFLOW_ROUTE_PREFIX}/workspace/scheduled-tasks`, pages: ["scheduled"] },
|
||||
] as const;
|
||||
@@ -706,29 +752,43 @@ export default function OfficialDeerFlowEntry() {
|
||||
) : threadsError ? (
|
||||
<p className="official-sidebar-error">对话列表加载失败</p>
|
||||
) : threads.length ? (
|
||||
threads.map((chat) => (
|
||||
<button
|
||||
className={`official-recent-chat${chatRoute.threadId === chat.thread_id ? " is-active" : ""}`}
|
||||
key={chat.thread_id}
|
||||
onClick={() => {
|
||||
const agentId = typeof chat.context?.agent_id === "string"
|
||||
? chat.context.agent_id
|
||||
: typeof chat.metadata?.agent_id === "string"
|
||||
? chat.metadata.agent_id
|
||||
: null;
|
||||
go(
|
||||
agentId
|
||||
? `${OFFICIAL_DEERFLOW_ROUTE_PREFIX}/workspace/agents/${encodeURIComponent(agentId)}/chats/${encodeURIComponent(chat.thread_id)}`
|
||||
: `${OFFICIAL_DEERFLOW_ROUTE_PREFIX}/workspace/chats/${encodeURIComponent(chat.thread_id)}`,
|
||||
);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<MessageSquareText aria-hidden="true" size={15} />
|
||||
<span>{titleOfThread(chat, "未命名对话")}</span>
|
||||
<small>{formatTimestamp(chat.updated_at)}</small>
|
||||
</button>
|
||||
))
|
||||
threads.map((chat) => {
|
||||
const projectType = myProjectConversationTypeOfThread(chat);
|
||||
return (
|
||||
<button
|
||||
className={`official-recent-chat${chatRoute.threadId === chat.thread_id ? " is-active" : ""}`}
|
||||
key={chat.thread_id}
|
||||
onClick={() => {
|
||||
const agentId = typeof chat.context?.agent_id === "string"
|
||||
? chat.context.agent_id
|
||||
: typeof chat.metadata?.agent_id === "string"
|
||||
? chat.metadata.agent_id
|
||||
: null;
|
||||
go(
|
||||
agentId
|
||||
? `${OFFICIAL_DEERFLOW_ROUTE_PREFIX}/workspace/agents/${encodeURIComponent(agentId)}/chats/${encodeURIComponent(chat.thread_id)}`
|
||||
: `${OFFICIAL_DEERFLOW_ROUTE_PREFIX}/workspace/chats/${encodeURIComponent(chat.thread_id)}`,
|
||||
);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{projectType ? (
|
||||
<MyProjectThreadIcon className="size-[15px]" type={projectType} />
|
||||
) : (
|
||||
<MessageSquareText aria-hidden="true" size={15} />
|
||||
)}
|
||||
<span className="official-recent-chat-copy">
|
||||
<span className="official-recent-chat-title">
|
||||
{titleOfThread(chat, "未命名对话")}
|
||||
</span>
|
||||
{projectType && (
|
||||
<MyProjectThreadBadge type={projectType} />
|
||||
)}
|
||||
</span>
|
||||
<small>{formatTimestamp(chat.updated_at)}</small>
|
||||
</button>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<p className="official-sidebar-empty">暂无历史对话</p>
|
||||
)}
|
||||
@@ -754,6 +814,17 @@ export default function OfficialDeerFlowEntry() {
|
||||
routeThreadId={chatRoute.threadId}
|
||||
/>
|
||||
)}
|
||||
{page === "projects" && (
|
||||
projectId ? (
|
||||
<StrategySelectionPage
|
||||
key={projectId}
|
||||
projectBasePath={projectBasePath}
|
||||
projectId={projectId}
|
||||
/>
|
||||
) : (
|
||||
<StrategyProjectsPage basePath={projectBasePath} />
|
||||
)
|
||||
)}
|
||||
{page === "agents" && (
|
||||
<AgentsPage
|
||||
openAgent={(agentId) =>
|
||||
@@ -771,6 +842,7 @@ export default function OfficialDeerFlowEntry() {
|
||||
{page === "scheduled" && <ScheduledTasksPage selectedTaskId={scheduledTaskId} />}
|
||||
{page === "settings" && <SettingsPage />}
|
||||
</div>
|
||||
<Toaster position="top-center" />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Navigate, Route, Routes } from "react-router-dom";
|
||||
import { Navigate, Route, Routes, useParams } from "react-router-dom";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
||||
@@ -78,6 +78,8 @@ import UsersPage from "./UsersPage";
|
||||
import LlmMetricsPage from "./LlmMetricsPage";
|
||||
import AdminScheduledTasksPage from "./AdminScheduledTasksPage";
|
||||
import AdminUserActivityPage from "./AdminUserActivityPage";
|
||||
import StrategyProjectsPage from "@/strategy-selection/StrategyProjectsPage";
|
||||
import StrategySelectionPage from "@/strategy-selection/StrategySelectionPage";
|
||||
import { ChatRuntime } from "./WorkspaceRoutes";
|
||||
|
||||
/**
|
||||
@@ -98,6 +100,11 @@ function StrategyChatThreadLayout() {
|
||||
);
|
||||
}
|
||||
|
||||
function StrategyProjectWorkbenchRoute() {
|
||||
const { project_id: projectId = "" } = useParams<{ project_id: string }>();
|
||||
return <StrategySelectionPage key={projectId} projectId={projectId} />;
|
||||
}
|
||||
|
||||
export default function StrategyRoutes() {
|
||||
const storedAuth = getStoredAuth();
|
||||
const canManageBusinessMappings = !storedAuth || storedAuth.system_role === "admin";
|
||||
@@ -110,6 +117,20 @@ export default function StrategyRoutes() {
|
||||
<Route index element={<Navigate to="qa/general" replace />} />
|
||||
|
||||
<Route path="qa/general" element={<GeneralQA />} />
|
||||
<Route
|
||||
path="qa/projects"
|
||||
element={
|
||||
<StrategyProjectsPage basePath="/page/strategy/qa/projects" />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="qa/projects/:project_id"
|
||||
element={<StrategyProjectWorkbenchRoute />}
|
||||
/>
|
||||
<Route
|
||||
path="qa/strategy-selection"
|
||||
element={<Navigate to="/page/strategy/qa/projects" replace />}
|
||||
/>
|
||||
<Route
|
||||
path="qa/expert-qa"
|
||||
element={
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
.official-recent-section > p { color: #90908e; font-size: 12px; margin: 0 8px 9px; }
|
||||
.official-recent-chat { display: grid; font-size: 13px; gap: 8px; grid-template-columns: 15px minmax(0, 1fr) auto; padding: 8px; }
|
||||
.official-recent-chat span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.official-recent-chat-copy { align-items: center; display: flex; gap: 6px; min-width: 0; }
|
||||
.official-recent-chat-title { flex: 1; min-width: 0; }
|
||||
.official-recent-chat small { color: #acaca9; font-size: 11px; }
|
||||
.official-recent-chat.is-active { background: #efefed; color: #252525; }
|
||||
.official-sidebar-loading,
|
||||
|
||||
Reference in new issue
Block a user