feat: 添加MinIO文件系统支持并优化外部连接器功能

- 添加MinIO用户文件系统配置选项(BEAVER_MINIO_ROOT_USER等)
- 更新外部连接器配置结构,包括BASE_URL和认证令牌设置
- 改进connector provider支持更多类型(official, feishu_bot等)
- 实现Mistral模型推理模式支持reasoning_effort参数
- 增强外部连接器策略配置和运行时配置管理
- 添加connector bridge事件验证和安全保护机制
- 优化任务路由逻辑,区分simple_chat和new_task场景
- 更新初始技能工具提示配置,分离authoring admin功能
This commit is contained in:
2026-06-05 11:46:40 +08:00
parent 236ac19789
commit 2c5205b06e
120 changed files with 8321 additions and 1865 deletions

View File

@ -1,6 +1,7 @@
'use client';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import {
AlertCircle,
BarChart3,
@ -77,13 +78,25 @@ import { containedJsonTextClass, containedLongTextClass } from '@/lib/text-wrapp
const TERMINAL_DRAFT_STATUSES = new Set(['rejected', 'published', 'disabled', 'archived']);
const REJECTABLE_DRAFT_STATUSES = new Set(['draft', 'in_review', 'approved']);
type SkillsTab = 'published' | 'candidates' | 'drafts';
function normalizeSkillsTab(value: string | null | undefined): SkillsTab {
if (value === 'candidates' || value === 'drafts') {
return value;
}
return 'published';
}
export default function SkillsPage() {
const { locale } = useAppI18n();
const pathname = usePathname();
const router = useRouter();
const searchParams = useSearchParams();
const t = (zh: string, en: string) => pickAppText(locale, zh, en);
const [skills, setSkills] = useState<Skill[]>([]);
const [candidates, setCandidates] = useState<SkillLearningCandidate[]>([]);
const [drafts, setDrafts] = useState<SkillDraft[]>([]);
const [activeTab, setActiveTab] = useState<SkillsTab>(() => normalizeSkillsTab(searchParams?.get('tab')));
const [loading, setLoading] = useState(true);
const [actionId, setActionId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
@ -119,6 +132,23 @@ export default function SkillsPage() {
void load();
}, [load]);
useEffect(() => {
setActiveTab(normalizeSkillsTab(searchParams?.get('tab')));
}, [searchParams]);
const changeTab = (value: string) => {
const nextTab = normalizeSkillsTab(value);
setActiveTab(nextTab);
const nextParams = new URLSearchParams(searchParams?.toString());
if (nextTab === 'published') {
nextParams.delete('tab');
} else {
nextParams.set('tab', nextTab);
}
const query = nextParams.toString();
router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false });
};
const runAction = async (id: string, action: () => Promise<unknown>) => {
setActionId(id);
setError(null);
@ -193,18 +223,18 @@ export default function SkillsPage() {
}
return (
<div className="mx-auto max-w-6xl space-y-6 bg-white p-6 text-black [--background:0_0%_100%] [--card:0_0%_100%] [--card-foreground:0_0%_0%] [--foreground:0_0%_0%] [--muted-foreground:0_0%_0%] [--popover:0_0%_100%] [--popover-foreground:0_0%_0%] [--secondary-foreground:0_0%_0%]">
<div className="mx-auto w-full max-w-6xl space-y-6 overflow-x-hidden bg-white px-4 py-6 text-black [--background:0_0%_100%] [--card:0_0%_100%] [--card-foreground:0_0%_0%] [--foreground:0_0%_0%] [--muted-foreground:0_0%_0%] [--popover:0_0%_100%] [--popover-foreground:0_0%_0%] [--secondary-foreground:0_0%_0%] sm:px-6">
<div className="flex flex-wrap items-center justify-between gap-3">
<h1 className="flex items-center gap-2 text-2xl font-bold">
<Puzzle className="w-6 h-6" />
{t('技能', 'Skills')}
</h1>
<div className="flex items-center gap-2">
<Button onClick={() => void load()} variant="outline" size="sm">
<div className="flex flex-wrap items-center gap-2">
<Button onClick={() => void load()} variant="outline" size="sm" className="h-11">
<RefreshCw className="mr-2 h-4 w-4" />
{t('刷新', 'Refresh')}
</Button>
<Button onClick={() => setShowUpload(true)} size="sm">
<Button onClick={() => setShowUpload(true)} size="sm" className="h-11">
<Upload className="mr-2 h-4 w-4" />
{t('上传技能', 'Upload skill')}
</Button>
@ -238,6 +268,7 @@ export default function SkillsPage() {
<Button
variant="ghost"
size="sm"
className="h-11"
onClick={() => {
setSelectedSkillName(null);
setSkillDetail(null);
@ -277,19 +308,20 @@ export default function SkillsPage() {
}
actions={
<div className="flex flex-wrap gap-2">
<Button variant="outline" size="sm" onClick={() => downloadSkill(skillDetail.skill.name).catch((err) => setError(err.message))}>
<Button variant="outline" size="sm" className="h-11" onClick={() => downloadSkill(skillDetail.skill.name).catch((err) => setError(err.message))}>
<Download className="mr-2 h-4 w-4" />
{t('下载', 'Download')}
</Button>
{skillDetail.skill.source === 'workspace' && (
<>
<Button variant="outline" size="sm" onClick={() => onRollbackSkill(skillDetail.skill.name)}>
<Button variant="outline" size="sm" className="h-11" onClick={() => onRollbackSkill(skillDetail.skill.name)}>
<RefreshCw className="mr-2 h-4 w-4" />
{t('回滚', 'Rollback')}
</Button>
<Button
variant="outline"
size="sm"
className="h-11"
disabled={Boolean(actionId)}
onClick={() => void runAction(`disable:${skillDetail.skill.name}`, () => disablePublishedSkill(skillDetail.skill.name, t('人工禁用', 'Manual disable')))}
>
@ -299,7 +331,7 @@ export default function SkillsPage() {
<Button
variant="outline"
size="sm"
className="text-destructive hover:text-destructive"
className="h-11 text-destructive hover:text-destructive"
disabled={Boolean(actionId)}
onClick={() => void runAction(`delete:${skillDetail.skill.name}`, () => deleteSkill(skillDetail.skill.name)).then(() => {
setSelectedSkillName(null);
@ -330,14 +362,14 @@ export default function SkillsPage() {
)}
{!selectedSkillName && (
<Tabs defaultValue="published" className="space-y-4">
<TabsList>
<TabsTrigger value="published">{t('已发布', 'Published')}</TabsTrigger>
<TabsTrigger value="candidates">{t('候选', 'Candidates')}</TabsTrigger>
<TabsTrigger value="drafts">{t('草稿评审', 'Draft review')}</TabsTrigger>
<Tabs value={activeTab} onValueChange={changeTab} className="min-w-0 space-y-4">
<TabsList className="h-auto min-h-11 w-full max-w-full justify-start overflow-x-auto sm:w-auto">
<TabsTrigger value="published" className="h-10">{t('已发布', 'Published')}</TabsTrigger>
<TabsTrigger value="candidates" className="h-10">{t('候选', 'Candidates')}</TabsTrigger>
<TabsTrigger value="drafts" className="h-10">{t('草稿评审', 'Draft review')}</TabsTrigger>
</TabsList>
<TabsContent value="published">
<TabsContent value="published" className="min-w-0">
<PublishedSkillsTable
skills={skills}
onOpen={(name) => void openSkillDetail(name)}
@ -350,7 +382,7 @@ export default function SkillsPage() {
/>
</TabsContent>
<TabsContent value="candidates">
<TabsContent value="candidates" className="min-w-0">
<Card>
<CardHeader>
<CardTitle className="text-base">{t('学习候选', 'Learning candidates')}</CardTitle>
@ -384,7 +416,7 @@ export default function SkillsPage() {
</Card>
</TabsContent>
<TabsContent value="drafts">
<TabsContent value="drafts" className="min-w-0">
<Card>
<CardHeader>
<CardTitle className="text-base">{t('草稿、评审与发布', 'Drafts, review, and publish')}</CardTitle>
@ -473,6 +505,54 @@ function PublishedSkillsTable({
{skills.length === 0 ? (
<EmptyState icon={<Puzzle className="h-8 w-8" />} text={t('暂无技能', 'No skills yet')} />
) : (
<>
<div className="space-y-3 p-3 md:hidden">
{skills.map((skill) => (
<div key={`${skill.source}:${skill.name}:card`} className="min-w-0 rounded-lg border border-border bg-white p-4">
<button
type="button"
className="block min-h-11 w-full text-left"
onClick={() => onOpen(skill.name)}
>
<div className={`text-sm font-semibold ${containedLongTextClass}`}>{skill.name}</div>
<div className={`mt-1 text-sm leading-5 text-muted-foreground ${containedLongTextClass}`}>
{skill.description || '-'}
</div>
</button>
<div className="mt-3 flex flex-wrap gap-2">
<Badge variant={skill.source === 'builtin' ? 'secondary' : 'default'} className="text-xs">
{skill.source === 'builtin' ? t('内置', 'Built in') : t('工作区', 'Workspace')}
</Badge>
<Badge variant={skill.available ? 'default' : 'outline'} className="text-xs">
{skill.available ? t('可用', 'Available') : t('不可用', 'Unavailable')}
</Badge>
</div>
<div className="mt-3 flex flex-wrap gap-2">
<Button variant="outline" size="sm" className="h-11" onClick={() => onDownload(skill.name)}>
<Download className="mr-2 h-4 w-4" />
{t('下载', 'Download')}
</Button>
{skill.source === 'workspace' && (
<>
<Button variant="outline" size="sm" className="h-11" onClick={() => onRollback(skill.name)}>
<RefreshCw className="mr-2 h-4 w-4" />
{t('回滚', 'Rollback')}
</Button>
<Button variant="outline" size="sm" className="h-11" onClick={() => onDisable(skill.name)}>
<ShieldCheck className="mr-2 h-4 w-4" />
{t('禁用', 'Disable')}
</Button>
<Button variant="outline" size="sm" className="h-11 text-destructive hover:text-destructive" onClick={() => onDelete(skill.name)}>
<Trash2 className="mr-2 h-4 w-4" />
{t('删除', 'Delete')}
</Button>
</>
)}
</div>
</div>
))}
</div>
<div className="hidden md:block">
<Table>
<TableHeader>
<TableRow>
@ -490,7 +570,7 @@ function PublishedSkillsTable({
className="cursor-pointer"
onClick={() => onOpen(skill.name)}
>
<TableCell className="font-medium">{skill.name}</TableCell>
<TableCell className={`font-medium ${containedLongTextClass}`}>{skill.name}</TableCell>
<TableCell>
<span className="block max-w-[360px] truncate text-sm text-muted-foreground">
{skill.description}
@ -508,7 +588,14 @@ function PublishedSkillsTable({
</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={(event) => { event.stopPropagation(); onDownload(skill.name); }}>
<Button
variant="ghost"
size="icon"
className="h-11 w-11"
aria-label={t('下载', 'Download')}
title={t('下载', 'Download')}
onClick={(event) => { event.stopPropagation(); onDownload(skill.name); }}
>
<Download className="h-3.5 w-3.5" />
</Button>
{skill.source === 'workspace' && (
@ -516,7 +603,9 @@ function PublishedSkillsTable({
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
className="h-11 w-11"
aria-label={t('回滚', 'Rollback')}
title={t('回滚', 'Rollback')}
onClick={(event) => {
event.stopPropagation();
onRollback(skill.name);
@ -527,7 +616,9 @@ function PublishedSkillsTable({
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
className="h-11 w-11"
aria-label={t('禁用', 'Disable')}
title={t('禁用', 'Disable')}
onClick={(event) => {
event.stopPropagation();
onDisable(skill.name);
@ -538,7 +629,9 @@ function PublishedSkillsTable({
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-destructive hover:text-destructive"
className="h-11 w-11 text-destructive hover:text-destructive"
aria-label={t('删除', 'Delete')}
title={t('删除', 'Delete')}
onClick={(event) => {
event.stopPropagation();
onDelete(skill.name);
@ -554,6 +647,8 @@ function PublishedSkillsTable({
))}
</TableBody>
</Table>
</div>
</>
)}
</CardContent>
</Card>
@ -590,7 +685,7 @@ function CandidateCard({
: null;
return (
<div className="rounded-lg border border-border bg-white p-4">
<div className="min-w-0 max-w-full rounded-lg border border-border bg-white p-4">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="min-w-0 flex-1 space-y-3">
<div className="flex flex-wrap items-center gap-2">
@ -607,7 +702,7 @@ function CandidateCard({
<div>
<h3 className="break-words text-base font-semibold tracking-normal">{title}</h3>
<p className="mt-1 text-sm leading-6 text-muted-foreground">
<p className={`mt-1 text-sm leading-6 text-muted-foreground ${containedLongTextClass}`}>
{candidate.reason || t('没有提供候选理由。', 'No candidate reason was provided.')}
</p>
</div>
@ -656,7 +751,7 @@ function CandidateCard({
)}
<div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
<span className="font-mono">{candidate.candidate_id}</span>
<span className={`font-mono ${containedLongTextClass}`}>{candidate.candidate_id}</span>
{String(evidence.task_id || '') && <span>{t('任务', 'Task')}: {String(evidence.task_id)}</span>}
{String(evidence.skill_version || '') && <span>{t('基线版本', 'Base version')}: {String(evidence.skill_version)}</span>}
{candidate.created_at && <span>{t('创建于', 'Created')}: {formatDateTime(candidate.created_at)}</span>}
@ -666,11 +761,12 @@ function CandidateCard({
</div>
<div className="flex shrink-0 flex-wrap gap-2">
<Button size="sm" variant="outline" disabled={Boolean(actionId)} onClick={onIgnore}>
<Button size="sm" variant="outline" className="h-11" disabled={Boolean(actionId)} onClick={onIgnore}>
{t('忽略', 'Ignore')}
</Button>
<Button
size="sm"
className="h-11"
disabled={Boolean(actionId)}
onClick={() => void onSynthesize()}
>
@ -685,6 +781,7 @@ function CandidateCard({
<Button
size="sm"
variant="outline"
className="h-11"
disabled={Boolean(actionId)}
onClick={() => void onRegenerate()}
>
@ -753,7 +850,7 @@ function DraftCard({
void onPublish(isHighRisk);
};
return (
<div className="rounded-lg border border-border bg-white p-4">
<div className="min-w-0 max-w-full rounded-lg border border-border bg-white p-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
@ -776,9 +873,9 @@ function DraftCard({
</div>
<div className="mt-2">
<div className="text-xs font-medium text-muted-foreground">{t('技能名', 'Skill name')}</div>
<h3 className="break-words text-lg font-semibold tracking-normal">{draft.skill_name}</h3>
<h3 className={`text-lg font-semibold tracking-normal ${containedLongTextClass}`}>{draft.skill_name}</h3>
</div>
<p className="mt-1 text-sm leading-6 text-muted-foreground">
<p className={`mt-1 text-sm leading-6 text-muted-foreground ${containedLongTextClass}`}>
{draft.reason || description || t('没有提供草稿说明。', 'No draft notes were provided.')}
</p>
<div className="mt-3 grid gap-3 md:grid-cols-3">
@ -805,37 +902,37 @@ function DraftCard({
</div>
)}
<div className="mt-3 flex flex-wrap gap-2 text-xs text-muted-foreground">
<span className="font-mono">{draft.skill_name}/{draft.draft_id}</span>
<span className={`font-mono ${containedLongTextClass}`}>{draft.skill_name}/{draft.draft_id}</span>
<span>{t('创建者', 'Author')}: {draft.created_by}</span>
<span>{t('创建于', 'Created')}: {formatDateTime(draft.created_at)}</span>
</div>
</div>
<div className="flex flex-wrap gap-2">
<Button variant="outline" size="sm" disabled={busy || submitBlocked} onClick={() => void onSubmit()}>
<Button variant="outline" size="sm" className="h-11" disabled={busy || submitBlocked} onClick={() => void onSubmit()}>
<Send className="mr-2 h-4 w-4" />
{t('送审', 'Submit')}
</Button>
<Button variant="outline" size="sm" disabled={busy || approveBlocked} onClick={() => void onApprove()}>
<Button variant="outline" size="sm" className="h-11" disabled={busy || approveBlocked} onClick={() => void onApprove()}>
<Check className="mr-2 h-4 w-4" />
{t('批准', 'Approve')}
</Button>
<Button variant="outline" size="sm" disabled={busy || rejectBlocked} onClick={() => void onReject()}>
<Button variant="outline" size="sm" className="h-11" disabled={busy || rejectBlocked} onClick={() => void onReject()}>
<XCircle className="mr-2 h-4 w-4" />
{t('拒绝', 'Reject')}
</Button>
<Button variant="outline" size="sm" disabled={busy || TERMINAL_DRAFT_STATUSES.has(draft.status)} onClick={() => void onRecheckSafety()}>
<Button variant="outline" size="sm" className="h-11" disabled={busy || TERMINAL_DRAFT_STATUSES.has(draft.status)} onClick={() => void onRecheckSafety()}>
<ShieldCheck className="mr-2 h-4 w-4" />
{t('复检', 'Recheck')}
</Button>
<Button size="sm" disabled={busy || publishBlocked} onClick={handlePublish}>
<Button size="sm" className="h-11" disabled={busy || publishBlocked} onClick={handlePublish}>
<Rocket className="mr-2 h-4 w-4" />
{t('发布', 'Publish')}
</Button>
</div>
</div>
<div className="mt-4 grid gap-3 lg:grid-cols-[minmax(0,1fr)_340px]">
<div className="rounded-md border border-border bg-muted/20 p-4">
<div className="mt-4 grid min-w-0 gap-3 lg:grid-cols-[minmax(0,1fr)_340px]">
<div className="min-w-0 max-w-full rounded-md border border-border bg-muted/20 p-3 sm:p-4">
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-2 text-sm font-medium">
<FileText className="h-4 w-4 text-muted-foreground" />
@ -858,7 +955,7 @@ function DraftCard({
)}
</div>
<div className="space-y-3">
<div className="min-w-0 space-y-3">
<GateSummary
title={t('发布门禁', 'Publish gates')}
summary={canPublishLabel}
@ -883,7 +980,7 @@ function DraftCard({
</div>
</div>
<div className="mt-3 grid gap-3 md:grid-cols-2">
<div className="mt-3 grid min-w-0 gap-3 md:grid-cols-2">
<SafetyReportPanel report={safety} />
<EvalReportPanel report={evalReport} />
</div>
@ -905,7 +1002,7 @@ function SafetyReportPanel({ report }: { report?: SkillDraftSafetyReport | null
}
const problems = [...(report.blocked_reasons || []), ...(report.issues || [])];
return (
<div className="rounded-md border border-border bg-muted/20 p-4">
<div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-2 text-sm font-medium">
{report.passed ? <ShieldCheck className="h-4 w-4 text-muted-foreground" /> : <ShieldAlert className="h-4 w-4 text-destructive" />}
@ -922,7 +1019,7 @@ function SafetyReportPanel({ report }: { report?: SkillDraftSafetyReport | null
{problems.map((item, index) => (
<li key={`${item}:${index}`} className="flex gap-2">
<AlertCircle className="mt-1 h-3.5 w-3.5 shrink-0 text-destructive" />
<span>{item}</span>
<span className={containedLongTextClass}>{item}</span>
</li>
))}
</ul>
@ -933,7 +1030,7 @@ function SafetyReportPanel({ report }: { report?: SkillDraftSafetyReport | null
</p>
)}
{report.suggested_fix && (
<p className="mt-3 rounded-md border border-border bg-white p-3 text-sm">
<p className={`mt-3 rounded-md border border-border bg-white p-3 text-sm ${containedLongTextClass}`}>
<span className="font-medium">{t('建议处理', 'Suggested fix')}:</span> {report.suggested_fix}
</p>
)}
@ -957,7 +1054,7 @@ function EvalReportPanel({ report }: { report?: SkillDraftEvalReport | null }) {
}
if (report.status === 'skipped_provider_unavailable') {
return (
<div className="rounded-md border border-border bg-muted/20 p-4">
<div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
<BarChart3 className="h-4 w-4 text-muted-foreground" />
{t('评估报告', 'Eval report')}
@ -970,7 +1067,7 @@ function EvalReportPanel({ report }: { report?: SkillDraftEvalReport | null }) {
);
}
return (
<div className="rounded-md border border-border bg-muted/20 p-4">
<div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-2 text-sm font-medium">
<BarChart3 className="h-4 w-4 text-muted-foreground" />
@ -1002,7 +1099,19 @@ function EvalReportPanel({ report }: { report?: SkillDraftEvalReport | null }) {
<div className="border-b border-border px-3 py-2 text-xs font-medium text-muted-foreground">
{t('回放案例', 'Replay cases')}
</div>
<div className="max-h-48 overflow-auto">
<div className="space-y-2 p-3 md:hidden">
{report.cases.map((item, index) => (
<div key={`${String(item.run_id || index)}:${index}:card`} className="rounded-md border border-border bg-muted/20 p-3 text-xs">
<div className={`font-mono ${containedLongTextClass}`}>{String(item.run_id || '-')}</div>
<div className="mt-2 grid grid-cols-3 gap-2">
<MetricTile label={t('基线', 'Baseline')} value={formatScore(toNumber(item.baseline_score))} />
<MetricTile label={t('候选', 'Candidate')} value={formatScore(toNumber(item.candidate_score))} />
<MetricTile label={t('变化', 'Delta')} value={formatSignedScore(toNumber(item.delta))} />
</div>
</div>
))}
</div>
<div className="hidden max-h-48 overflow-auto md:block">
<table className="w-full text-left text-xs">
<thead className="bg-muted/40 text-muted-foreground">
<tr>
@ -1042,7 +1151,7 @@ function GateSummary({
items: Array<{ label: string; ok: boolean }>;
}) {
return (
<div className="rounded-md border border-border bg-muted/20 p-4">
<div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
<ListChecks className="h-4 w-4 text-muted-foreground" />
{title}
@ -1070,7 +1179,7 @@ function ReadablePanel({
empty: string;
}) {
return (
<div className="rounded-md border border-border bg-muted/20 p-4">
<div className="min-w-0 rounded-md border border-border bg-muted/20 p-4">
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
{icon}
{title}
@ -1090,7 +1199,7 @@ function ReadableFact({
value: string;
}) {
return (
<div className="rounded-md border border-border bg-white p-3">
<div className="min-w-0 rounded-md border border-border bg-white p-3">
<div className="mb-1 flex items-center gap-2 text-xs font-medium text-muted-foreground">
{icon}
{label}
@ -1111,7 +1220,7 @@ function MetricTile({
}) {
const toneClass = tone === 'bad' ? 'text-destructive' : 'text-foreground';
return (
<div className="rounded-md border border-border bg-white p-3">
<div className="min-w-0 rounded-md border border-border bg-white p-3">
<div className="text-xs font-medium text-muted-foreground">{label}</div>
<div className={`mt-1 text-lg font-semibold ${toneClass}`}>{value}</div>
</div>
@ -1121,7 +1230,7 @@ function MetricTile({
function RawDetails({ title, payload }: { title: string; payload: unknown }) {
return (
<details className="mt-3 min-w-0 max-w-full overflow-hidden rounded-md border border-border bg-white">
<summary className="flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 text-xs font-medium text-muted-foreground">
<summary className="flex h-11 cursor-pointer list-none items-center justify-between gap-2 px-3 py-2 text-xs font-medium text-muted-foreground">
{title}
<ChevronDown className="h-3.5 w-3.5" />
</summary>
@ -1134,7 +1243,7 @@ function RawDetails({ title, payload }: { title: string; payload: unknown }) {
function MarkdownPreview({ content }: { content: string }) {
return (
<div className="prose prose-sm max-w-none text-black prose-a:text-black prose-code:rounded prose-code:bg-white prose-code:px-1 prose-code:py-0.5 prose-code:text-black prose-headings:text-black prose-headings:tracking-normal prose-li:text-black prose-p:text-black prose-pre:border prose-pre:border-border prose-pre:bg-white prose-pre:text-black prose-strong:text-black [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
<div className={`prose prose-sm max-w-none text-black prose-a:text-black prose-code:rounded prose-code:bg-white prose-code:px-1 prose-code:py-0.5 prose-code:text-black prose-headings:text-black prose-headings:tracking-normal prose-li:text-black prose-p:text-black prose-pre:border prose-pre:border-border prose-pre:bg-white prose-pre:text-black prose-strong:text-black [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_*]:min-w-0 ${containedLongTextClass}`}>
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
</div>
);