Merge branch 'ji' into master
合并 ji 的功能:开放问答接口(/api/open/chat[/stream] 回传生成文件 + 临时不留存)、 技能地址管理、智能体详情「最近对话」等。 冲突 frontend StrategyRoutes.tsx 已解决:同时保留 admin/roundtable-diagnostics(master) 与 admin/skill-addresses(ji) 两个管理员路由。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
fbec222eb4
38 files changed
+3365
-94
No files matched your search
@@ -77,6 +77,7 @@ import {
|
||||
import { getAgent } from "@/core/agents/api";
|
||||
import type { Agent, AgentQuestion, UpdateAgentRequest } from "@/core/agents";
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { useLiveAccount } from "@/core/auth/use-live-account";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useSkills } from "@/core/skills/hooks";
|
||||
import { formatDateTime } from "@/core/utils/datetime";
|
||||
@@ -250,6 +251,8 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
|
||||
const storedAuth = useMemo(() => getStoredAuth(), []);
|
||||
const currentUserId = storedAuth?.user_id ?? null;
|
||||
// 发布请求实际以「实时」token 执行 —— 用 live 账号来提示发布者并检测账号是否被切换。
|
||||
const live = useLiveAccount();
|
||||
const isAdmin = storedAuth?.system_role === "admin";
|
||||
const isBuiltin = agent.builtin;
|
||||
const isOwned =
|
||||
@@ -424,18 +427,38 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
setPublishPickerOpen(true);
|
||||
return;
|
||||
}
|
||||
if (!warnIfAccountSwitched()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await updateAgent.mutateAsync({
|
||||
id: agent.id,
|
||||
request: { published: !agent.published },
|
||||
});
|
||||
toast.success(agent.published ? "已取消发布" : "已发布");
|
||||
toast.success(
|
||||
agent.published ? "已取消发布" : `已发布(操作账号:${live.displayName})`,
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
}
|
||||
|
||||
// 发布操作以实时 token 执行。若会话中途账号被切换(常见于另一标签页登录了别的
|
||||
// 账号),先弹一次确认 —— 避免误以本人之外的账号执行发布。返回 false = 用户取消。
|
||||
function warnIfAccountSwitched(): boolean {
|
||||
if (!live.switched) {
|
||||
return true;
|
||||
}
|
||||
return window.confirm(
|
||||
`当前登录账号已从「${live.mountDisplayName}」切换为「${live.displayName}」。\n` +
|
||||
`本次发布将以「${live.displayName}」执行。如需以「${live.mountDisplayName}」操作,请取消并刷新页面。\n\n仍要继续吗?`,
|
||||
);
|
||||
}
|
||||
|
||||
async function handlePublishToSquare(squareId: string) {
|
||||
if (!warnIfAccountSwitched()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await updateAgent.mutateAsync({
|
||||
id: agent.id,
|
||||
@@ -443,7 +466,7 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
});
|
||||
const sqName =
|
||||
publishSquares?.squares.find((s) => s.id === squareId)?.name ?? "广场";
|
||||
toast.success(`已发布到「${sqName}」`);
|
||||
toast.success(`已发布到「${sqName}」(操作账号:${live.displayName})`);
|
||||
setPublishPickerOpen(false);
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
@@ -1117,6 +1140,23 @@ export function AgentCard({ agent, layout = "card", onPublisherClick }: AgentCar
|
||||
loading={updateAgent.isPending}
|
||||
onConfirm={(squareId) => void handlePublishToSquare(squareId)}
|
||||
description="请选择要把该智能体发布到哪个广场。每个智能体只能归属一个广场,不会重复出现。"
|
||||
accountNotice={
|
||||
<div className="text-xs">
|
||||
<p className="text-muted-foreground">
|
||||
发布操作账号:
|
||||
<span className="text-foreground font-medium">
|
||||
「{live.displayName}」
|
||||
</span>
|
||||
</p>
|
||||
{live.switched && (
|
||||
<p className="text-destructive mt-1">
|
||||
⚠ 当前登录账号已从「{live.mountDisplayName}」切换为「
|
||||
{live.displayName}」。如需以「{live.mountDisplayName}
|
||||
」操作,请刷新页面确认。
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -70,6 +70,8 @@ import {
|
||||
} from "@/core/threads/hooks";
|
||||
import type { AgentThread, AgentThreadState } from "@/core/threads/types";
|
||||
import {
|
||||
bootstrapResumePath,
|
||||
isBootstrapThread,
|
||||
isThreadFavorite,
|
||||
isThreadPinned,
|
||||
sortThreadsPinnedFirst,
|
||||
@@ -338,14 +340,14 @@ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) {
|
||||
const [renameValue, setRenameValue] = useState("");
|
||||
const [shareThreadId, setShareThreadId] = useState<string | null>(null);
|
||||
|
||||
// 排除调度/引导/会商类会话,与首页一致;置顶优先。
|
||||
// 排除调度/会商类系统会话,与首页一致;智能体创建(bootstrap)会话保留展示
|
||||
// (带机器人图标、点击回创建页);置顶优先。
|
||||
const visibleThreads = useMemo(
|
||||
() =>
|
||||
sortThreadsPinnedFirst(
|
||||
threads.filter(
|
||||
(thread) =>
|
||||
thread.metadata?.thread_type !== "scheduler" &&
|
||||
thread.metadata?.thread_type !== "bootstrap" &&
|
||||
thread.metadata?.thread_type !== "roundtable",
|
||||
),
|
||||
),
|
||||
@@ -450,7 +452,8 @@ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) {
|
||||
</div>
|
||||
) : (
|
||||
filteredThreads.map((thread) => {
|
||||
const href = pathOf(thread);
|
||||
const isBoot = isBootstrapThread(thread);
|
||||
const href = isBoot ? bootstrapResumePath(thread.thread_id) : pathOf(thread);
|
||||
const isActive = href === pathname;
|
||||
const pinned = isThreadPinned(thread);
|
||||
const favorited = isThreadFavorite(thread);
|
||||
@@ -468,12 +471,22 @@ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) {
|
||||
: "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<MessageSquare
|
||||
className={cn(
|
||||
"size-3.5 shrink-0",
|
||||
isActive ? "text-primary" : "text-muted-foreground/70",
|
||||
)}
|
||||
/>
|
||||
{isBoot ? (
|
||||
<Bot
|
||||
className={cn(
|
||||
"size-3.5 shrink-0",
|
||||
isActive ? "text-primary" : "text-muted-foreground/70",
|
||||
)}
|
||||
aria-label="智能体创建会话"
|
||||
/>
|
||||
) : (
|
||||
<MessageSquare
|
||||
className={cn(
|
||||
"size-3.5 shrink-0",
|
||||
isActive ? "text-primary" : "text-muted-foreground/70",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{pinned && (
|
||||
<Pin className="text-muted-foreground/70 size-3 shrink-0" />
|
||||
)}
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { AgentQuestionEditor } from "@/components/workspace/agents/agent-question-editor";
|
||||
import { AgentRecentChats } from "@/components/workspace/agents/agent-recent-chats";
|
||||
import { useArtifactsOptional } from "@/components/workspace/artifacts";
|
||||
import { EnabledSkillList } from "@/components/workspace/agents/enabled-skill-list";
|
||||
import { SkillPickerSheet } from "@/components/workspace/agents/skill-picker-sheet";
|
||||
@@ -59,6 +60,7 @@ import {
|
||||
type UpdateAgentRequest,
|
||||
} from "@/core/agents";
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { useLiveAccount } from "@/core/auth/use-live-account";
|
||||
import { isAgentSkillCandidate } from "@/core/skills";
|
||||
import { useSkills } from "@/core/skills/hooks";
|
||||
import { usePublishSquares } from "@/core/system-settings/hooks";
|
||||
@@ -135,6 +137,7 @@ export function AgentDetailPanel({
|
||||
onAskQuestion,
|
||||
disabled,
|
||||
defaultCollapsed,
|
||||
showRecentChats,
|
||||
}: {
|
||||
agent: Agent | null | undefined;
|
||||
agentName: string;
|
||||
@@ -142,6 +145,8 @@ export function AgentDetailPanel({
|
||||
disabled?: boolean;
|
||||
/** 初始收起(任务工作区进入时默认收起智能体信息面板)。 */
|
||||
defaultCollapsed?: boolean;
|
||||
/** 在面板底部展示「有关此智能体的最近对话」(仅智能体问答页开启,任务工作区不展示)。 */
|
||||
showRecentChats?: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [collapsed, setCollapsed] = useState(defaultCollapsed ?? false);
|
||||
@@ -159,6 +164,8 @@ export function AgentDetailPanel({
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [publishPickerOpen, setPublishPickerOpen] = useState(false);
|
||||
const [detailSkillName, setDetailSkillName] = useState<string | null>(null);
|
||||
// 发布以实时 token 执行 —— 用 live 账号提示发布者并检测账号是否被切换。
|
||||
const live = useLiveAccount();
|
||||
|
||||
const updateAgent = useUpdateAgent();
|
||||
const deleteAgent = useDeleteAgent();
|
||||
@@ -343,19 +350,39 @@ export function AgentDetailPanel({
|
||||
setPublishPickerOpen(true);
|
||||
return;
|
||||
}
|
||||
if (!warnIfAccountSwitched()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await updateAgent.mutateAsync({
|
||||
id: agent.id,
|
||||
request: { published: !agent.published },
|
||||
});
|
||||
toast.success(agent.published ? "已下架" : "已发布");
|
||||
toast.success(
|
||||
agent.published ? "已下架" : `已发布(操作账号:${live.displayName})`,
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
}
|
||||
|
||||
// 发布以实时 token 执行。会话中途账号被切换(常见于另一标签页登录了别的账号)时
|
||||
// 先弹确认,避免误以本人之外的账号执行发布。返回 false = 用户取消。
|
||||
function warnIfAccountSwitched(): boolean {
|
||||
if (!live.switched) {
|
||||
return true;
|
||||
}
|
||||
return window.confirm(
|
||||
`当前登录账号已从「${live.mountDisplayName}」切换为「${live.displayName}」。\n` +
|
||||
`本次发布将以「${live.displayName}」执行。如需以「${live.mountDisplayName}」操作,请取消并刷新页面。\n\n仍要继续吗?`,
|
||||
);
|
||||
}
|
||||
|
||||
async function handlePublishToSquare(squareId: string) {
|
||||
if (!agent) return;
|
||||
if (!warnIfAccountSwitched()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await updateAgent.mutateAsync({
|
||||
id: agent.id,
|
||||
@@ -363,7 +390,7 @@ export function AgentDetailPanel({
|
||||
});
|
||||
const sqName =
|
||||
publishSquares?.squares.find((s) => s.id === squareId)?.name ?? "广场";
|
||||
toast.success(`已发布到「${sqName}」`);
|
||||
toast.success(`已发布到「${sqName}」(操作账号:${live.displayName})`);
|
||||
setPublishPickerOpen(false);
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
@@ -791,6 +818,9 @@ export function AgentDetailPanel({
|
||||
onViewDetail={setDetailSkillName}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{/* ── 最近对话(右下角,仅智能体问答页展示,编辑态隐藏)──────────── */}
|
||||
{showRecentChats && !editing && <AgentRecentChats agentId={agent.id} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -921,6 +951,23 @@ export function AgentDetailPanel({
|
||||
loading={updateAgent.isPending}
|
||||
onConfirm={(squareId) => void handlePublishToSquare(squareId)}
|
||||
description="请选择要把该智能体发布到哪个广场。每个智能体只能归属一个广场,不会重复出现。"
|
||||
accountNotice={
|
||||
<div className="text-xs">
|
||||
<p className="text-muted-foreground">
|
||||
发布操作账号:
|
||||
<span className="text-foreground font-medium">
|
||||
「{live.displayName}」
|
||||
</span>
|
||||
</p>
|
||||
{live.switched && (
|
||||
<p className="text-destructive mt-1">
|
||||
⚠ 当前登录账号已从「{live.mountDisplayName}」切换为「
|
||||
{live.displayName}」。如需以「{live.mountDisplayName}
|
||||
」操作,请刷新页面确认。
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { MessageSquareIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { useThreadsPage } from "@/core/threads/hooks";
|
||||
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
||||
import { desensitize } from "@/lib/desensitize";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const RECENT_LIMIT = 8;
|
||||
|
||||
/**
|
||||
* 「有关此智能体的最近对话」—— 渲染在智能体详情面板底部(右下角)。
|
||||
* 按 `metadata.agent_id` 过滤出该智能体的会话,点击直接打开对应对话。
|
||||
*/
|
||||
export function AgentRecentChats({ agentId }: { agentId: string }) {
|
||||
const pathname = usePathname();
|
||||
const { data } = useThreadsPage({
|
||||
page: 1,
|
||||
pageSize: RECENT_LIMIT,
|
||||
metadata: { agent_id: agentId },
|
||||
});
|
||||
// 该智能体的会话里同样排除调度 / 引导 / 会商类系统会话。
|
||||
const threads = (data?.threads ?? []).filter(
|
||||
(th) =>
|
||||
th.metadata?.thread_type !== "bootstrap" &&
|
||||
th.metadata?.thread_type !== "scheduler" &&
|
||||
th.metadata?.thread_type !== "roundtable",
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="bg-card/60 rounded-xl border p-4">
|
||||
<div className="mb-3 flex items-center justify-between gap-2">
|
||||
<h4 className="text-foreground flex items-center gap-1.5 text-sm font-semibold">
|
||||
<MessageSquareIcon className="size-3.5" />
|
||||
最近对话
|
||||
</h4>
|
||||
<span className="text-muted-foreground text-xs tabular-nums">
|
||||
{threads.length} 个
|
||||
</span>
|
||||
</div>
|
||||
{threads.length === 0 ? (
|
||||
<p className="text-muted-foreground/60 text-sm">暂无对话</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1">
|
||||
{threads.map((thread) => {
|
||||
const href = pathOfThread(thread);
|
||||
const isActive = href === pathname;
|
||||
return (
|
||||
<Link
|
||||
key={thread.thread_id}
|
||||
href={href}
|
||||
title={desensitize(titleOfThread(thread, "新对话"))}
|
||||
className={cn(
|
||||
"flex min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-sm transition-colors",
|
||||
isActive
|
||||
? "bg-primary/5 text-primary font-medium"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<MessageSquareIcon
|
||||
className={cn(
|
||||
"size-3.5 shrink-0",
|
||||
isActive ? "text-primary" : "text-muted-foreground/70",
|
||||
)}
|
||||
/>
|
||||
<span className="truncate">
|
||||
{desensitize(titleOfThread(thread, "新对话"))}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -75,8 +75,10 @@ import {
|
||||
} from "@/core/threads/hooks";
|
||||
import type { AgentThread, AgentThreadState } from "@/core/threads/types";
|
||||
import {
|
||||
isBootstrapThread,
|
||||
isThreadFavorite,
|
||||
isThreadPinned,
|
||||
listPathOfThread,
|
||||
pathOfThread,
|
||||
sortThreadsPinnedFirst,
|
||||
titleOfThread,
|
||||
@@ -213,11 +215,12 @@ export function GeneralQaSidebar() {
|
||||
|
||||
const visibleThreads = useMemo(
|
||||
() =>
|
||||
// 智能体创建(bootstrap)会话也展示(带机器人图标、点击回创建页);
|
||||
// 仅排除调度 / 会商类系统会话。
|
||||
sortThreadsPinnedFirst(
|
||||
threads.filter(
|
||||
(thread) =>
|
||||
thread.metadata?.thread_type !== "scheduler" &&
|
||||
thread.metadata?.thread_type !== "bootstrap" &&
|
||||
thread.metadata?.thread_type !== "roundtable",
|
||||
),
|
||||
),
|
||||
@@ -249,9 +252,9 @@ export function GeneralQaSidebar() {
|
||||
let nextThreadPath = pathOfThread("new", { agent_id: agentIdFromPath });
|
||||
if (threadIndex > -1) {
|
||||
if (threads[threadIndex + 1]) {
|
||||
nextThreadPath = pathOfThread(threads[threadIndex + 1]!);
|
||||
nextThreadPath = listPathOfThread(threads[threadIndex + 1]!);
|
||||
} else if (threads[threadIndex - 1]) {
|
||||
nextThreadPath = pathOfThread(threads[threadIndex - 1]!);
|
||||
nextThreadPath = listPathOfThread(threads[threadIndex - 1]!);
|
||||
}
|
||||
}
|
||||
void router.push(nextThreadPath);
|
||||
@@ -417,7 +420,9 @@ export function GeneralQaSidebar() {
|
||||
</div>
|
||||
) : (
|
||||
filteredThreads.map((thread) => {
|
||||
const isActive = pathOfThread(thread) === pathname;
|
||||
const href = listPathOfThread(thread);
|
||||
const isActive = href === pathname;
|
||||
const isBoot = isBootstrapThread(thread);
|
||||
const pinned = isThreadPinned(thread);
|
||||
const favorited = isThreadFavorite(thread);
|
||||
return (
|
||||
@@ -426,7 +431,7 @@ export function GeneralQaSidebar() {
|
||||
className="group/item relative flex items-center"
|
||||
>
|
||||
<Link
|
||||
href={pathOfThread(thread)}
|
||||
href={href}
|
||||
className={cn(
|
||||
"relative flex h-9 min-w-0 flex-1 items-center gap-2 rounded-lg px-2.5 text-[13px] transition-colors",
|
||||
isActive
|
||||
@@ -434,12 +439,22 @@ export function GeneralQaSidebar() {
|
||||
: "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<MessageSquare
|
||||
className={cn(
|
||||
"size-3.5 shrink-0",
|
||||
isActive ? "text-primary" : "text-muted-foreground/70",
|
||||
)}
|
||||
/>
|
||||
{isBoot ? (
|
||||
<Bot
|
||||
className={cn(
|
||||
"size-3.5 shrink-0",
|
||||
isActive ? "text-primary" : "text-muted-foreground/70",
|
||||
)}
|
||||
aria-label="智能体创建会话"
|
||||
/>
|
||||
) : (
|
||||
<MessageSquare
|
||||
className={cn(
|
||||
"size-3.5 shrink-0",
|
||||
isActive ? "text-primary" : "text-muted-foreground/70",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{pinned && (
|
||||
<Pin className="size-3 shrink-0 text-muted-foreground/70" />
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { type ReactNode, useEffect, useState } from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -26,6 +26,8 @@ interface PublishSquarePickerDialogProps {
|
||||
loading?: boolean;
|
||||
title?: string;
|
||||
description?: string;
|
||||
/** 可选提示节点,渲染在描述下方 —— 用于显示「本次发布将以哪个账号进行」及账号切换告警。 */
|
||||
accountNotice?: ReactNode;
|
||||
}
|
||||
|
||||
/** A "publish to which square (发布广场)?" chooser. Squares are rendered as a
|
||||
@@ -39,6 +41,7 @@ export function PublishSquarePickerDialog({
|
||||
loading,
|
||||
title = "选择发布广场",
|
||||
description = "请选择要把它发布到哪个广场。每个项目只能归属一个广场,不会重复出现。",
|
||||
accountNotice,
|
||||
}: PublishSquarePickerDialogProps) {
|
||||
const [selected, setSelected] = useState<string>(
|
||||
defaultSquareId || squares[0]?.id || "",
|
||||
@@ -58,6 +61,7 @@ export function PublishSquarePickerDialog({
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{accountNotice}
|
||||
<div className="max-h-[50vh] space-y-1.5 overflow-y-auto py-1">
|
||||
{squares.map((sq) => {
|
||||
const active = selected === sq.id;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Bot,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Download,
|
||||
@@ -64,8 +65,10 @@ import {
|
||||
} from "@/core/threads/hooks";
|
||||
import type { AgentThread, AgentThreadState } from "@/core/threads/types";
|
||||
import {
|
||||
isBootstrapThread,
|
||||
isThreadFavorite,
|
||||
isThreadPinned,
|
||||
listPathOfThread,
|
||||
pathOfThread,
|
||||
sortThreadsPinnedFirst,
|
||||
titleOfThread,
|
||||
@@ -130,9 +133,9 @@ export function RecentChatList({
|
||||
});
|
||||
if (threadIndex > -1) {
|
||||
if (threads[threadIndex + 1]) {
|
||||
nextThreadPath = pathOfThread(threads[threadIndex + 1]!);
|
||||
nextThreadPath = listPathOfThread(threads[threadIndex + 1]!);
|
||||
} else if (threads[threadIndex - 1]) {
|
||||
nextThreadPath = pathOfThread(threads[threadIndex - 1]!);
|
||||
nextThreadPath = listPathOfThread(threads[threadIndex - 1]!);
|
||||
}
|
||||
}
|
||||
void router.push(nextThreadPath);
|
||||
@@ -189,11 +192,12 @@ export function RecentChatList({
|
||||
[t],
|
||||
);
|
||||
|
||||
// 智能体创建(bootstrap)会话也展示在常用对话里(带机器人图标、点击回创建页);
|
||||
// 仅排除调度 / 会商类系统会话。
|
||||
const visibleThreads = sortThreadsPinnedFirst(
|
||||
threads.filter(
|
||||
(thread) =>
|
||||
thread.metadata?.thread_type !== "scheduler" &&
|
||||
thread.metadata?.thread_type !== "bootstrap" &&
|
||||
thread.metadata?.thread_type !== "roundtable",
|
||||
),
|
||||
);
|
||||
@@ -239,7 +243,9 @@ export function RecentChatList({
|
||||
<SidebarMenu>
|
||||
<div className="flex w-full flex-col gap-0.5">
|
||||
{shownThreads.map((thread) => {
|
||||
const isActive = pathOfThread(thread) === pathname;
|
||||
const href = listPathOfThread(thread);
|
||||
const isActive = href === pathname;
|
||||
const isBoot = isBootstrapThread(thread);
|
||||
const pinned = isThreadPinned(thread);
|
||||
const favorited = isThreadFavorite(thread);
|
||||
return (
|
||||
@@ -261,17 +267,29 @@ export function RecentChatList({
|
||||
>
|
||||
<Link
|
||||
className="flex w-full items-center gap-1 whitespace-nowrap group-hover/side-menu-item:overflow-hidden"
|
||||
href={pathOfThread(thread)}
|
||||
href={href}
|
||||
>
|
||||
{compact && (
|
||||
<MessageSquare
|
||||
{isBoot ? (
|
||||
<Bot
|
||||
className={
|
||||
"size-3.5 shrink-0 " +
|
||||
(isActive
|
||||
? "text-[var(--primary)]"
|
||||
: "text-muted-foreground/70")
|
||||
}
|
||||
aria-label="智能体创建会话"
|
||||
/>
|
||||
) : (
|
||||
compact && (
|
||||
<MessageSquare
|
||||
className={
|
||||
"size-3.5 shrink-0 " +
|
||||
(isActive
|
||||
? "text-[var(--primary)]"
|
||||
: "text-muted-foreground/70")
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{pinned && (
|
||||
<Pin className="size-3 shrink-0 text-muted-foreground/70" />
|
||||
|
||||
Reference in new issue
Block a user