Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 101 additions & 12 deletions example/parsar/src/Agents.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { tr } from "./lib/i18n";
import { useState } from "react";
import { Plus, Search, Send, Server } from "lucide-react";
import { Blocks, Plug, Plus, Search, Send, Server } from "lucide-react";
import {
useAgents,
useFeishuBots,
useMCPs,
useModels,
useRuntimes,
type AgentProfile,
type FeishuBot,
type RuntimeProfile,
} from "./lib/product";
import { useCapabilities } from "./Capabilities";
import { useSkills } from "./Skills";
import { engineSummary } from "./ExecutionFields";
import { Button } from "./components/ui/button";
import { Input } from "./components/ui/input";
Expand Down Expand Up @@ -42,7 +47,12 @@ export function Agents() {
const agents = useAgents(),
models = useModels(),
runtimes = useRuntimes(),
bots = useFeishuBots();
bots = useFeishuBots(),
skills = useSkills(),
mcps = useMCPs(),
capabilities = useCapabilities();
const names = (rows: { id: string; name: string }[] = [], ids: string[] = []) =>
ids.flatMap((id) => rows.find((row) => row.id === id)?.name || []);
const [search, setSearch] = useState("");
const rows = (agents.data || []).filter((agent) =>
`${agent.name} ${agent.description || ""}`
Expand Down Expand Up @@ -93,8 +103,18 @@ export function Agents() {
key={agent.id}
agent={agent}
engine={engineSummary(agent, models.data)}
runtime={runtimes.data?.find((r) => r.id === agent.runtime_id)?.name}
bot={bots.data?.find((b) => b.agent_id === agent.id)?.name}
runtime={runtimes.data?.find((r) => r.id === agent.runtime_id)}
bot={bots.data?.find((b) => b.agent_id === agent.id)}
skills={names(skills.data, agent.skill_ids)}
capabilities={[
...(agent.web_search && agent.web_search !== "disabled" ? [tr("网页搜索")] : []),
...(agent.ask_user ? [tr("向用户提问")] : []),
...names(mcps.data, agent.mcp_ids),
...names(
capabilities.data?.filter((row) => row.kind !== "prompt"),
agent.capability_ids,
),
]}
/>
))}
</div>
Expand All @@ -104,18 +124,46 @@ export function Agents() {
);
}

// One line of the card: which bot, template, Skills or capabilities the Agent is assembled from.
function Part({
icon: Icon,
label,
children,
}: {
icon: typeof Server;
label: string;
children: React.ReactNode;
}) {
return (
<div className="flex items-start gap-3">
<span className="flex min-h-7 w-24 shrink-0 items-center gap-2 text-sm text-fg">
<Icon strokeWidth={1.7} className="h-4 w-4 shrink-0 text-fg-subtle" />
{label}
</span>
<div className="flex min-h-7 min-w-0 flex-1 flex-wrap items-center gap-1.5">{children}</div>
</div>
);
}

const shownSkills = 8;

function AgentCard({
agent,
engine,
runtime,
bot,
skills,
capabilities,
}: {
agent: AgentProfile;
engine: string;
runtime?: string;
bot?: string;
runtime?: RuntimeProfile;
bot?: FeishuBot;
skills: string[];
capabilities: string[];
}) {
const disabled = agent.enabled === false;
const packages = [...(runtime?.packages?.npm || []), ...(runtime?.packages?.python || [])];
return (
<article className="group relative flex flex-col rounded-2xl border border-line bg-surface p-6 app-shadow-card transition-colors hover:border-line-strong">
<div className="flex items-start gap-4">
Expand All @@ -135,14 +183,55 @@ function AgentCard({
</div>
{disabled && <Chip muted>{tr("已停用")}</Chip>}
</div>
<p className="mt-5 line-clamp-2 min-h-[3.25em] flex-1 text-sm leading-relaxed text-fg-muted">
<p className="mt-5 line-clamp-2 min-h-[3.25em] text-sm leading-relaxed text-fg-muted">
{agent.description || agent.instructions || tr("暂无描述")}
</p>
<div className="mt-5 flex flex-wrap gap-2">
<Chip icon={Server} muted={!runtime}>
{runtime || tr("未选择环境")}
</Chip>
{bot && <Chip icon={Send}>{bot}</Chip>}
<div className="mt-5 flex flex-1 flex-col gap-3">
<Part icon={Send} label={tr("飞书机器人")}>
{bot ? (
<span className="flex min-w-0 items-center gap-2 text-sm font-medium">
<span className="truncate">{bot.name}</span>
{bot.health?.state === "connected" && (
<span className="h-2 w-2 shrink-0 rounded-full bg-success" />
)}
</span>
) : (
<Chip muted>{tr("未绑定")}</Chip>
)}
</Part>
<Part icon={Server} label={tr("环境模板")}>
{runtime ? (
<>
<span className="mr-1 truncate text-sm font-medium">{runtime.name}</span>
{packages.map((name) => (
<Chip key={name}>{name}</Chip>
))}
</>
) : (
<Chip muted>{tr("未选择环境")}</Chip>
)}
</Part>
<Part icon={Blocks} label="Skills">
{skills.length ? (
<>
{skills.slice(0, shownSkills).map((name) => (
<Chip key={name}>{name}</Chip>
))}
{skills.length > shownSkills && (
<Chip muted>+{skills.length - shownSkills}</Chip>
)}
</>
) : (
<Chip muted>{tr("无")}</Chip>
)}
</Part>
<Part icon={Plug} label={tr("能力")}>
{capabilities.length ? (
capabilities.map((name) => <Chip key={name}>{name}</Chip>)
) : (
<Chip muted>{tr("无")}</Chip>
)}
</Part>
</div>
<div className="relative mt-6 flex gap-2 border-t border-line-muted pt-5">
<Button asChild size="sm" disabled={disabled}>
Expand Down
3 changes: 0 additions & 3 deletions example/parsar/src/ExecutionFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@ export function engineSummary(agent: Execution, models: ModelProfile[] = []) {
tr("推理{{value0}}", {
value0: optionValue("reasoning_effort", agent.reasoning_effort),
}),
agent.web_search &&
agent.web_search !== "disabled" &&
optionValue("web_search", agent.web_search),
]
.filter(Boolean)
.join(" · ");
Expand Down
4 changes: 3 additions & 1 deletion example/parsar/src/lib/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -521,5 +521,7 @@
"请先在飞书接入中给这个 Agent 绑定机器人": "Bind a bot to this Agent under Feishu first",
"变量名称不能重复": "Variable names must be unique",
"文件不能为空。": "The file is empty.",
"文件不能超过 50 MiB。": "Files can be at most 50 MiB."
"文件不能超过 50 MiB。": "Files can be at most 50 MiB.",
"向用户提问": "Ask the user",
"无": "None"
}
3 changes: 3 additions & 0 deletions example/parsar/tests/workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,9 @@ test("Agent engine options narrow to the selected Harness and reach new conversa
expect(sent.reasoning).toMatchObject({ effort: "high", summary: "auto" });
await page.getByRole("button", { name: "会话详情" }).click();
await expect(page.getByRole("dialog")).toContainText("网页搜索实时搜索");
await page.goto("/#/agents");
for (const part of ["未绑定", "开发沙箱", "code-review", "网页搜索", "文档服务"])
await expect(page.getByRole("article")).toContainText(part);
});

test("chat previews authorization QR and artifact images, downloads files and keeps unsafe images out", async ({ page }) => {
Expand Down
Loading