Files
Agent-System/web/src/hooks/useDocumentLifecycleActions.ts
T
weichen 143ee934b8 feat(collab): free-lane chat, claim-on-view, lease ownership, notify inbox
Open member free-lane for chat initiation; auto-claim on author view;
harden lease persist/ownership with readable DOC_BUSY; add notification
inbox and supplement-impact HTTP decision APIs wired to composition root.
2026-09-24 11:49:43 +00:00

238 lines
7.3 KiB
TypeScript

import type { Dispatch, SetStateAction } from 'react';
import {
approverDecision,
claimDocument,
mapLeaseToAstEditLease,
reviewDecision,
submitForReview,
type DocGovRecord,
} from '../api/docgov';
import type { DocumentAST } from '../types/block';
import type { DocLifecycleState, UserProfile } from '../types/docgov';
import type { ChatMessage, DeliverableItem } from '../types/workbench';
export interface DocumentLifecycleInput {
docAST: DocumentAST;
setDocAST: (next: DocumentAST) => void;
activeDocId: string;
currentUser: UserProfile;
activeSessionId: string;
setDeliverables: Dispatch<SetStateAction<DeliverableItem[]>>;
setMessages: Dispatch<SetStateAction<ChatMessage[]>>;
}
export interface DocumentLifecycleActions {
handleClaim: () => Promise<void>;
handleSubmitReview: () => Promise<void>;
handlePassReview: () => Promise<void>;
handleRejectReview: (reason: string) => Promise<void>;
handleApprove: () => Promise<void>;
handleRejectApprove: (reason: string) => Promise<void>;
}
export function useDocumentLifecycleActions(input: DocumentLifecycleInput): DocumentLifecycleActions {
const {
docAST,
setDocAST,
activeDocId,
currentUser,
activeSessionId,
setDeliverables,
setMessages,
} = input;
const applyDocGovRecord = (doc: DocGovRecord) => {
const nextState = (doc.current_state || docAST.lifecycleState) as DocLifecycleState;
const nextClaimed = doc.author_claimed ?? docAST.isClaimed;
const lease = mapLeaseToAstEditLease(doc);
const nextAst: DocumentAST = {
...docAST,
lifecycleState: nextState,
isClaimed: nextClaimed,
authorClaimedAt: doc.author_claimed_at || docAST.authorClaimedAt,
authorId: doc.author_id ?? docAST.authorId,
author: doc.author_name || docAST.author,
reviewerId: doc.reviewer_id ?? docAST.reviewerId,
reviewer: doc.reviewer_name || docAST.reviewer,
approverId: doc.approver_id ?? docAST.approverId,
approver: doc.approver_name || docAST.approver,
dutyPolicy: doc.duty_policy || docAST.dutyPolicy,
contentSha256: doc.content_sha256 || docAST.contentSha256,
editLease: lease ?? docAST.editLease,
};
setDocAST(nextAst);
setDeliverables((prev) =>
prev.map((d) =>
d.docId === activeDocId || d.docId === doc.doc_id
? {
...d,
lifecycleState: nextState,
isClaimed: nextClaimed,
authorId: doc.author_id ?? d.authorId,
author: doc.author_name || d.author,
reviewerId: doc.reviewer_id ?? d.reviewerId,
reviewer: doc.reviewer_name || d.reviewer,
approverId: doc.approver_id ?? d.approverId,
approver: doc.approver_name || d.approver,
dutyPolicy: doc.duty_policy || d.dutyPolicy,
editLease: lease ?? d.editLease,
}
: d,
),
);
};
const pushDocGovError = (actionLabel: string, err: unknown) => {
const message = err instanceof Error ? err.message : String(err);
setMessages((prev) => [
...prev,
{
id: 'docgov-err-' + Date.now(),
sessionId: activeSessionId,
role: 'system',
content: `⚠️ ${actionLabel}失败:${message}`,
timestamp: new Date().toTimeString().substring(0, 5),
},
]);
};
const handleClaim = async () => {
try {
const doc = await claimDocument({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
user_role: 'author',
});
applyDocGovRecord(doc);
} catch (err) {
pushDocGovError('认领文档', err);
}
};
const handleSubmitReview = async () => {
try {
const doc = await submitForReview({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
});
applyDocGovRecord(doc);
} catch (err) {
pushDocGovError('提交送审', err);
}
};
const handlePassReview = async () => {
try {
const doc = await reviewDecision({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
action: 'approve',
});
applyDocGovRecord(doc);
} catch (err) {
pushDocGovError('审核通过', err);
}
};
const handleRejectReview = async (reason: string) => {
try {
const doc = await reviewDecision({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
action: 'reject',
reason,
});
applyDocGovRecord(doc);
setMessages((prev) => [
...prev,
{
id: 'rej-' + Date.now(),
sessionId: activeSessionId,
role: 'system',
content: `⚠️ ${currentUser.name}(审核)驳回了《${docAST.title}》,已退回编制阶段。驳回原因:${reason}`,
timestamp: new Date().toTimeString().substring(0, 5),
},
]);
} catch (err) {
pushDocGovError('审核驳回', err);
}
};
const handleApprove = async () => {
try {
const doc = await approverDecision({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
action: 'approve',
});
applyDocGovRecord(doc);
setMessages((prev) => [
...prev,
{
id: 'appr-' + Date.now(),
sessionId: activeSessionId,
role: 'assistant',
authorName: '水木东方协同参谋',
intentTag: '🏆 终审批准生效',
content: `🎉 祝贺!${currentUser.name}(批准人)已终审批准《${docAST.title}》!系统已自动对该受控交付物进行微粒度章节切片与向量化,成功注入全局标杆案例经验库!`,
timestamp: new Date().toTimeString().substring(0, 5),
recommendedCases: [
{
docId: docAST.docId,
taskKey: docAST.taskKey,
artifactTitle: docAST.title,
sectionTitle: '终审已生效标杆案例',
similarityScore: 1.0,
keyRegulations: ['GB 9706.1-2020', 'YY 0569-2011', 'IEC 60601-1-2'],
summarySnippet:
'本交付物已通过编制、审核、批准法定三级签署闭环,作为往期标杆经验向全平台开放自推荐。',
},
],
},
]);
} catch (err) {
pushDocGovError('终审批准', err);
}
};
const handleRejectApprove = async (reason: string) => {
try {
const doc = await approverDecision({
doc_id: docAST.docId,
user_id: currentUser.id,
user_name: currentUser.name,
action: 'reject',
reason,
rollback_to: 'drafting',
});
applyDocGovRecord(doc);
setMessages((prev) => [
...prev,
{
id: 'rej-appr-' + Date.now(),
sessionId: activeSessionId,
role: 'system',
content: `⚠️ ${currentUser.name}(批准人)驳回了《${docAST.title}》,已退回编制阶段。驳回原因:${reason}`,
timestamp: new Date().toTimeString().substring(0, 5),
},
]);
} catch (err) {
pushDocGovError('批准驳回', err);
}
};
return {
handleClaim,
handleSubmitReview,
handlePassReview,
handleRejectReview,
handleApprove,
handleRejectApprove,
};
}