代码提交
This commit is contained in:
1 parent
7d249a81ce
commit
4948a80c56
3 files changed
+64
-137
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import type { BaseStream } from "@langchain/langgraph-sdk/react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Bot, CheckCircle2, FileText, LockKeyhole, Sparkles, Target, Trash2 } from "lucide-react";
|
||||
import { Bot, CheckCircle2, FileText, LockKeyhole, Sparkles, Target } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button as TButton } from "tdesign-react";
|
||||
|
||||
@@ -9,7 +9,7 @@ import { MessageList } from "@/components/workspace/messages";
|
||||
import { ThreadContext } from "@/components/workspace/messages/context";
|
||||
import { useAgent } from "@/core/agents";
|
||||
import { useLocalSettings } from "@/core/settings";
|
||||
import { useClearThreadMessages, useThreadStream } from "@/core/threads/hooks";
|
||||
import { useThreadStream } from "@/core/threads/hooks";
|
||||
import type { AgentThreadState } from "@/core/threads/types";
|
||||
import { textOfMessage } from "@/core/threads/utils";
|
||||
import { uuid } from "@/core/utils/uuid";
|
||||
@@ -229,10 +229,6 @@ export function PositionBuiltinAgentChat({
|
||||
useState<PendingBuiltinSubmission | null>(null);
|
||||
const pendingSubmissionRef = useRef(false);
|
||||
const handledAutoStartRequestRef = useRef<number | undefined>(undefined);
|
||||
const clearThreadMessages = useClearThreadMessages();
|
||||
const [externallyHiddenMessageIds, setExternallyHiddenMessageIds] = useState<Set<string>>(
|
||||
() => new Set(),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setAssistantId(fallbackAgentId);
|
||||
@@ -243,7 +239,6 @@ export function PositionBuiltinAgentChat({
|
||||
pendingSubmissionRef.current = false;
|
||||
setPendingSubmission(null);
|
||||
handledAutoStartRequestRef.current = undefined;
|
||||
setExternallyHiddenMessageIds(new Set());
|
||||
// Do not depend on `persistedThreadId` here. Preparing the automatic
|
||||
// first question can persist a thread id while the page's initial
|
||||
// session-request is still resolving. Treating that update as a new
|
||||
@@ -483,31 +478,6 @@ export function PositionBuiltinAgentChat({
|
||||
[activeThreadId, onPreviewArtifact],
|
||||
);
|
||||
|
||||
const handleClearConversation = useCallback(async () => {
|
||||
const threadId = boundThreadIdRef.current;
|
||||
if (!threadId) {
|
||||
toast.info("当前智能体还没有可清空的对话");
|
||||
return;
|
||||
}
|
||||
if (thread.isLoading || isSubmitting) {
|
||||
toast.info("智能体正在生成,完成后再清空对话");
|
||||
return;
|
||||
}
|
||||
const confirmed = window.confirm("确定清空当前智能体对话吗?清空后不可恢复。");
|
||||
if (!confirmed) return;
|
||||
|
||||
const fallbackIds = thread.messages
|
||||
.map((message) => message.id)
|
||||
.filter((id): id is string => Boolean(id));
|
||||
try {
|
||||
const deletedIds = await clearThreadMessages.mutateAsync({ threadId });
|
||||
setExternallyHiddenMessageIds(new Set(deletedIds.length ? deletedIds : fallbackIds));
|
||||
toast.success("对话已清空");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "清空对话失败");
|
||||
}
|
||||
}, [clearThreadMessages, isSubmitting, thread.isLoading, thread.messages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!autoStartRequestKey ||
|
||||
@@ -563,16 +533,6 @@ export function PositionBuiltinAgentChat({
|
||||
onSelect={onSelectBuiltinAgent}
|
||||
/>
|
||||
)}
|
||||
<TButton
|
||||
size="small"
|
||||
variant="outline"
|
||||
shape="circle"
|
||||
className="!h-8 !w-8 !min-w-8 !shrink-0 !p-0 !bg-white/90 dark:!border-[#2b8ffe]/55 dark:!bg-[#082d5f]/90"
|
||||
icon={<Trash2 className="!size-4 !shrink-0" />}
|
||||
disabled={!boundThreadId || thread.isLoading || isSubmitting || clearThreadMessages.isPending}
|
||||
onClick={() => void handleClearConversation()}
|
||||
title="清空当前智能体对话"
|
||||
/>
|
||||
</header>
|
||||
|
||||
{persistedArtifacts.length > 0 && (
|
||||
@@ -625,7 +585,6 @@ export function PositionBuiltinAgentChat({
|
||||
}
|
||||
onEditResubmit={(text) => void handleSubmit({ text, files: [] })}
|
||||
onOpenArtifact={handleOpenMessageArtifact}
|
||||
externallyHiddenMessageIds={externallyHiddenMessageIds}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex min-h-0 flex-1 flex-col items-center justify-center px-8 text-center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BaseStream } from "@langchain/langgraph-sdk/react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Bot, FileText, LockKeyhole, Settings, Trash2 } from "lucide-react";
|
||||
import { Bot, FileText, LockKeyhole, Settings } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button as TButton, Select as TSelect } from "tdesign-react";
|
||||
import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||
@@ -15,7 +15,7 @@ import { ThreadContext } from "@/components/workspace/messages/context";
|
||||
import { MessageList } from "@/components/workspace/messages";
|
||||
import { useAgent } from "@/core/agents";
|
||||
import { useLocalSettings } from "@/core/settings";
|
||||
import { useClearThreadMessages, useEditMessage, useThreadStream } from "@/core/threads/hooks";
|
||||
import { useEditMessage, useThreadStream } from "@/core/threads/hooks";
|
||||
import type { AgentThreadState } from "@/core/threads/types";
|
||||
import { textOfMessage } from "@/core/threads/utils";
|
||||
import { uuid } from "@/core/utils/uuid";
|
||||
@@ -163,7 +163,7 @@ function buildWriteFileVirtualUrl(path: string, messageId: string, toolCallId: s
|
||||
|
||||
function latestStreamingWriteFile(
|
||||
messages: AgentThreadState["messages"],
|
||||
): { path: string; name: string; mimeType: string | null } | null {
|
||||
): { key: string; path: string; name: string; mimeType: string | null } | null {
|
||||
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
||||
const message = messages[messageIndex] as (typeof messages)[number] & {
|
||||
id?: string;
|
||||
@@ -179,6 +179,7 @@ function latestStreamingWriteFile(
|
||||
if (typeof content !== "string" || content.length === 0) continue;
|
||||
const virtualPath = buildWriteFileVirtualUrl(rawPath, message.id, toolCall.id);
|
||||
return {
|
||||
key: `${message.id}:${toolCall.id}`,
|
||||
path: virtualPath,
|
||||
name: artifactDisplayName(rawPath),
|
||||
mimeType: rawPath.toLowerCase().endsWith(".md") ? "text/markdown" : null,
|
||||
@@ -188,6 +189,28 @@ function latestStreamingWriteFile(
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tool-call id is stable within a persisted thread. Capturing the existing
|
||||
* ids right before a new submission lets the streaming preview distinguish a
|
||||
* new write_file from an older deliverable kept in the message history.
|
||||
*/
|
||||
function writeFileCallKeys(messages: AgentThreadState["messages"]): Set<string> {
|
||||
const keys = new Set<string>();
|
||||
for (const rawMessage of messages) {
|
||||
const message = rawMessage as (typeof messages)[number] & {
|
||||
id?: string;
|
||||
tool_calls?: Array<{ id?: string; name?: string }>;
|
||||
};
|
||||
if (message.type !== "ai" || !message.id || !Array.isArray(message.tool_calls)) continue;
|
||||
for (const toolCall of message.tool_calls) {
|
||||
if (toolCall?.name === "write_file" && toolCall.id) {
|
||||
keys.add(`${message.id}:${toolCall.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct single-agent chat for one frozen business-chain node.
|
||||
*
|
||||
@@ -232,12 +255,10 @@ export function PositionNodeChat({
|
||||
const draftThreadIdRef = useRef(node.threadId ?? uuid());
|
||||
const upstreamRevisionRef = useRef<Record<string, unknown> | null>(null);
|
||||
const autoPreviewedArtifactKeyRef = useRef<string | null>(null);
|
||||
const clearThreadMessages = useClearThreadMessages();
|
||||
const autoPreviewBaselineCallKeysRef = useRef<Set<string>>(new Set());
|
||||
const autoPreviewRunArmedRef = useRef(false);
|
||||
const editMessage = useEditMessage();
|
||||
const [messageContentOverrides, setMessageContentOverrides] = useState<Record<string, string>>({});
|
||||
const [externallyHiddenMessageIds, setExternallyHiddenMessageIds] = useState<Set<string>>(
|
||||
() => new Set(),
|
||||
);
|
||||
const nodeArtifacts = useMemo(() => readNodeArtifacts(node.artifactManifest), [node.artifactManifest]);
|
||||
const canWrite =
|
||||
sessionStatus !== "archived" &&
|
||||
@@ -250,7 +271,8 @@ export function PositionNodeChat({
|
||||
setBoundThreadId(node.threadId);
|
||||
boundThreadIdRef.current = node.threadId;
|
||||
setMessageContentOverrides({});
|
||||
setExternallyHiddenMessageIds(new Set());
|
||||
autoPreviewBaselineCallKeysRef.current = new Set();
|
||||
autoPreviewRunArmedRef.current = false;
|
||||
}, [node.nodeKey, node.threadId]);
|
||||
|
||||
const bindCreatedThread = useCallback(
|
||||
@@ -399,31 +421,6 @@ export function PositionNodeChat({
|
||||
sessionId,
|
||||
],
|
||||
);
|
||||
const handleClearConversation = useCallback(async () => {
|
||||
const threadId = boundThreadIdRef.current;
|
||||
if (!threadId) {
|
||||
toast.info("当前智能体还没有可清空的对话");
|
||||
return;
|
||||
}
|
||||
if (thread.isLoading) {
|
||||
toast.info("智能体正在生成,完成后再清空对话");
|
||||
return;
|
||||
}
|
||||
const confirmed = window.confirm("确定清空当前智能体对话吗?清空后不可恢复。");
|
||||
if (!confirmed) return;
|
||||
|
||||
const fallbackIds = thread.messages
|
||||
.map((message) => message.id)
|
||||
.filter((id): id is string => Boolean(id));
|
||||
try {
|
||||
const deletedIds = await clearThreadMessages.mutateAsync({ threadId });
|
||||
setExternallyHiddenMessageIds(new Set(deletedIds.length ? deletedIds : fallbackIds));
|
||||
setMessageContentOverrides({});
|
||||
toast.success("对话已清空");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "清空对话失败");
|
||||
}
|
||||
}, [clearThreadMessages, thread.isLoading, thread.messages]);
|
||||
const hasMultipleSwitchOptions = switchOptions.length > 1;
|
||||
const switchSelectOptions = useMemo(
|
||||
() =>
|
||||
@@ -471,7 +468,14 @@ export function PositionNodeChat({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!thread.isLoading || !streamingArtifact) return;
|
||||
if (
|
||||
!thread.isLoading ||
|
||||
!streamingArtifact ||
|
||||
!autoPreviewRunArmedRef.current ||
|
||||
autoPreviewBaselineCallKeysRef.current.has(streamingArtifact.key)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// One write_file call should open at most one preview. The parent further
|
||||
// guards manual close state, because it owns the page-wide singleton sandbox.
|
||||
const artifactKey = `${activeThreadId}:${streamingArtifact.path}`;
|
||||
@@ -519,11 +523,16 @@ export function PositionNodeChat({
|
||||
upstreamRevision && typeof upstreamRevision === "object" && !Array.isArray(upstreamRevision)
|
||||
? (upstreamRevision as Record<string, unknown>)
|
||||
: null;
|
||||
// A thread retains historical write_file tool calls. Only files whose
|
||||
// call id appears after this exact submission may open the sandbox.
|
||||
autoPreviewBaselineCallKeysRef.current = writeFileCallKeys(thread.messages);
|
||||
autoPreviewRunArmedRef.current = true;
|
||||
await sendMessage(boundThreadIdRef.current ?? draftThreadIdRef.current, message, {
|
||||
agent_id: node.agentId,
|
||||
task_external_context: taskExternalContext,
|
||||
});
|
||||
} catch (error) {
|
||||
autoPreviewRunArmedRef.current = false;
|
||||
toast.error(`发送失败:${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
},
|
||||
@@ -536,6 +545,7 @@ export function PositionNodeChat({
|
||||
sendMessage,
|
||||
sessionId,
|
||||
sessionStatus,
|
||||
thread.messages,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -615,16 +625,6 @@ export function PositionNodeChat({
|
||||
查看驳回理由
|
||||
</TButton>
|
||||
)}
|
||||
<TButton
|
||||
size="small"
|
||||
variant="outline"
|
||||
shape="circle"
|
||||
className="!h-8 !w-8 !min-w-8 !shrink-0 !p-0 !bg-white/90 dark:!border-[#2b8ffe]/55 dark:!bg-[#082d5f]/90"
|
||||
icon={<Trash2 className="!size-4 !shrink-0" />}
|
||||
disabled={!boundThreadId || thread.isLoading || clearThreadMessages.isPending}
|
||||
onClick={() => void handleClearConversation()}
|
||||
title="清空当前智能体对话"
|
||||
/>
|
||||
{onOpenConfiguration && (
|
||||
<TButton
|
||||
size="small"
|
||||
@@ -690,7 +690,6 @@ export function PositionNodeChat({
|
||||
onEditResubmit={(text) => void handleSubmit({ text, files: [] })}
|
||||
onEditSave={handleSaveMessageEdit}
|
||||
messageContentOverrides={messageContentOverrides}
|
||||
externallyHiddenMessageIds={externallyHiddenMessageIds}
|
||||
onOpenArtifact={handleOpenMessageArtifact}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
Settings,
|
||||
Sparkles,
|
||||
Target,
|
||||
Trash2,
|
||||
UserRoundCog,
|
||||
XCircle,
|
||||
type LucideIcon,
|
||||
@@ -67,7 +66,6 @@ import { useAgent } from "@/core/agents";
|
||||
import { loadArtifactContent } from "@/core/artifacts/loader";
|
||||
import type { Agent } from "@/core/agents";
|
||||
import { useModels } from "@/core/models/hooks";
|
||||
import { useClearThreadMessages } from "@/core/threads/hooks";
|
||||
import type { AgentThreadState } from "@/core/threads/types";
|
||||
import { textOfMessage } from "@/core/threads/utils";
|
||||
import { desensitize } from "@/lib/desensitize";
|
||||
@@ -517,7 +515,6 @@ export default function PositionRoundtablePage({
|
||||
const [sandboxArtifact, setSandboxArtifact] = useState<PositionArtifactPreview | null>(null);
|
||||
const sandboxArtifactRef = useRef<PositionArtifactPreview | null>(null);
|
||||
const closedSandboxKeyRef = useRef<string | null>(null);
|
||||
const suppressedAutoPreviewThreadIdsRef = useRef<Set<string>>(new Set());
|
||||
const [sandboxThreadSnapshot, setSandboxThreadSnapshot] = useState<SandboxThreadSnapshot | null>(null);
|
||||
const latestNodeThreadSnapshotRef = useRef<SandboxThreadSnapshot | null>(null);
|
||||
const pendingSandboxThreadSnapshotRef = useRef<SandboxThreadSnapshot | null>(null);
|
||||
@@ -634,7 +631,6 @@ export default function PositionRoundtablePage({
|
||||
})),
|
||||
[models],
|
||||
);
|
||||
const clearIntentThreadMessages = useClearThreadMessages();
|
||||
const triggerToast = useCallback((message: string) => toast.info(message), []);
|
||||
const intent = useStep1Intent({
|
||||
// 全岗位共用原会商的对话接口与输入面板;岗位运行编排后续再按角色接入。
|
||||
@@ -651,26 +647,6 @@ export default function PositionRoundtablePage({
|
||||
const persistedIntentRef = useRef<string | null>(null);
|
||||
const isArchivedSession = positionSession?.status === "archived";
|
||||
|
||||
const handleClearIntentConversation = useCallback(async () => {
|
||||
if (intent.isIntentSending) {
|
||||
toast.info("意图理解正在生成,完成后再清空对话");
|
||||
return;
|
||||
}
|
||||
const confirmed = window.confirm("确定清空意图理解对话吗?清空后不可恢复。");
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
if (intent.intentThreadId) {
|
||||
await clearIntentThreadMessages.mutateAsync({
|
||||
threadId: intent.intentThreadId,
|
||||
});
|
||||
}
|
||||
intent.clearInteractiveMessages();
|
||||
toast.success("意图理解对话已清空");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "清空意图理解对话失败");
|
||||
}
|
||||
}, [clearIntentThreadMessages, intent]);
|
||||
|
||||
const syncSessionUrl = useCallback(
|
||||
(sessionId: string, nextTaskId: string) => {
|
||||
const next = new URLSearchParams(searchParamsString);
|
||||
@@ -836,6 +812,16 @@ export default function PositionRoundtablePage({
|
||||
}, [positionSession?.chainSnapshot, positionSession?.status, selectedChain]);
|
||||
const isIntelligenceDeliveryOverview =
|
||||
activePosition.isIntentPosition && positionSession !== null && positionSession.status !== "intent_pending";
|
||||
const previousIntelligenceDeliveryOverviewRef = useRef(isIntelligenceDeliveryOverview);
|
||||
useEffect(() => {
|
||||
// Sending the last intent answer transitions the page into the delivery
|
||||
// overview. Restore the right workspace at that moment instead of leaving
|
||||
// the user on an unexpectedly full-width, empty-looking conversation area.
|
||||
if (isIntelligenceDeliveryOverview && !previousIntelligenceDeliveryOverviewRef.current) {
|
||||
setRightPanelOpen(true);
|
||||
}
|
||||
previousIntelligenceDeliveryOverviewRef.current = isIntelligenceDeliveryOverview;
|
||||
}, [isIntelligenceDeliveryOverview]);
|
||||
const leftSidebarHidden = leftPanelCollapsed;
|
||||
const allPositionDeliveriesDone = useMemo(
|
||||
() => allExecutableNodesDone(positionSession?.nodes ?? []),
|
||||
@@ -1250,7 +1236,6 @@ export default function PositionRoundtablePage({
|
||||
ctx?.setOpen(false);
|
||||
}
|
||||
closedSandboxKeyRef.current = null;
|
||||
suppressedAutoPreviewThreadIdsRef.current.clear();
|
||||
setSandboxArtifact(null);
|
||||
setSandboxThreadSnapshot(null);
|
||||
intent.resetIntent();
|
||||
@@ -1329,14 +1314,12 @@ export default function PositionRoundtablePage({
|
||||
|
||||
const artifactKey = sandboxCloseKey(artifact);
|
||||
if (artifact.auto) {
|
||||
if (
|
||||
closedSandboxKeyRef.current === artifactKey ||
|
||||
suppressedAutoPreviewThreadIdsRef.current.has(artifact.threadId)
|
||||
) {
|
||||
// Closing an old file should only suppress that exact preview. A later
|
||||
// write_file in the same thread is a new deliverable and must still be
|
||||
// able to open its own streaming sandbox.
|
||||
if (closedSandboxKeyRef.current === artifactKey) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
suppressedAutoPreviewThreadIdsRef.current.delete(artifact.threadId);
|
||||
}
|
||||
closedSandboxKeyRef.current = null;
|
||||
|
||||
@@ -1453,7 +1436,6 @@ export default function PositionRoundtablePage({
|
||||
const current = sandboxArtifactRef.current;
|
||||
if (current) {
|
||||
closedSandboxKeyRef.current = sandboxCloseKey(current);
|
||||
suppressedAutoPreviewThreadIdsRef.current.add(current.threadId);
|
||||
}
|
||||
const ctx = artifactsCtxRef.current;
|
||||
if (ctx?.open) {
|
||||
@@ -1554,18 +1536,6 @@ export default function PositionRoundtablePage({
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
headerRightSlot={
|
||||
<TButton
|
||||
size="small"
|
||||
variant="outline"
|
||||
shape="circle"
|
||||
className="!h-8 !w-8 !min-w-8 !shrink-0 !p-0 !bg-white/90 dark:!border-[#2b8ffe]/55 dark:!bg-[#082d5f]/90"
|
||||
icon={<Trash2 className="!size-4 !shrink-0" />}
|
||||
disabled={intent.isIntentSending || clearIntentThreadMessages.isPending}
|
||||
onClick={() => void handleClearIntentConversation()}
|
||||
title="清空意图理解对话"
|
||||
/>
|
||||
}
|
||||
interactiveMessages={intent.interactiveMessages}
|
||||
intentThreadId={intent.intentThreadId}
|
||||
intentReady={chainStarted ? frozenIntent ?? intent.intentReady : intent.intentReady}
|
||||
@@ -1578,7 +1548,6 @@ export default function PositionRoundtablePage({
|
||||
handleStartedIntentSend();
|
||||
return;
|
||||
}
|
||||
setRightPanelOpen(false);
|
||||
void intent.sendIntentMessage(text, files);
|
||||
}}
|
||||
onStopIntent={intent.stopIntentStream}
|
||||
@@ -1836,8 +1805,7 @@ export default function PositionRoundtablePage({
|
||||
)}
|
||||
|
||||
<section className="relative flex min-w-[420px] flex-1 flex-col overflow-hidden rounded-[4px] border border-slate-200 bg-white shadow-sm dark:border-[#2b8ffe]/45 dark:bg-[#032650]/80 dark:shadow-none dark:backdrop-blur-md">
|
||||
{!isIntelligenceDeliveryOverview &&
|
||||
!rightPanelOpen &&
|
||||
{!rightPanelOpen &&
|
||||
!sandboxOpen &&
|
||||
!(positionSession && activeNode && !activeBuiltinKind) && (
|
||||
<TButton
|
||||
@@ -1864,7 +1832,8 @@ export default function PositionRoundtablePage({
|
||||
builtinAgentOptions={showClosureAgents ? builtinAgentOptions : undefined}
|
||||
onSelectBuiltinAgent={showClosureAgents ? handleSelectBuiltinStage : undefined}
|
||||
onSessionUpdate={setPositionSession}
|
||||
onConversationStart={() => setRightPanelOpen(false)}
|
||||
// 测试分析岗的右侧交付工作区应持续可见;会话开始不再把它收起。
|
||||
onConversationStart={undefined}
|
||||
onPreviewArtifact={handlePreviewArtifact}
|
||||
onThreadSnapshot={handleNodeThreadSnapshot}
|
||||
/>
|
||||
@@ -1901,7 +1870,7 @@ export default function PositionRoundtablePage({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{activePosition.isIntentPosition && !isIntelligenceDeliveryOverview && rightPanelOpen && !sandboxOpen && (
|
||||
{activePosition.isIntentPosition && rightPanelOpen && !sandboxOpen && (
|
||||
<aside className="flex w-[400px] shrink-0 flex-col overflow-hidden rounded-[4px] border border-slate-200 bg-white shadow-sm dark:border-[#2b8ffe]/45 dark:bg-[#032650]/80 dark:text-[#c5cfe0] dark:shadow-none dark:backdrop-blur-md">
|
||||
<RightWorkspace
|
||||
selectedAgent={selectedAgent}
|
||||
|
||||
Reference in new issue
Block a user