fix: 修复日志清理和提供商显示问题 (#51)

问题修复:
1. 清除日志后列表仍有数据 - cleanup_flows 现在同时清理 file_store 和 memory_store
2. 提供商显示不正确 - 从模型名推断实际提供商(如 deepseek 模型显示 DeepSeek 而非 Kiro)

改动说明:
- flow_monitor_cmd.rs: 在 All/ByTime/ByCount 清理类型中添加内存存储清理
- memory_store.rs: 添加 cleanup_before() 方法支持按时间清理内存数据
- FlowList.tsx: 添加 inferProviderFromModel/getDisplayProvider 函数从模型名推断提供商

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
coso
2025-12-25 22:06:05 +08:00
co-authored by Claude Opus 4.5
parent ba0af7ebf0
commit 404287c58d
3 changed files with 148 additions and 8 deletions
@@ -507,6 +507,7 @@ pub async fn cleanup_flows(
// 清理所有数据 - 使用当前时间 + 1天,确保删除所有数据
let before = chrono::Utc::now() + chrono::Duration::days(1);
// 清理文件存储
if let Some(file_store) = monitor.0.file_store() {
match file_store.cleanup(before) {
Ok(result) => {
@@ -520,6 +521,14 @@ pub async fn cleanup_flows(
}
}
}
// 清理内存存储
{
let memory_store = monitor.0.memory_store();
let mut store = memory_store.write().await;
store.clear();
tracing::info!("已清理内存存储");
}
}
CleanupType::ByTime => {
@@ -546,6 +555,15 @@ pub async fn cleanup_flows(
}
}
}
// 清理内存存储
{
let memory_store = monitor.0.memory_store();
let mut store = memory_store.write().await;
let memory_cleaned = store.cleanup_before(before);
cleaned_count += memory_cleaned;
tracing::info!("已清理内存存储 {} 条记录", memory_cleaned);
}
}
CleanupType::ByCount => {
@@ -563,6 +581,15 @@ pub async fn cleanup_flows(
}
}
}
// 清理内存存储 - 对于按数量清理,清空所有内存数据
// 因为文件存储已按保留策略清理,内存应保持一致
{
let memory_store = monitor.0.memory_store();
let mut store = memory_store.write().await;
store.clear();
tracing::info!("已清理内存存储");
}
}
CleanupType::ByStatus => {
@@ -506,6 +506,37 @@ impl FlowMemoryStore {
self.ordered_ids.clear();
}
/// 按时间清理 Flow
///
/// 删除指定时间之前创建的所有 Flow。
///
/// # 参数
/// - `before`: 截止时间,早于此时间的 Flow 将被删除
///
/// # 返回
/// 删除的 Flow 数量
pub fn cleanup_before(&mut self, before: chrono::DateTime<chrono::Utc>) -> usize {
let mut to_remove = Vec::new();
for id in &self.ordered_ids {
if let Some(flow_lock) = self.flows.get(id) {
if let Ok(flow) = flow_lock.read() {
if flow.timestamps.created < before {
to_remove.push(id.clone());
}
}
}
}
let count = to_remove.len();
for id in to_remove {
self.flows.remove(&id);
self.ordered_ids.retain(|i| i != &id);
}
count
}
/// 驱逐最旧的 Flow
fn evict_oldest(&mut self) {
if let Some(oldest_id) = self.ordered_ids.pop_front() {
+90 -8
View File
@@ -327,6 +327,66 @@ export function FlowList({
}
};
// 从模型名推断实际的模型提供商
const inferProviderFromModel = (model: string): string | null => {
const modelLower = model.toLowerCase();
// DeepSeek 模型
if (modelLower.includes("deepseek")) {
return "DeepSeek";
}
// Claude 模型
if (
modelLower.includes("claude") ||
modelLower.includes("anthropic") ||
modelLower.includes("sonnet") ||
modelLower.includes("opus") ||
modelLower.includes("haiku")
) {
return "Claude";
}
// GPT/OpenAI 模型
if (
modelLower.includes("gpt") ||
modelLower.includes("o1") ||
modelLower.includes("o3") ||
modelLower.includes("chatgpt")
) {
return "OpenAI";
}
// Gemini 模型
if (modelLower.includes("gemini")) {
return "Gemini";
}
// Qwen 模型
if (modelLower.includes("qwen") || modelLower.includes("qwq")) {
return "Qwen";
}
// Llama 模型
if (modelLower.includes("llama")) {
return "Llama";
}
// Mistral 模型
if (modelLower.includes("mistral") || modelLower.includes("mixtral")) {
return "Mistral";
}
return null;
};
// 获取显示的提供商名称(优先使用模型推断的提供商)
const getDisplayProvider = (
metadataProvider: string,
model: string,
): string => {
const inferredProvider = inferProviderFromModel(model);
// 如果能从模型名推断出提供商,且与凭证池不同,则显示推断的提供商
if (inferredProvider && inferredProvider !== metadataProvider) {
return inferredProvider;
}
return metadataProvider;
};
const getProviderColor = (provider: string) => {
const colors: Record<string, string> = {
Kiro: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300",
@@ -339,6 +399,12 @@ export function FlowList({
Qwen: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-300",
Antigravity:
"bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300",
DeepSeek:
"bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300",
Llama:
"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300",
Mistral:
"bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-300",
};
return (
colors[provider] ||
@@ -553,6 +619,7 @@ export function FlowList({
onCopyId={(e) => handleCopyId(e, flow.id)}
getStateIcon={getStateIcon}
getProviderColor={getProviderColor}
getDisplayProvider={getDisplayProvider}
formatTime={formatTime}
/>
))}
@@ -605,6 +672,7 @@ interface FlowListItemProps {
onCopyId: (e: React.MouseEvent) => void;
getStateIcon: (state: FlowState) => React.ReactNode;
getProviderColor: (provider: string) => string;
getDisplayProvider: (metadataProvider: string, model: string) => string;
formatTime: (timestamp: string) => string;
}
@@ -619,6 +687,7 @@ function FlowListItem({
onCopyId,
getStateIcon,
getProviderColor,
getDisplayProvider,
formatTime,
}: FlowListItemProps) {
const hasToolCalls =
@@ -669,14 +738,27 @@ function FlowListItem({
</span>
{/* 提供商 */}
<span
className={cn(
"text-xs px-2 py-0.5 rounded-full shrink-0",
getProviderColor(flow.metadata.provider),
)}
>
{flow.metadata.provider}
</span>
{(() => {
const displayProvider = getDisplayProvider(
flow.metadata.provider,
flow.request.model,
);
return (
<span
className={cn(
"text-xs px-2 py-0.5 rounded-full shrink-0",
getProviderColor(displayProvider),
)}
title={
displayProvider !== flow.metadata.provider
? `凭证池: ${flow.metadata.provider}`
: undefined
}
>
{displayProvider}
</span>
);
})()}
{/* 模型 */}
<span className="text-sm font-medium truncate flex-1 min-w-0">