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
+56
View File
@@ -83,6 +83,30 @@ export type PracticeAssignmentRequest = {
reason: string;
};
export type PracticeDifficulty = {
scenarioId: string;
scenarioName: string;
cause: string;
count: number;
affectedPeople: number;
averageScore: number;
latestTime: string;
position?: string;
assignmentStatus?: string;
attempts?: number;
mistakeCount?: number;
difficultyScore?: number;
};
export type PracticeAssignmentBatchResponse = {
requestId: string;
total: number;
created: number;
duplicate: number;
failed: number;
results: Array<{ extPartyId: string; traineeName: string; status: 'CREATED' | 'DUPLICATE' | 'FAILED'; message: string }>;
};
export type PromotionEvidence = {
extPartyId: string;
currentLevel: string;
@@ -244,6 +268,38 @@ export function createPracticeAssignment(data: PracticeAssignmentRequest): Promi
});
}
export function listPracticeDifficulties(params?: { windowDays?: number; position?: string }): Promise<ApiResult<PracticeDifficulty[]>> {
type RawDifficulty = Omit<PracticeDifficulty, 'cause' | 'count'> & { attempts: number; mistakeCount: number };
return request<unknown, ApiResult<RawDifficulty[]>>({
url: '/api/aihr/mobile/practice/difficulties',
method: 'get',
params
}).then((result) => ({
...result,
data: (result.data || []).map((item) => ({
...item,
cause: item.mistakeCount >= Math.max(2, Math.ceil(item.attempts / 2)) ? '高频错题或低分' : '近期低分记录',
count: item.mistakeCount
}))
}));
}
export function assignPracticeDifficulty(data: {
scenarioId: string;
cause: string;
dueDate: string;
requestId: string;
windowDays: number;
position?: string;
}): Promise<ApiResult<PracticeAssignmentBatchResponse>> {
return request({
url: '/api/aihr/mobile/practice/difficulties/assign',
method: 'post',
data,
headers: { repeatSubmit: false }
});
}
export function syncOrgSnapshot(data: OrgSyncRequest): Promise<ApiResult<OrgSyncResponse>> {
return request({
url: '/api/aihr/org/sync',
+92
View File
@@ -0,0 +1,92 @@
import request from '@/utils/request';
type ApiResult<T> = { code: number; msg: string; data: T };
export type AdminQuestionAnswer = {
id: number;
questionId: number;
content: string;
authorName: string;
authorPosition?: string;
status: string;
best: boolean;
recommended?: boolean;
createTime: string;
};
export type AdminQuestion = {
id: number;
type: 'HELP' | 'BOUNTY' | string;
title: string;
content: string;
category: string;
authorName: string;
status: string;
rewardPoints: number;
rewardAccountType: 'POINTS' | 'STUDY_CREDIT' | string;
answerCount: number;
deadline?: string | null;
createTime: string;
bestAnswerId?: number | null;
answers?: AdminQuestionAnswer[];
};
export type IncentiveRule = {
id?: number;
ruleCode: string;
displayName: string;
accountType: 'POINTS' | 'STUDY_CREDIT';
value: number;
dailyCap?: number | null;
enabled: boolean;
};
type RawAdminQuestionAnswer = Omit<AdminQuestionAnswer, 'createTime'> & { createdAt: string };
type RawAdminQuestion = Omit<AdminQuestion, 'createTime' | 'answers'> & {
createdAt: string;
answers?: RawAdminQuestionAnswer[];
};
const normalizeAnswer = (item: RawAdminQuestionAnswer): AdminQuestionAnswer => ({
...item,
createTime: item.createdAt
});
const normalizeQuestion = (item: RawAdminQuestion): AdminQuestion => ({
...item,
createTime: item.createdAt,
answers: (item.answers || []).map(normalizeAnswer)
});
export const listAdminQuestions = (status?: string): Promise<ApiResult<AdminQuestion[]>> =>
request<unknown, ApiResult<RawAdminQuestion[]>>({
url: '/api/aihr/question-admin/questions', method: 'get', params: { status }
}).then((result) => ({ ...result, data: (result.data || []).map(normalizeQuestion) }));
export const createBountyQuestion = (data: { title: string; content: string; category: string; rewardPoints: number; deadline: string }): Promise<ApiResult<AdminQuestion>> =>
request<unknown, ApiResult<RawAdminQuestion>>({
url: '/api/aihr/question-admin/questions', method: 'post', data: { ...data, ruleCode: 'BEST_ANSWER' }, headers: { repeatSubmit: false }
}).then((result) => ({ ...result, data: normalizeQuestion(result.data) }));
export const reviewQuestion = (id: number, decision: 'APPROVE' | 'REJECT', note = ''): Promise<ApiResult<AdminQuestion>> =>
request<unknown, ApiResult<RawAdminQuestion>>({
url: `/api/aihr/question-admin/questions/${id}/review`, method: 'post', data: { decision, note }, headers: { repeatSubmit: false }
}).then((result) => ({ ...result, data: normalizeQuestion(result.data) }));
export const reviewQuestionAnswer = (id: number, decision: 'APPROVE' | 'REJECT', note = ''): Promise<ApiResult<AdminQuestionAnswer>> =>
request<unknown, ApiResult<RawAdminQuestionAnswer>>({
url: `/api/aihr/question-admin/answers/${id}/review`, method: 'post', data: { decision, note }, headers: { repeatSubmit: false }
}).then((result) => ({ ...result, data: normalizeAnswer(result.data) }));
export const selectBestAnswer = (questionId: number, answerId: number): Promise<ApiResult<unknown>> => request({
url: `/api/aihr/question-admin/questions/${questionId}/best-answer`, method: 'post', data: { answerId }, headers: { repeatSubmit: false }
});
export const listIncentiveRules = (): Promise<ApiResult<IncentiveRule[]>> => request({
url: '/api/aihr/incentive-admin/rules', method: 'get'
});
export const saveIncentiveRule = (data: IncentiveRule): Promise<ApiResult<IncentiveRule>> => request({
url: '/api/aihr/incentive-admin/rules',
method: data.id ? 'put' : 'post', data, headers: { repeatSubmit: false }
});
+66 -2
View File
@@ -16,6 +16,9 @@ export type SopDoc = {
export type SopSnippet = {
title: string;
text: string;
fragmentId?: number | null;
spaceCode?: string | null;
sourceType?: string;
};
export type SopRecord = {
@@ -29,6 +32,7 @@ export type SopRecord = {
};
export type SopSearchResponse = {
requestId?: string;
queryText: string;
category: string;
answer: string;
@@ -41,6 +45,31 @@ export type SopSearchResponse = {
cautions?: string[];
promptVersion?: string | null;
reviewId?: number;
citations?: SopCitation[];
usedSpaceCodes?: string[];
noEvidence?: boolean;
data?: unknown;
};
export type SopCitation = {
spaceCode?: string | null;
sourceType: string;
docId?: string | null;
title: string;
snippet: string;
fragmentId?: number | null;
};
type UnifiedQueryResponse = {
requestId: string;
queryText: string;
answer: string;
citations: SopCitation[];
usedSpaceCodes: string[];
noEvidence: boolean;
promptVersion?: string | null;
legacy?: SopSearchResponse | null;
data?: unknown;
};
export type SopReviewResponse = {
@@ -92,12 +121,46 @@ export function searchKnowledgeSop(data: {
position?: string;
source?: string;
limit: number;
spaceCodes?: string[];
toolCode?: 'MY_PRACTICE_SUMMARY' | 'TEAM_PRACTICE_SUMMARY';
}): Promise<ApiResult<SopSearchResponse>> {
return request({
url: '/api/knowledge/search',
return request<unknown, ApiResult<UnifiedQueryResponse>>({
url: '/api/knowledge/query',
method: 'post',
data,
headers: { repeatSubmit: false }
}).then((result) => {
const response = result.data;
const legacy = response.legacy || ({} as SopSearchResponse);
const snippets = legacy.snippets?.length
? legacy.snippets
: (response.citations || []).map((item) => ({
title: item.title,
text: item.snippet,
fragmentId: item.fragmentId,
spaceCode: item.spaceCode,
sourceType: item.sourceType
}));
return {
...result,
data: {
...legacy,
requestId: response.requestId,
queryText: response.queryText,
category: legacy.category || data.category || 'sop',
answer: response.answer,
reference: legacy.reference || response.citations.map((item) => item.title).join('、'),
docs: legacy.docs || [],
snippets,
training: legacy.training || [],
records: legacy.records || [],
citations: response.citations || [],
usedSpaceCodes: response.usedSpaceCodes || [],
noEvidence: response.noEvidence,
promptVersion: response.promptVersion,
data: response.data
}
};
});
}
@@ -149,6 +212,7 @@ export type UploadItem = {
batchId: string;
fileName: string;
category: string;
spaceCodes: string[];
status: number;
statusLabel: string;
error: string | null;
+107
View File
@@ -0,0 +1,107 @@
import request from '@/utils/request';
type ApiResult<T> = { code: number; msg: string; data: T };
export type KnowledgeSpace = {
id: number;
code: string;
name: string;
description: string;
spaceType: 'PUBLIC' | 'BUSINESS' | 'MANAGEMENT' | 'EXTERNAL';
sensitivityLevel: 'PUBLIC' | 'INTERNAL' | 'CONFIDENTIAL';
status: 'DRAFT' | 'ACTIVE' | 'DISABLED';
documentCount: number;
fragmentCount: number;
};
export type SpaceForm = Pick<KnowledgeSpace, 'code' | 'name' | 'description' | 'spaceType' | 'sensitivityLevel' | 'status'>;
export type SpaceGrant = {
principalType: 'ROLE' | 'USER';
principalValue: string;
permission: 'READ' | 'MANAGE';
status: 'ACTIVE' | 'DISABLED';
};
export type KnowledgeDocument = {
attachId: number;
docId: string;
name: string;
type: string;
status: string;
ossId?: number | null;
fragmentCount: number;
updateTime?: string | null;
};
export type UnbindDocumentResult = {
attachId: number;
spaceCode: string;
ossId?: number | null;
ossDeleted: boolean;
};
export type KnowledgeApp = {
id: number;
appCode: string;
appName: string;
authType: 'SESSION' | 'API_TOKEN';
internalClientKey?: string | null;
status: 'ACTIVE' | 'DISABLED';
rateLimitPerMinute: number;
expiresTime?: string | null;
lastUsedTime?: string | null;
spaceCodes: string[];
};
export type AppForm = {
appCode: string;
appName: string;
authType: 'SESSION' | 'API_TOKEN';
internalClientKey?: string | null;
status: 'ACTIVE' | 'DISABLED';
rateLimitPerMinute: number;
expiresTime?: string | null;
spaceCodes: string[];
};
export type IssuedToken = { appCode: string; token: string; expiresTime?: string | null };
export type CreatedApp = { app: KnowledgeApp; token?: string | null };
export const listKnowledgeSpaces = (): Promise<ApiResult<KnowledgeSpace[]>> =>
request({ url: '/api/knowledge/admin/spaces', method: 'get' });
export const createKnowledgeSpace = (data: SpaceForm): Promise<ApiResult<KnowledgeSpace>> =>
request({ url: '/api/knowledge/admin/spaces', method: 'post', data });
export const updateKnowledgeSpace = (id: number, data: SpaceForm): Promise<ApiResult<KnowledgeSpace>> =>
request({ url: `/api/knowledge/admin/spaces/${id}`, method: 'put', data });
export const listSpaceDocuments = (id: number): Promise<ApiResult<KnowledgeDocument[]>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/documents`, method: 'get' });
export const unbindSpaceDocument = (id: number, attachId: number): Promise<ApiResult<UnbindDocumentResult>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/documents/${attachId}`, method: 'delete' });
export const listSpaceGrants = (id: number): Promise<ApiResult<SpaceGrant[]>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/grants`, method: 'get' });
export const replaceSpaceGrants = (id: number, grants: SpaceGrant[]): Promise<ApiResult<SpaceGrant[]>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/grants`, method: 'put', data: { grants } });
export const listKnowledgeApps = (): Promise<ApiResult<KnowledgeApp[]>> =>
request({ url: '/api/knowledge/admin/apps', method: 'get' });
export const createKnowledgeApp = (data: AppForm): Promise<ApiResult<CreatedApp>> =>
request({ url: '/api/knowledge/admin/apps', method: 'post', data });
export const updateKnowledgeApp = (
id: number,
data: Pick<AppForm, 'appName' | 'status' | 'rateLimitPerMinute' | 'expiresTime'>
): Promise<ApiResult<KnowledgeApp>> => request({ url: `/api/knowledge/admin/apps/${id}`, method: 'put', data });
export const replaceKnowledgeAppSpaces = (id: number, spaceCodes: string[]): Promise<ApiResult<KnowledgeApp>> =>
request({ url: `/api/knowledge/admin/apps/${id}/spaces`, method: 'put', data: { spaceCodes } });
export const rotateKnowledgeAppToken = (id: number, expiresTime?: string | null): Promise<ApiResult<IssuedToken>> =>
request({ url: `/api/knowledge/admin/apps/${id}/rotate-token`, method: 'post', data: { expiresTime } });
+47
View File
@@ -0,0 +1,47 @@
import request from '@/utils/request';
type ApiResult<T> = { code: number; msg: string; data: T };
export interface WebSearchProvider {
id: number;
name: string;
providerCode: 'tavily' | string;
endpoint: string;
configured: boolean;
enabled: boolean;
lastTestOk: boolean;
lastTestTime?: string | null;
}
export interface WebSearchProviderForm {
id?: number;
name: string;
providerCode: 'tavily';
endpoint: string;
apiKey: string;
}
export const listWebSearchProviders = (): Promise<ApiResult<WebSearchProvider[]>> => request({
url: '/api/aihr/web-search/providers',
method: 'get'
});
export const saveWebSearchProvider = (data: WebSearchProviderForm): Promise<ApiResult<WebSearchProvider>> => request({
url: '/api/aihr/web-search/providers',
method: data.id ? 'put' : 'post',
data,
headers: { repeatSubmit: false }
});
export const toggleWebSearchProvider = (id: number, enabled: boolean): Promise<ApiResult<WebSearchProvider>> => request({
url: `/api/aihr/web-search/providers/${id}/enabled`,
method: 'patch',
data: { enabled },
headers: { repeatSubmit: false }
});
export const testWebSearchProvider = (id: number): Promise<ApiResult<{ ok: boolean; sourceCount: number; message: string }>> => request({
url: `/api/aihr/web-search/providers/${id}/test`,
method: 'post',
headers: { repeatSubmit: false }
});