feat(Hooks): 添加标签管理 Hooks

- 添加 useTags 全局标签统计 Hook
- 添加 useConversationTags 对话标签管理 Hook
- 添加 useTagFilter 标签筛选 Hook
- 支持标签增删改查、AI 自动生成、筛选等功能
This commit is contained in:
gaoziman 2025-12-28 17:28:09 +08:00
parent 5189ebb232
commit 728c13eb5e

229
src/hooks/useTags.ts Normal file
View File

@ -0,0 +1,229 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
import type { TagStats, SuggestedTag, AutoTagResponse, AllTagsResponse } from '@/types/tags';
/**
* Hook -
*/
export function useTags() {
const [tags, setTags] = useState<TagStats[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchTags = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await fetch('/api/tags');
if (!response.ok) {
throw new Error('获取标签失败');
}
const data: AllTagsResponse = await response.json();
setTags(data.tags);
} catch (err) {
setError(err instanceof Error ? err.message : '未知错误');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchTags();
}, [fetchTags]);
return {
tags,
loading,
error,
refetch: fetchTags,
};
}
/**
* Hook -
*/
export function useConversationTags(conversationId: string | null) {
const [tags, setTags] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [updating, setUpdating] = useState(false);
const [generating, setGenerating] = useState(false);
const [suggestedTags, setSuggestedTags] = useState<SuggestedTag[]>([]);
const [error, setError] = useState<string | null>(null);
// 获取对话标签
const fetchTags = useCallback(async () => {
if (!conversationId) {
setTags([]);
return;
}
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/conversations/${conversationId}/tags`);
if (!response.ok) {
throw new Error('获取标签失败');
}
const data = await response.json();
setTags(data.tags || []);
} catch (err) {
setError(err instanceof Error ? err.message : '未知错误');
} finally {
setLoading(false);
}
}, [conversationId]);
// 更新标签
const updateTags = useCallback(async (newTags: string[]) => {
if (!conversationId) return;
try {
setUpdating(true);
setError(null);
const response = await fetch(`/api/conversations/${conversationId}/tags`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tags: newTags }),
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || '更新标签失败');
}
const data = await response.json();
setTags(data.tags || []);
return data.tags;
} catch (err) {
setError(err instanceof Error ? err.message : '未知错误');
throw err;
} finally {
setUpdating(false);
}
}, [conversationId]);
// 添加单个标签
const addTag = useCallback(async (tag: string) => {
const trimmedTag = tag.trim();
if (!trimmedTag || tags.includes(trimmedTag)) return;
const newTags = [...tags, trimmedTag];
return updateTags(newTags);
}, [tags, updateTags]);
// 移除单个标签
const removeTag = useCallback(async (tag: string) => {
const newTags = tags.filter((t) => t !== tag);
return updateTags(newTags);
}, [tags, updateTags]);
// AI 自动生成标签
const generateTags = useCallback(async () => {
if (!conversationId) return;
try {
setGenerating(true);
setError(null);
setSuggestedTags([]);
const response = await fetch(`/api/conversations/${conversationId}/tags/auto`, {
method: 'POST',
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || '生成标签失败');
}
const data: AutoTagResponse = await response.json();
setSuggestedTags(data.suggestedTags);
return data.suggestedTags;
} catch (err) {
setError(err instanceof Error ? err.message : '未知错误');
throw err;
} finally {
setGenerating(false);
}
}, [conversationId]);
// 应用选中的推荐标签
const applySuggestedTags = useCallback(async (selectedTags: string[]) => {
// 合并现有标签和选中的推荐标签,去重
const mergedTags = [...new Set([...tags, ...selectedTags])];
const result = await updateTags(mergedTags);
setSuggestedTags([]); // 清空推荐列表
return result;
}, [tags, updateTags]);
// 清空推荐标签
const clearSuggestedTags = useCallback(() => {
setSuggestedTags([]);
}, []);
useEffect(() => {
fetchTags();
}, [fetchTags]);
return {
tags,
loading,
updating,
generating,
suggestedTags,
error,
refetch: fetchTags,
updateTags,
addTag,
removeTag,
generateTags,
applySuggestedTags,
clearSuggestedTags,
};
}
/**
* Hook -
*/
export function useTagFilter() {
const [selectedTags, setSelectedTags] = useState<string[]>([]);
// 切换标签选中状态
const toggleTag = useCallback((tag: string) => {
setSelectedTags((prev) => {
if (prev.includes(tag)) {
return prev.filter((t) => t !== tag);
}
return [...prev, tag];
});
}, []);
// 选中单个标签(单选模式)
const selectTag = useCallback((tag: string) => {
setSelectedTags((prev) => {
if (prev.includes(tag) && prev.length === 1) {
return []; // 如果已选中且只有一个,则取消选中
}
return [tag];
});
}, []);
// 清除所有筛选
const clearFilter = useCallback(() => {
setSelectedTags([]);
}, []);
// 检查对话是否匹配筛选条件
const matchesFilter = useCallback((conversationTags: string[] | null) => {
if (selectedTags.length === 0) return true;
if (!conversationTags || conversationTags.length === 0) return false;
// 对话必须包含所有选中的标签
return selectedTags.every((tag) => conversationTags.includes(tag));
}, [selectedTags]);
return {
selectedTags,
isFiltering: selectedTags.length > 0,
toggleTag,
selectTag,
clearFilter,
matchesFilter,
};
}