feat: add project-scoped work assistant results

This commit is contained in:
2026-07-21 04:44:15 +08:00
parent 53b15e506c
commit 4536d607e3
75 changed files with 3281 additions and 507 deletions
+46 -2
View File
@@ -1,8 +1,9 @@
import type { LoginResult } from '@/types/api';
import type { LoginResult, MobileProject } from '@/types/api';
export const tokenKey = 'aihr_mobile_access_token';
export const clientIdKey = 'aihr_mobile_client_id';
export const phoneKey = 'aihr_mobile_phone';
export const tenantIdKey = 'aihr_mobile_tenant_id';
export const loginRedirectKey = 'aihr_mobile_login_redirect';
export const positionKey = 'aihr_mobile_position';
export const positionSourceKey = 'aihr_mobile_position_source';
@@ -25,6 +26,7 @@ export interface AuthState {
token: string;
clientId: string;
phone: string;
tenantId: string;
position: UserPosition | '';
}
@@ -112,6 +114,7 @@ export const getAuth = (): AuthState => ({
token: uni.getStorageSync(tokenKey) || '',
clientId: uni.getStorageSync(clientIdKey) || '',
phone: uni.getStorageSync(phoneKey) || '',
tenantId: uni.getStorageSync(tenantIdKey) || '000000',
position: normalizePosition(uni.getStorageSync(positionKey))
});
@@ -134,7 +137,45 @@ export const leaveSupervisorLearnerMode = () => {
uni.removeStorageSync(supervisorLearnerModeKey);
};
export const saveAuth = (result: LoginResult, phone: string) => {
const projectStorageKey = (tenantId: string, phone: string) =>
`aihr_mobile_current_project:${tenantId || '000000'}:${phone || 'anonymous'}`;
export const getCurrentProject = (): MobileProject | null => {
const auth = getAuth();
const stored = uni.getStorageSync(projectStorageKey(auth.tenantId, auth.phone));
if (!stored || typeof stored !== 'object') return null;
const project = stored as Partial<MobileProject>;
return project.projectCode && project.projectName
? { projectCode: String(project.projectCode), projectName: String(project.projectName) }
: null;
};
export const saveCurrentProject = (project: MobileProject) => {
const auth = getAuth();
uni.setStorageSync(projectStorageKey(auth.tenantId, auth.phone), project);
};
export const clearCurrentProject = () => {
const auth = getAuth();
uni.removeStorageSync(projectStorageKey(auth.tenantId, auth.phone));
};
export const reconcileCurrentProject = (projects: MobileProject[]) => {
const current = getCurrentProject();
const matched = current && projects.find((project) => project.projectCode === current.projectCode);
if (matched) {
saveCurrentProject(matched);
return matched;
}
clearCurrentProject();
if (projects.length === 1) {
saveCurrentProject(projects[0]);
return projects[0];
}
return null;
};
export const saveAuth = (result: LoginResult, phone: string, tenantId = '000000') => {
const previousPhone = uni.getStorageSync(phoneKey) || '';
if (previousPhone !== phone) {
clearOrgPosition();
@@ -144,12 +185,15 @@ export const saveAuth = (result: LoginResult, phone: string) => {
uni.setStorageSync(tokenKey, result.access_token);
uni.setStorageSync(clientIdKey, result.client_id);
uni.setStorageSync(phoneKey, phone);
uni.setStorageSync(tenantIdKey, tenantId);
};
export const clearAuth = () => {
clearCurrentProject();
uni.removeStorageSync(tokenKey);
uni.removeStorageSync(clientIdKey);
uni.removeStorageSync(phoneKey);
uni.removeStorageSync(tenantIdKey);
clearOrgPosition();
uni.removeStorageSync(roleKey);
leaveSupervisorLearnerMode();
+14 -6
View File
@@ -81,7 +81,9 @@ export const mergeKnowledgeSnippets = (...groups: RawKnowledgeSnippet[][]): Know
export const searchKnowledge = async (
queryText: string,
position = getSelectedPosition(),
conversation?: KnowledgeConversationState
conversation?: KnowledgeConversationState,
projectCode?: string,
source: 'mobile_uni_sop' | 'mobile_uni_voice' = 'mobile_uni_sop'
) => {
const response = await apiRequest<UnifiedKnowledgeResponse>({
url: '/api/knowledge/query',
@@ -90,9 +92,11 @@ export const searchKnowledge = async (
queryText,
category: 'sop',
position,
source,
limit: 5,
conversationId: conversation?.conversationId,
contextVersion: conversation?.contextVersion
contextVersion: conversation?.contextVersion,
projectCode
},
timeout: 30000
});
@@ -104,7 +108,8 @@ export const searchKnowledgeWithMedia = (
queryText: string,
position = getSelectedPosition(),
onUploadTask?: (task: ReturnType<typeof uni.uploadFile> | null) => void,
conversation?: KnowledgeConversationState
conversation?: KnowledgeConversationState,
projectCode?: string
) => new Promise<KnowledgeSearchResponse>((resolve, reject) => {
const task = uni.uploadFile({
url: apiUrl('/api/knowledge/query-media'),
@@ -117,7 +122,8 @@ export const searchKnowledgeWithMedia = (
...(conversation ? {
conversationId: conversation.conversationId,
contextVersion: String(conversation.contextVersion)
} : {})
} : {}),
...(projectCode ? { projectCode } : {})
},
timeout: 360000,
success: (response) => {
@@ -137,7 +143,8 @@ export const searchKnowledgeWithMedia = (
export const queryKnowledgeTool = async (
toolCode: 'MY_PRACTICE_SUMMARY' | 'TEAM_PRACTICE_SUMMARY',
queryText: string,
conversation?: KnowledgeConversationState
conversation?: KnowledgeConversationState,
projectCode?: string
) => {
const response = await apiRequest<UnifiedKnowledgeResponse>({
url: '/api/knowledge/query',
@@ -148,7 +155,8 @@ export const queryKnowledgeTool = async (
limit: 5,
toolCode,
conversationId: conversation?.conversationId,
contextVersion: conversation?.contextVersion
contextVersion: conversation?.contextVersion,
projectCode
},
timeout: 30000
});
+27 -1
View File
@@ -5,7 +5,8 @@ import type {
MemoryConfirmResult,
ServiceMemory
} from '@/types/api';
import { apiRequest } from './api';
import type { CaptureStatusHistory } from '@/types/api';
import { apiRequest, apiUrl } from './api';
const candidateBase = '/api/aihr/personal-assistant/memory-candidates';
@@ -52,6 +53,31 @@ export const getAssistantCaptures = async () =>
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',
+9 -12
View File
@@ -12,6 +12,13 @@ import {
type UserPosition
} from './auth';
export const fetchMobileIdentity = () => apiRequest<MobileIdentity>({
url: '/api/aihr/mobile/me',
method: 'GET',
timeout: 15000,
clearAuthOnUnauthorized: false
});
export const userTabPages = new Set([
'/pages/user/today/index',
'/pages/user/practice/index',
@@ -35,12 +42,7 @@ export const routeForMobileRole = (role: RoleKey, requested: string) => {
export const hydrateMobileRole = async (): Promise<RoleKey> => {
try {
const identity = await apiRequest<MobileIdentity>({
url: '/api/aihr/mobile/me',
method: 'GET',
timeout: 15000,
clearAuthOnUnauthorized: false
});
const identity = await fetchMobileIdentity();
const role = identity.role === 'supervisor' ? 'supervisor' : 'user';
uni.setStorageSync(roleKey, role);
if (role !== 'supervisor') leaveSupervisorLearnerMode();
@@ -71,12 +73,7 @@ export type OrgPositionResult =
export const fetchOrgPositionResult = async (): Promise<OrgPositionResult> => {
try {
const identity = await apiRequest<MobileIdentity>({
url: '/api/aihr/mobile/me',
method: 'GET',
timeout: 15000,
clearAuthOnUnauthorized: false
});
const identity = await fetchMobileIdentity();
const officialPosition = saveOrgPosition(identity.positionName);
if (!officialPosition) {
clearOrgPosition();
+49
View File
@@ -0,0 +1,49 @@
import { apiUrl, authHeaders } from './api';
type PreviewMediaApi = (options: {
sources: Array<{ url: string; type: 'video' }>;
fail?: () => void;
}) => void;
let sourceAudio: UniApp.InnerAudioContext | null = null;
const fail = () => uni.showToast({ title: '原始来源读取失败', icon: 'none' });
export const openProtectedSource = (sourceUrl: string, sourceType: string) => {
uni.downloadFile({
url: apiUrl(sourceUrl),
header: authHeaders(false),
success: (response) => {
if (response.statusCode !== 200 || !response.tempFilePath) {
fail();
return;
}
if (sourceType === 'IMAGE') {
uni.previewImage({ urls: [response.tempFilePath] });
return;
}
if (sourceType === 'VOICE') {
sourceAudio?.destroy();
sourceAudio = uni.createInnerAudioContext();
sourceAudio.src = response.tempFilePath;
sourceAudio.onError(fail);
sourceAudio.play();
return;
}
if (sourceType === 'VIDEO') {
const previewMedia = (uni as typeof uni & { previewMedia?: PreviewMediaApi }).previewMedia;
if (!previewMedia) {
uni.showToast({ title: '当前端暂不支持视频预览', icon: 'none' });
return;
}
previewMedia({
sources: [{ url: response.tempFilePath, type: 'video' }],
fail
});
return;
}
uni.showToast({ title: '当前来源类型暂不支持预览', icon: 'none' });
},
fail
});
};
+22
View File
@@ -0,0 +1,22 @@
import type { ProjectWorkResult, WorkResult } from '@/types/api';
import { apiRequest } from './api';
const params = (projectCode: string, workDate?: string) => {
const query = new URLSearchParams({ projectCode });
if (workDate) query.set('workDate', workDate);
return query.toString();
};
export const generateMyWorkResult = (projectCode: string, workDate?: string) =>
apiRequest<WorkResult>({
url: `/api/aihr/work-results/mine/generate?${params(projectCode, workDate)}`,
method: 'POST',
timeout: 20000
});
export const getProjectWorkResult = (projectCode: string, workDate?: string) =>
apiRequest<ProjectWorkResult>({
url: `/api/aihr/work-results/project?${params(projectCode, workDate)}`,
method: 'GET',
timeout: 20000
});