mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-05 07:36:53 +08:00
fix(PictureService, SearchDialog): enhance vector search with error handling and tab management #9
This commit is contained in:
@@ -50,18 +50,31 @@ public class PictureService(
|
|||||||
// 决定是使用向量搜索还是普通搜索
|
// 决定是使用向量搜索还是普通搜索
|
||||||
if (useVectorSearch && !string.IsNullOrWhiteSpace(searchQuery))
|
if (useVectorSearch && !string.IsNullOrWhiteSpace(searchQuery))
|
||||||
{
|
{
|
||||||
return await PerformVectorSearchAsync(
|
try
|
||||||
dbContext, page, pageSize, searchQuery, tags,
|
{
|
||||||
startDate, endDate, userId, onlyWithGps, similarityThreshold,
|
return await PerformVectorSearchAsync(
|
||||||
excludeAlbumId, albumId, onlyFavorites, ownerId, includeAllPublic);
|
dbContext, page, pageSize, searchQuery, tags,
|
||||||
}
|
startDate, endDate, userId, onlyWithGps, similarityThreshold,
|
||||||
else
|
excludeAlbumId, albumId, onlyFavorites, ownerId, includeAllPublic);
|
||||||
{
|
}
|
||||||
return await PerformStandardSearchAsync(
|
catch (Exception ex)
|
||||||
dbContext, page, pageSize, searchQuery, tags,
|
{
|
||||||
startDate, endDate, userId, sortBy, onlyWithGps,
|
// 如果向量搜索失败,记录错误并回退到标准搜索
|
||||||
excludeAlbumId, albumId, onlyFavorites, ownerId, includeAllPublic);
|
Console.WriteLine($"向量搜索失败,回退到标准搜索: {ex.Message}");
|
||||||
|
|
||||||
|
// 如果是明确的配置错误,则向上抛出异常
|
||||||
|
if (ex.Message.Contains("请检查嵌入模型配置"))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 执行标准搜索(作为默认方法或向量搜索的回退选项)
|
||||||
|
return await PerformStandardSearchAsync(
|
||||||
|
dbContext, page, pageSize, searchQuery, tags,
|
||||||
|
startDate, endDate, userId, sortBy, onlyWithGps,
|
||||||
|
excludeAlbumId, albumId, onlyFavorites, ownerId, includeAllPublic);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行向量搜索
|
// 执行向量搜索
|
||||||
@@ -82,55 +95,78 @@ public class PictureService(
|
|||||||
int? ownerId,
|
int? ownerId,
|
||||||
bool includeAllPublic)
|
bool includeAllPublic)
|
||||||
{
|
{
|
||||||
var queryEmbedding = await embeddingService.GetEmbeddingAsync(searchQuery);
|
try
|
||||||
var queryVector = new Vector(queryEmbedding);
|
{
|
||||||
|
float[]? queryEmbedding = null;
|
||||||
// 构建基础查询
|
try
|
||||||
var query = dbContext.Pictures
|
|
||||||
.Include(p => p.Tags)
|
|
||||||
.Include(p => p.User)
|
|
||||||
.Where(p => p.Embedding != null);
|
|
||||||
|
|
||||||
// 应用共通的查询条件
|
|
||||||
query = ApplyCommonFilters(query, tags, startDate, endDate, userId, onlyWithGps,
|
|
||||||
excludeAlbumId, albumId, onlyFavorites, ownerId, includeAllPublic);
|
|
||||||
|
|
||||||
// 执行向量搜索
|
|
||||||
var allResults = await query
|
|
||||||
.Select(p => new
|
|
||||||
{
|
{
|
||||||
Picture = p,
|
queryEmbedding = await embeddingService.GetEmbeddingAsync(searchQuery);
|
||||||
Similarity = 1.0 - p.Embedding!.CosineDistance(queryVector)
|
|
||||||
})
|
// 检查嵌入向量是否有效
|
||||||
.Where(p => p.Similarity >= similarityThreshold)
|
if (queryEmbedding == null || queryEmbedding.Length == 0)
|
||||||
.OrderByDescending(p => p.Similarity)
|
{
|
||||||
.ToListAsync();
|
throw new InvalidOperationException("嵌入模型返回了空向量");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"向量搜索失败,请检查嵌入模型配置: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryVector = new Vector(queryEmbedding);
|
||||||
|
|
||||||
// 计算总数并分页
|
// 构建基础查询
|
||||||
var totalCount = allResults.Count;
|
var query = dbContext.Pictures
|
||||||
|
.Include(p => p.Tags)
|
||||||
|
.Include(p => p.User)
|
||||||
|
.Where(p => p.Embedding != null);
|
||||||
|
|
||||||
var paginatedResults = allResults
|
// 应用共通的查询条件
|
||||||
.Skip((page - 1) * pageSize)
|
query = ApplyCommonFilters(query, tags, startDate, endDate, userId, onlyWithGps,
|
||||||
.Take(pageSize)
|
excludeAlbumId, albumId, onlyFavorites, ownerId, includeAllPublic);
|
||||||
.Select(r => MapPictureToResponse(r.Picture))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// 处理收藏信息
|
// 执行向量搜索
|
||||||
await PopulateFavoriteInfo(dbContext, paginatedResults, userId);
|
var allResults = await query
|
||||||
|
.Select(p => new
|
||||||
|
{
|
||||||
|
Picture = p,
|
||||||
|
Similarity = 1.0 - p.Embedding!.CosineDistance(queryVector)
|
||||||
|
})
|
||||||
|
.Where(p => p.Similarity >= similarityThreshold)
|
||||||
|
.OrderByDescending(p => p.Similarity)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
// 为当前用户的图片添加相册信息
|
// 计算总数并分页
|
||||||
if (userId.HasValue)
|
var totalCount = allResults.Count;
|
||||||
{
|
|
||||||
await PopulateAlbumInfo(dbContext, paginatedResults, userId.Value);
|
var paginatedResults = allResults
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.Select(r => MapPictureToResponse(r.Picture))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 处理收藏信息
|
||||||
|
await PopulateFavoriteInfo(dbContext, paginatedResults, userId);
|
||||||
|
|
||||||
|
// 为当前用户的图片添加相册信息
|
||||||
|
if (userId.HasValue)
|
||||||
|
{
|
||||||
|
await PopulateAlbumInfo(dbContext, paginatedResults, userId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PaginatedResult<PictureResponse>
|
||||||
|
{
|
||||||
|
Data = paginatedResults,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize,
|
||||||
|
TotalCount = totalCount
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
return new PaginatedResult<PictureResponse>
|
|
||||||
{
|
{
|
||||||
Data = paginatedResults,
|
Console.WriteLine($"向量搜索失败: {ex.Message}");
|
||||||
Page = page,
|
throw new InvalidOperationException($"向量搜索失败: {ex.Message}", ex);
|
||||||
PageSize = pageSize,
|
}
|
||||||
TotalCount = totalCount
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行标准搜索
|
// 执行标准搜索
|
||||||
|
|||||||
@@ -126,6 +126,17 @@ const SearchDialog: React.FC<SearchDialogProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理标签页切换,同时更新 useVectorSearch 状态
|
||||||
|
const handleTabChange = (key: string) => {
|
||||||
|
setActiveTabKey(key);
|
||||||
|
// 根据标签页自动设置向量搜索状态
|
||||||
|
if (key === 'vector') {
|
||||||
|
setUseVectorSearch(true);
|
||||||
|
} else {
|
||||||
|
setUseVectorSearch(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
@@ -143,7 +154,7 @@ const SearchDialog: React.FC<SearchDialogProps> = ({
|
|||||||
>
|
>
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeTabKey}
|
activeKey={activeTabKey}
|
||||||
onChange={setActiveTabKey}
|
onChange={handleTabChange}
|
||||||
className="search-tabs"
|
className="search-tabs"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
@@ -163,9 +174,6 @@ const SearchDialog: React.FC<SearchDialogProps> = ({
|
|||||||
autoFocus={activeTabKey === 'text'}
|
autoFocus={activeTabKey === 'text'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider orientation="left" plain>筛选选项</Divider>
|
|
||||||
|
|
||||||
<div className="search-option-group">
|
<div className="search-option-group">
|
||||||
<Text strong className="option-label">标签筛选:</Text>
|
<Text strong className="option-label">标签筛选:</Text>
|
||||||
<Select
|
<Select
|
||||||
@@ -212,9 +220,6 @@ const SearchDialog: React.FC<SearchDialogProps> = ({
|
|||||||
autoFocus={activeTabKey === 'vector'}
|
autoFocus={activeTabKey === 'vector'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider orientation="left" plain>高级选项</Divider>
|
|
||||||
|
|
||||||
<div className="search-option-group vector-options">
|
<div className="search-option-group vector-options">
|
||||||
<div className="vector-switch-container">
|
<div className="vector-switch-container">
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
Reference in New Issue
Block a user