feat: restore learning feedback and knowledge capabilities

This commit is contained in:
2026-07-18 00:50:11 +08:00
parent 876a23a123
commit 6bdc86e3fc
152 changed files with 30337 additions and 953 deletions
+104 -5
View File
@@ -1,11 +1,13 @@
import type {
AnswerFeedbackVerdict,
KnowledgeSearchResponse,
KnowledgeMediaSelectedFile,
UnifiedKnowledgeResponse,
KnowledgeSnippet,
PositionSopSummary,
SummaryCardResponse
} from '@/types/api';
import { apiRequest } from './api';
import { apiRequest, apiUrl, authHeaders, readTextPayload } from './api';
import { getSelectedPosition } from './auth';
type RawKnowledgeSnippet = KnowledgeSnippet & {
@@ -13,6 +15,38 @@ type RawKnowledgeSnippet = KnowledgeSnippet & {
text?: string;
};
const mediaExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.mp4', '.mov', '.avi', '.mkv', '.webm', '.m4v'];
const videoExtensions = new Set(['.mp4', '.mov', '.avi', '.mkv', '.webm', '.m4v']);
export const chooseKnowledgeMedia = () =>
new Promise<KnowledgeMediaSelectedFile>((resolve, reject) => {
uni.chooseFile({
count: 1,
type: 'all',
extension: mediaExtensions,
success: (result) => {
const tempFiles = Array.isArray(result.tempFiles) ? result.tempFiles : [];
const file = (tempFiles[0] || {}) as { path?: string; name?: string; size?: number };
const path = file.path || result.tempFilePaths?.[0] || '';
const name = file.name || path.split('/').pop() || '现场附件';
const extension = `.${name.split('.').pop()?.toLowerCase() || ''}`;
const kind = videoExtensions.has(extension) ? 'video' : 'image';
const size = Number(file.size || 0);
const maxSize = kind === 'video' ? 100 * 1024 * 1024 : 10 * 1024 * 1024;
if (!path) {
reject(new Error('未获取到附件文件'));
return;
}
if (size > maxSize) {
reject(new Error(kind === 'video' ? '视频不能超过 100MB' : '图片不能超过 10MB'));
return;
}
resolve({ path, name, size, kind });
},
fail: () => reject(new Error('未选择附件'))
});
});
const normalizeSnippet = (snippet: RawKnowledgeSnippet): KnowledgeSnippet => ({
...snippet,
docTitle: snippet.docTitle || snippet.title || '',
@@ -20,8 +54,8 @@ const normalizeSnippet = (snippet: RawKnowledgeSnippet): KnowledgeSnippet => ({
});
export const searchKnowledge = async (queryText: string, position = getSelectedPosition()) => {
const response = await apiRequest<KnowledgeSearchResponse>({
url: '/api/knowledge/search',
const response = await apiRequest<UnifiedKnowledgeResponse>({
url: '/api/knowledge/query',
method: 'POST',
data: {
queryText,
@@ -31,9 +65,74 @@ export const searchKnowledge = async (queryText: string, position = getSelectedP
},
timeout: 30000
});
return normalizeUnifiedResponse(response);
};
export const searchKnowledgeWithMedia = (
file: KnowledgeMediaSelectedFile,
queryText: string,
position = getSelectedPosition(),
onUploadTask?: (task: ReturnType<typeof uni.uploadFile> | null) => void
) => new Promise<KnowledgeSearchResponse>((resolve, reject) => {
const task = uni.uploadFile({
url: apiUrl('/api/knowledge/query-media'),
filePath: file.path,
name: 'file',
header: authHeaders(false),
formData: { queryText, position },
timeout: 360000,
success: (response) => {
try {
const payload = readTextPayload<UnifiedKnowledgeResponse>(response.statusCode, response.data);
resolve(normalizeUnifiedResponse(payload));
} catch (error) {
reject(error);
}
},
fail: () => reject(new Error(file.kind === 'video' ? '视频上传或分析超时' : '图片上传失败')),
complete: () => onUploadTask?.(null)
});
onUploadTask?.(task);
});
export const queryKnowledgeTool = async (
toolCode: 'MY_PRACTICE_SUMMARY' | 'TEAM_PRACTICE_SUMMARY',
queryText: string
) => {
const response = await apiRequest<UnifiedKnowledgeResponse>({
url: '/api/knowledge/query',
method: 'POST',
data: { queryText, source: 'mobile_uni_tool', limit: 5, toolCode },
timeout: 30000
});
return normalizeUnifiedResponse(response);
};
const normalizeUnifiedResponse = (response: UnifiedKnowledgeResponse): KnowledgeSearchResponse => {
const legacy = response.legacy || ({} as KnowledgeSearchResponse);
const citationSnippets = (response.citations || []).map((item) => normalizeSnippet({
docTitle: item.title,
content: item.snippet,
fragmentId: item.fragmentId,
spaceCode: item.spaceCode,
sourceType: item.sourceType
}));
return {
...response,
snippets: (response.snippets || []).map((item) => normalizeSnippet(item as RawKnowledgeSnippet))
...legacy,
requestId: response.requestId,
queryText: response.queryText,
category: legacy.category || 'sop',
answer: response.answer,
reference: legacy.reference || (response.citations || []).map((item) => item.title).join('、'),
docs: legacy.docs || [],
snippets: (legacy.snippets?.length ? legacy.snippets : citationSnippets).map((item) => normalizeSnippet(item as RawKnowledgeSnippet)),
training: legacy.training || [],
records: legacy.records || [],
citations: response.citations || [],
usedSpaceCodes: response.usedSpaceCodes || [],
noEvidence: response.noEvidence,
promptVersion: response.promptVersion,
data: response.data
};
};