接入体验:
- 客户端选择器覆盖 7 个客户端(Claude Desktop/Code、Cursor、Cline、
Windsurf、Codex、GitHub Copilot),自动生成对应配置
- API Key 一键含 key 复制(密码验证后写入剪贴板)
- 公开 /mcp/:projectId/info + Test connection 按钮
- 项目新增 Usage 标签(每日柱状图 + 工具调用分布)
- 导入完成后引导跳转到 MCP 配置
协议层稳定性:
- session 超时清理(5 分钟扫描 / 30 分钟 idle 关闭)
- 错误响应标准化为 { error: { code, message } }
- 调用日志捕获错误信息 + 4KB payload 截断
- McpCallLog 增加 errorMessage 字段(migration)
抽出可复用模块:PasswordRevealPrompt 组件、BarChart 组件、
trends helper(daysWindowStart + getDailyTrends)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
1.9 KiB
TypeScript
46 lines
1.9 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
|
|
export type BarChartPoint = {
|
|
key: string;
|
|
label: string;
|
|
value: number;
|
|
/** Optional secondary value rendered as an overlay (e.g., errors stacked on calls). */
|
|
secondary?: number;
|
|
tooltip: ReactNode;
|
|
};
|
|
|
|
export default function BarChart({ points, height = 180, emptyLabel }: { points: BarChartPoint[]; height?: number; emptyLabel: string }) {
|
|
if (points.length === 0) {
|
|
return <div style={{ height }} className="flex items-center justify-center text-[13px] text-text-muted">{emptyLabel}</div>;
|
|
}
|
|
|
|
const max = Math.max(...points.map((p) => p.value), 1);
|
|
|
|
return (
|
|
<div className="flex items-end gap-1.5" style={{ height }}>
|
|
{points.map((point) => {
|
|
const barHeight = (point.value / max) * 100;
|
|
const overlayHeight = point.secondary !== undefined && point.value > 0
|
|
? (point.secondary / point.value) * barHeight
|
|
: 0;
|
|
return (
|
|
<div key={point.key} className="flex-1 flex flex-col items-center gap-1 group relative">
|
|
<div className="absolute bottom-full mb-2 hidden group-hover:block z-10">
|
|
<div className="bg-bg-elevated border border-border-default rounded-lg shadow-lg px-3 py-2 text-[11px] whitespace-nowrap">
|
|
{point.tooltip}
|
|
</div>
|
|
</div>
|
|
<div className="w-full flex-1 flex items-end relative">
|
|
<div className="w-full rounded-t-md bg-accent/70 group-hover:bg-accent transition-colors" style={{ height: `${Math.max(barHeight, 2)}%` }} />
|
|
{overlayHeight > 0 && (
|
|
<div className="w-full absolute bottom-0 rounded-t-md bg-danger/70" style={{ height: `${overlayHeight}%` }} />
|
|
)}
|
|
</div>
|
|
<span className="text-[9px] text-text-muted">{point.label}</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|