feat: add project-scoped work assistant results
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
});
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
Reference in New Issue
Block a user