# Conflicts: # backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java # backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrModelSeedService.java # backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java # backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrModelSeedServiceTest.java # backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java # docs/API_INTEGRATION.md # docs/个人AI助理阶段二专项TechSpec.md # docs/个人AI助理阶段二开发推进计划.md # frontend/src/views/knowledge/processing.vue # mobile-uni/src/pages/user/sop/index.vue # mobile-uni/src/services/api.ts # mobile-uni/src/services/personal-assistant.ts # mobile-uni/tests/personal-assistant.test.mjs # scripts/demo-check.sh # scripts/personal-assistant-smoke.sh
171 lines
9.2 KiB
TypeScript
171 lines
9.2 KiB
TypeScript
import type {
|
|
AssistantCapture,
|
|
MemoryCandidate,
|
|
MemoryConfirmRequest,
|
|
MemoryConfirmResult,
|
|
PersonalAskResponse,
|
|
PersonalExportOutline,
|
|
PersonalExportOutlineSection,
|
|
PersonalItem,
|
|
PersonalItemCreated,
|
|
PersonalItemSourceType,
|
|
PersonalKnowledgeScope,
|
|
PersonalPage,
|
|
PersonalPublishRequest,
|
|
PersonalSession,
|
|
PersonalSessionDetail,
|
|
PersonalSpace,
|
|
ServiceMemory
|
|
} from '@/types/api';
|
|
import type { CaptureStatusHistory } from '@/types/api';
|
|
import { apiRequest, apiUrl, authHeaders, readTextPayload } from './api';
|
|
import { personalItemApiPath, personalSessionApiPath } from './personal-assistant-id';
|
|
|
|
const candidateBase = '/api/aihr/personal-assistant/memory-candidates';
|
|
|
|
type RawServiceMemory = Omit<ServiceMemory, 'version'> & { version?: number; currentVersion?: number };
|
|
|
|
const rowsOf = <T>(value: T[] | { rows?: T[]; records?: T[] } | null | undefined): T[] => {
|
|
if (Array.isArray(value)) return value;
|
|
return value?.rows || value?.records || [];
|
|
};
|
|
|
|
export const createMemoryIdempotencyKey = (candidateId: number) => {
|
|
const suffix = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
? crypto.randomUUID()
|
|
: `${Date.now()}-${Math.random().toString(36).slice(2, 12)}`;
|
|
return `memory-confirm-${candidateId}-${suffix}`;
|
|
};
|
|
|
|
export const getMemoryCandidates = async (status = 'DRAFT') =>
|
|
rowsOf(await apiRequest<MemoryCandidate[] | { rows?: MemoryCandidate[]; records?: MemoryCandidate[] }>({
|
|
url: `${candidateBase}?status=${encodeURIComponent(status)}`,
|
|
method: 'GET',
|
|
timeout: 15000
|
|
}));
|
|
|
|
export const confirmMemoryCandidate = (candidateId: number, request: MemoryConfirmRequest) =>
|
|
apiRequest<MemoryConfirmResult>({
|
|
url: `${candidateBase}/${candidateId}/confirm`,
|
|
method: 'POST',
|
|
data: request,
|
|
timeout: 15000
|
|
});
|
|
|
|
export const dismissMemoryCandidate = (candidateId: number) =>
|
|
apiRequest<{ id: number; status: string }>({
|
|
url: `${candidateBase}/${candidateId}/dismiss`,
|
|
method: 'POST',
|
|
timeout: 15000
|
|
});
|
|
|
|
export const getAssistantCaptures = async () =>
|
|
rowsOf(await apiRequest<AssistantCapture[] | { rows?: AssistantCapture[]; records?: AssistantCapture[] }>({
|
|
url: '/api/aihr/personal-assistant/assistant-captures?limit=50',
|
|
method: 'GET',
|
|
timeout: 15000
|
|
}));
|
|
|
|
export const updateAssistantCaptureStatus = (
|
|
captureId: number,
|
|
businessStatus: 'RECORDED' | 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'VOID',
|
|
note = ''
|
|
) => apiRequest<AssistantCapture>({
|
|
url: `/api/aihr/personal-assistant/assistant-captures/${captureId}/status`,
|
|
method: 'POST',
|
|
data: {
|
|
businessStatus,
|
|
note,
|
|
idempotencyKey: `capture-status-${captureId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
|
|
},
|
|
timeout: 15000
|
|
});
|
|
|
|
export const getAssistantCaptureStatusHistory = (captureId: number) =>
|
|
apiRequest<CaptureStatusHistory[]>({
|
|
url: `/api/aihr/personal-assistant/assistant-captures/${captureId}/status-history`,
|
|
method: 'GET',
|
|
timeout: 15000
|
|
});
|
|
|
|
export const assistantCaptureSourceUrl = (captureId: number) =>
|
|
apiUrl(`/api/aihr/personal-assistant/assistant-captures/${captureId}/source`);
|
|
|
|
export const getServiceMemories = async () => {
|
|
const rows = rowsOf(await apiRequest<RawServiceMemory[] | { rows?: RawServiceMemory[]; records?: RawServiceMemory[] }>({
|
|
url: '/api/aihr/service-memories',
|
|
method: 'GET',
|
|
timeout: 15000
|
|
}));
|
|
return rows.map((item) => ({ ...item, version: item.version ?? item.currentVersion ?? 0 }));
|
|
};
|
|
|
|
export type KnowledgeScope = PersonalKnowledgeScope;
|
|
|
|
export interface PersonalItemQuery {
|
|
pageNum?: number;
|
|
pageSize?: number;
|
|
status?: string;
|
|
sourceType?: PersonalItemSourceType | '';
|
|
dateFrom?: string;
|
|
dateTo?: string;
|
|
keyword?: string;
|
|
}
|
|
|
|
const personalQuery = (query: PersonalItemQuery) => {
|
|
const entries = Object.entries(query).filter(([, value]) => value !== undefined && value !== '');
|
|
return entries.length ? `?${entries.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join('&')}` : '';
|
|
};
|
|
|
|
export const askPersonalAssistant = (data: {
|
|
sessionId?: string; queryText: string; scope: KnowledgeScope[]; dateFrom?: string; dateTo?: string;
|
|
itemIds?: string[]; outputFormat?: 'ANSWER' | 'ACTION_PLAN' | 'OUTLINE';
|
|
}) => apiRequest<PersonalAskResponse>({ url: '/api/aihr/personal-assistant/ask', method: 'POST', data, timeout: 45000 });
|
|
|
|
export const mergePersonalRows = (current: PersonalItem[], incoming: PersonalItem[]) =>
|
|
Array.from(new Map([...current, ...incoming].map((item) => [item.id, item])).values());
|
|
|
|
export const loadPersonalSpace = () => apiRequest<PersonalSpace>({ url: '/api/aihr/personal-assistant/space', method: 'GET' });
|
|
export const loadPersonalItems = (query: PersonalItemQuery = {}) =>
|
|
apiRequest<PersonalPage<PersonalItem>>({ url: `/api/aihr/personal-assistant/items${personalQuery(query)}`, method: 'GET' });
|
|
export const loadPersonalItem = (id: string) => apiRequest<PersonalItem>({ url: personalItemApiPath(id), method: 'GET' });
|
|
export const createTextItem = (data: { title?: string; content: string; capturedAt?: string; tags?: string[] }) =>
|
|
apiRequest<PersonalItemCreated>({ url: '/api/aihr/personal-assistant/items/text', method: 'POST', data });
|
|
export const createUrlItem = (data: { url: string; title?: string; capturedAt?: string }) =>
|
|
apiRequest<PersonalItemCreated>({ url: '/api/aihr/personal-assistant/items/url', method: 'POST', data, timeout: 45000 });
|
|
|
|
export interface PersonalSelectedFile { path: string; name: string; size: number; }
|
|
export const choosePersonalFile = () => new Promise<PersonalSelectedFile>((resolve, reject) => {
|
|
uni.chooseFile({ count: 1, type: 'all', success: (result) => {
|
|
const file = (Array.isArray(result.tempFiles) ? result.tempFiles[0] : undefined) as Partial<PersonalSelectedFile> | undefined;
|
|
const path = file?.path || result.tempFilePaths?.[0] || '';
|
|
if (!path) return reject(new Error('未获取到文件路径'));
|
|
resolve({ path, name: file?.name || path.split('/').pop() || 'personal-file', size: Number(file?.size || 0) });
|
|
}, fail: () => reject(new Error('未选择文件')) });
|
|
});
|
|
|
|
export const uploadPersonalFile = (file: PersonalSelectedFile, title?: string, capturedAt?: string) =>
|
|
new Promise<PersonalItemCreated>((resolve, reject) => uni.uploadFile({
|
|
url: apiUrl('/api/aihr/personal-assistant/items/file'), filePath: file.path, name: 'file', header: authHeaders(false),
|
|
formData: { ...(title?.trim() ? { title: title.trim() } : {}), ...(capturedAt ? { capturedAt } : {}) }, timeout: 45000,
|
|
success: (response) => { try { resolve(readTextPayload<PersonalItemCreated>(response.statusCode, response.data)); } catch (error) { reject(error); } },
|
|
fail: () => reject(new Error('文件收藏失败'))
|
|
}));
|
|
|
|
export const retryPersonalItem = (id: string) => apiRequest<PersonalItem>({ url: personalItemApiPath(id, '/retry'), method: 'POST' });
|
|
export const retryPersonalOcrPages = (id: string) => apiRequest<PersonalItem>({ url: personalItemApiPath(id, '/ocr/retry-failed'), method: 'POST' });
|
|
export const deletePersonalItem = (id: string) => apiRequest<{ cleanupJobId: string }>({ url: personalItemApiPath(id), method: 'DELETE', timeout: 15000 });
|
|
export const downloadPersonalItem = (id: string) => apiRequest<{ url: string; expiresAt?: string }>({ url: personalItemApiPath(id, '/download-url'), method: 'GET' });
|
|
export const loadPersonalSessions = () => apiRequest<PersonalSession[]>({ url: '/api/aihr/personal-assistant/sessions', method: 'GET' });
|
|
export const loadPersonalSession = (id: string) => apiRequest<PersonalSessionDetail>({ url: personalSessionApiPath(id), method: 'GET' });
|
|
export const deletePersonalSession = (id: string) => apiRequest<void>({ url: personalSessionApiPath(id), method: 'DELETE' });
|
|
export const createPersonalOutline = (data: { sessionId: string; title?: string }) => apiRequest<PersonalExportOutline>({ url: '/api/aihr/personal-assistant/exports/outline', method: 'POST', data });
|
|
export const updatePersonalOutline = (id: string, data: { title: string; sections: PersonalExportOutlineSection[] }) =>
|
|
apiRequest<PersonalExportOutline>({ url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}/outline`, method: 'PUT', data });
|
|
export const confirmPersonalOutline = (id: string) => apiRequest<void>({ url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}/confirm`, method: 'POST' });
|
|
export const loadPersonalExport = (id: string) => apiRequest<PersonalExportOutline>({ url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}`, method: 'GET' });
|
|
export const enqueuePersonalPpt = (id: string) => apiRequest<void>({ url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}/pptx`, method: 'POST', data: { templateCode: 'standard' } });
|
|
export const downloadPersonalExport = (id: string) => apiRequest<{ url: string; expiresAt?: string }>({ url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}/download-url`, method: 'GET' });
|
|
export const requestPersonalPublish = (itemId: string, reason: string) => apiRequest<PersonalPublishRequest>({ url: `${personalItemApiPath(itemId)}/publish-requests`, method: 'POST', data: { targetScope: 'POSITION:生活顾问', reason } });
|
|
export const loadPersonalPublishRequests = () => apiRequest<PersonalPublishRequest[]>({ url: '/api/aihr/personal-assistant/publish-requests', method: 'GET' });
|