feat(Hooks): 添加标签管理 Hooks
- 添加 useTags 全局标签统计 Hook - 添加 useConversationTags 对话标签管理 Hook - 添加 useTagFilter 标签筛选 Hook - 支持标签增删改查、AI 自动生成、筛选等功能
This commit is contained in:
parent
5189ebb232
commit
728c13eb5e
229
src/hooks/useTags.ts
Normal file
229
src/hooks/useTags.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user