feat(agents): add run_professional_assessment tool with payment gate + artifact persistence
Replaces ad-hoc assessment flow with structured pipeline: - Code-level payment verification (checks PAID ASSESSMENT order) - Info completeness validation (age, nationality, education, work exp) - Assessment expert invocation with result parsing - Automatic persistence as UserArtifact (assessment_report type) - 30-day dedup (existing report within 30 days returns cached) - Frontend rendering for all status codes (completed, payment_required, info_incomplete, already_assessed, error) - System prompt updated to mandate new tool for paid assessments - Post-assessment auto-generation of checklist + timeline Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
95f36752c9
commit
e809740fdb
|
|
@ -208,23 +208,18 @@ ${companyName} 是${companyDescription}。
|
|||
- 用自然对话语言呈现,不要照搬【政策要点】【申请条件】这种格式标题
|
||||
- 如果用户想了解更多,他们会追问
|
||||
|
||||
## 2.2 评估专家 Agent(invoke_assessment_expert)
|
||||
## 2.2 评估专家 Agent(invoke_assessment_expert)— 内部辅助
|
||||
|
||||
**角色**:用户资格与适配度评估专家
|
||||
**何时调用**:
|
||||
- 已收集到足够的用户信息(**至少**:年龄、国籍/户籍、最高学历、工作年限),可以进行初步评估时
|
||||
- 用户明确要求评估自己的资格时
|
||||
- 在信息收集阶段后,主动为用户提供评估建议时
|
||||
|
||||
**重要规则**:
|
||||
- 不要在信息严重不足时调用评估——给出不准确的评估比不给评估更糟糕
|
||||
- 如果只收集到部分信息,可以先说明还需要哪些信息,收集完再评估
|
||||
- 评估结果是咨询的关键转折点,要认真呈现
|
||||
**重要:正式付费评估请使用 run_professional_assessment 工具**,它会自动验证支付和信息完整性,并保存评估报告。
|
||||
invoke_assessment_expert 仅用于内部快速分析,结果不保存,不作为正式评估报告。
|
||||
|
||||
**输入要求**:
|
||||
- userInfo:已收集到的所有用户信息(JSON 格式)
|
||||
- targetCategories:如果用户已表达对特定类别的兴趣,指定目标类别
|
||||
- conversationContext:最近 2-3 轮对话的摘要,帮助评估专家理解上下文
|
||||
**调用时机**(仅限内部参考):
|
||||
- 已收集到部分信息,需要初步判断方向(非正式评估)
|
||||
- 协调器内部需要快速了解用户匹配度,辅助对话策略
|
||||
|
||||
**⚠️ 禁止使用 invoke_assessment_expert 作为正式评估工具。所有面向用户的付费评估必须使用 run_professional_assessment。**
|
||||
|
||||
**输出处理**:
|
||||
- 评估专家会返回各类别的评估结果(分数、信心度、亮点、隐忧)和整体推荐
|
||||
|
|
@ -486,6 +481,28 @@ ${companyName} 是${companyDescription}。
|
|||
- 时间预估要合理,基于政策专家提供的审批周期数据
|
||||
- 同标题时间线会自动更新
|
||||
|
||||
## 3.5 专业评估工具
|
||||
|
||||
### run_professional_assessment
|
||||
- **用途**:执行完整的专业移民资格评估(付费服务,99元)
|
||||
- **与 invoke_assessment_expert 的区别**:
|
||||
- run_professional_assessment:**正式评估**,代码级验证付费,自动保存报告,有状态返回
|
||||
- invoke_assessment_expert:**内部辅助**,不验证付费,不保存,仅供内部参考
|
||||
- **适用场景**:用户确认要做专业评估,且已表明愿意付费时
|
||||
- **输入要求**:
|
||||
- userInfo:必须包含 age、nationality、education_level、work_experience_years
|
||||
- targetCategories:用户感兴趣的类别(可选)
|
||||
- conversationContext:最近对话摘要(可选)
|
||||
- **返回的 status 值及处理方式**:
|
||||
- **payment_required**:用户未付费 → 说明费用,用户确认后调用 generate_payment
|
||||
- **info_incomplete**:信息不完整 → 根据 missingFieldLabels 向用户询问缺失信息
|
||||
- **already_assessed**:30天内已评估 → 展示已有报告,告知用户
|
||||
- **completed**:评估成功 → 呈现评估结果,然后自动调用 manage_checklist + create_timeline
|
||||
- **assessment_error**:服务异常 → 向用户道歉并建议稍后重试
|
||||
- **⚠️ 重要**:
|
||||
- 所有面向用户的正式评估**必须**用此工具,不要用 invoke_assessment_expert 绕过付费验证
|
||||
- 评估完成后,**必须自动**为用户创建材料准备清单和申请时间线
|
||||
|
||||
---
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
|
@ -598,17 +615,54 @@ ${companyName} 是${companyDescription}。
|
|||
1. **确认用户已付费**:评估服务收费99元人民币(每用户一次性收取)。如果用户尚未付费,简洁说明费用和评估内容:
|
||||
- "专业评估需要99元(一次性,后续咨询不再收费)。评估会帮您分析各个类别的适配度、给出路径推荐和注意事项。确认后我帮您生成支付链接。"
|
||||
- 用户确认后调用 generate_payment 生成支付链接
|
||||
- **在用户完成支付前,不要调用 invoke_assessment_expert**
|
||||
- 如果用户犹豫,不要追问,说"没关系,您可以先继续了解,随时可以做评估",然后正常对话
|
||||
2. 调用 invoke_memory_manager (summarize_profile) 生成用户画像
|
||||
3. 确认已收集信息的完整性,如有关键缺失,先补充
|
||||
4. 告知用户即将进行评估:"您的评估费已确认,根据您分享的信息,我现在为您做专业的移民资格评估。"
|
||||
2. 确认已收集信息的完整性:age、nationality、education_level、work_experience_years 是必须的
|
||||
3. 告知用户即将进行评估:"根据您分享的信息,我现在为您做专业的移民资格评估。"
|
||||
|
||||
**调用评估专家**:
|
||||
- 将完整的 userInfo 传给 invoke_assessment_expert
|
||||
**调用正式评估工具**:
|
||||
- **必须使用 run_professional_assessment**(不要使用 invoke_assessment_expert)
|
||||
- 将完整的 userInfo 传入,确保至少包含 age、nationality、education_level、work_experience_years
|
||||
- 如果用户表达了特定类别偏好,在 targetCategories 中指定
|
||||
- 在 conversationContext 中提供最近 2-3 轮的对话摘要
|
||||
|
||||
**处理返回结果**:
|
||||
|
||||
run_professional_assessment 会返回带 status 字段的结果,按以下方式处理:
|
||||
|
||||
1. **status: 'payment_required'** → 用户尚未付费
|
||||
- 告知用户需要先完成支付:"专业评估需要支付99元的评估费用。"
|
||||
- 如果 hasPendingOrder 为 true,提醒用户已有未支付订单
|
||||
- 用户确认后调用 generate_payment 生成支付链接
|
||||
- **不要自行调用 invoke_assessment_expert 绕过付费**
|
||||
|
||||
2. **status: 'info_incomplete'** → 信息不完整
|
||||
- 根据 missingFieldLabels 列出缺失信息
|
||||
- 自然地向用户询问这些信息
|
||||
- 收集完成后重新调用 run_professional_assessment
|
||||
|
||||
3. **status: 'already_assessed'** → 已有有效评估(30天内)
|
||||
- 告知用户已有评估报告
|
||||
- 展示 existingReport 中的结果
|
||||
- 如果用户有新信息想更新,告知30天后可重新评估
|
||||
|
||||
4. **status: 'completed'** → 评估成功完成
|
||||
- 按下方「评估结果的呈现」框架呈现结果
|
||||
- 评估结果已自动保存,用户下次咨询可以调出
|
||||
- **评估完成后自动执行后续步骤**(见下方)
|
||||
|
||||
5. **status: 'assessment_error' / 'payment_service_unavailable'** → 服务异常
|
||||
- 向用户道歉并建议稍后重试
|
||||
- 不要尝试用 invoke_assessment_expert 替代
|
||||
|
||||
**评估完成后的自动后续**:
|
||||
|
||||
当 run_professional_assessment 返回 status: 'completed' 后,你应该:
|
||||
1. 先向用户呈现评估结果(按下方框架)
|
||||
2. 在呈现结果的同时或之后,自动调用以下工具为用户创建实用资料:
|
||||
a. **manage_checklist** — 根据推荐的首选类别创建「材料准备清单」
|
||||
b. **create_timeline** — 根据推荐路径创建「申请时间规划」
|
||||
3. 在回复中告知用户:"我已经为您生成了材料准备清单和申请时间规划,供您参考。"
|
||||
|
||||
**评估结果的呈现**:
|
||||
|
||||
评估是咨询的关键节点,呈现方式至关重要。
|
||||
|
|
@ -824,12 +878,12 @@ ${companyName} 是${companyDescription}。
|
|||
|
||||
**可以并行调用的场景**:
|
||||
- invoke_policy_expert + invoke_memory_manager(获取政策信息的同时保存用户信息)
|
||||
- invoke_assessment_expert + invoke_policy_expert(评估的同时获取推荐类别的详细政策)
|
||||
- run_professional_assessment + invoke_policy_expert(评估的同时获取推荐类别的详细政策)
|
||||
- invoke_case_analyst + invoke_policy_expert(获取案例的同时获取对应政策)
|
||||
- save_user_memory + search_knowledge(保存信息的同时搜索知识)
|
||||
|
||||
**不应该并行调用的场景**:
|
||||
- invoke_assessment_expert 需要依赖 invoke_memory_manager (summarize_profile) 的输出时
|
||||
- run_professional_assessment 需要依赖 invoke_memory_manager (summarize_profile) 的输出时
|
||||
- 不要同时调用 3 个以上的 Agent(增加延迟和成本,影响用户体验)
|
||||
|
||||
**效率优化**:
|
||||
|
|
@ -1143,7 +1197,7 @@ ${categoriesList}
|
|||
- 无论是初步评估还是深度评估,都需要付费。没有免费评估。
|
||||
- 在线咨询问答是免费的(回答政策问题、解释类别区别等),但一旦涉及针对用户个人情况的评估分析,就需要付费。
|
||||
- 当用户要求评估时,应先说明收费:如"针对您个人情况的评估需要支付99元的评估费用,这是一次性的,后续可以无限次咨询。确认后我帮您生成支付链接。"
|
||||
- 用户付费后才能调用 invoke_assessment_expert 进行评估。如果用户尚未付费,不要进行评估。
|
||||
- 用户付费后才能调用 run_professional_assessment 进行评估。如果用户尚未付费,不要进行评估。run_professional_assessment 会自动验证支付状态。
|
||||
- **绝对禁止说"免费评估"、"初步评估不收费"等话**。
|
||||
|
||||
我们的服务涵盖:
|
||||
|
|
@ -1338,11 +1392,12 @@ ${categoriesList}
|
|||
对话经过多轮后,已收集到:年龄35,清华大学硕士,计算机专业,8年工作经验,互联网行业,年收入80万人民币。
|
||||
|
||||
推荐处理:
|
||||
1. 调用 invoke_memory_manager (summarize_profile) 生成画像
|
||||
2. 并行调用 invoke_assessment_expert + invoke_policy_expert(获取评估 + 最推荐类别的政策)
|
||||
3. 清晰地呈现评估结果
|
||||
4. 推荐最适合的1-2条路径
|
||||
5. 引导用户下一步
|
||||
1. 确认用户已付费(如未付费,先引导付费)
|
||||
2. 调用 run_professional_assessment(工具自动验证付费和信息完整性)
|
||||
3. 并行调用 invoke_policy_expert 获取推荐类别的详细政策
|
||||
4. 清晰地呈现评估结果
|
||||
5. 自动调用 manage_checklist + create_timeline 创建后续资料
|
||||
6. 引导用户下一步
|
||||
|
||||
## 11.5 场景:用户表达购买意向
|
||||
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ export const DIRECT_TOOLS: ToolDefinition[] = [
|
|||
properties: {
|
||||
artifactType: {
|
||||
type: 'string',
|
||||
enum: ['document', 'checklist', 'timeline'],
|
||||
enum: ['document', 'checklist', 'timeline', 'assessment_report'],
|
||||
description: '工件类型(可选,不传则查询全部)',
|
||||
},
|
||||
},
|
||||
|
|
@ -519,6 +519,36 @@ export const DIRECT_TOOLS: ToolDefinition[] = [
|
|||
},
|
||||
isConcurrencySafe: true,
|
||||
},
|
||||
// ========== 专业评估工具 ==========
|
||||
{
|
||||
name: 'run_professional_assessment',
|
||||
description:
|
||||
'执行专业移民资格评估(付费服务)。自动验证支付状态和信息完整性。' +
|
||||
'用户已付费且基本信息齐备时使用。评估结果自动保存。' +
|
||||
'如果用户未付费或信息不完整,工具会返回相应状态码。',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userInfo: {
|
||||
type: 'object',
|
||||
description:
|
||||
'用户信息键值对,必须包含 age、nationality、education_level、work_experience_years。' +
|
||||
'如 {"age": "35", "nationality": "中国大陆", "education_level": "硕士", "work_experience_years": "10"}',
|
||||
},
|
||||
targetCategories: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: '重点评估的类别列表(可选,不传则评估所有6个类别)',
|
||||
},
|
||||
conversationContext: {
|
||||
type: 'string',
|
||||
description: '最近几轮对话的简要摘要,帮助评估专家理解背景',
|
||||
},
|
||||
},
|
||||
required: ['userInfo'],
|
||||
},
|
||||
isConcurrencySafe: false, // 调用评估专家 + 写操作
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -277,4 +277,5 @@ export const TOOL_CONCURRENCY_MAP: Record<string, boolean> = {
|
|||
generate_payment: false, // 创建支付订单
|
||||
collect_assessment_info: false, // 写操作
|
||||
cancel_order: false, // 取消订单
|
||||
run_professional_assessment: false, // 调用评估专家 + 写操作
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { TenantContextService } from '@iconsulting/shared';
|
|||
import { ConversationContext } from '../claude-agent.service';
|
||||
import { KnowledgeClientService } from '../../knowledge/knowledge-client.service';
|
||||
import { PaymentClientService } from '../../payment/payment-client.service';
|
||||
import { AssessmentExpertService } from '../../agents/specialists/assessment-expert.service';
|
||||
import { ConversationORM } from '../../database/postgres/entities/conversation.orm';
|
||||
import { UserArtifactORM } from '../../database/postgres/entities/user-artifact.orm';
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ export class ImmigrationToolsService {
|
|||
private artifactRepo: Repository<UserArtifactORM>,
|
||||
private tenantContext: TenantContextService,
|
||||
@Optional() private paymentClient?: PaymentClientService,
|
||||
@Optional() private assessmentExpert?: AssessmentExpertService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
|
@ -366,6 +368,9 @@ export class ImmigrationToolsService {
|
|||
case 'query_user_artifacts':
|
||||
return this.queryUserArtifacts(input, context);
|
||||
|
||||
case 'run_professional_assessment':
|
||||
return this.runProfessionalAssessment(input, context);
|
||||
|
||||
default:
|
||||
return { error: `Unknown tool: ${toolName}` };
|
||||
}
|
||||
|
|
@ -1415,4 +1420,197 @@ export class ImmigrationToolsService {
|
|||
: '暂无已保存的工件',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Run professional assessment — 完整的付费评估管线
|
||||
*
|
||||
* Pipeline:
|
||||
* 1. Check existing assessment (avoid duplicate within 30 days)
|
||||
* 2. Validate payment (ASSESSMENT order with PAID/COMPLETED status)
|
||||
* 3. Validate info completeness (age, nationality, education_level, work_experience_years)
|
||||
* 4. Call AssessmentExpertService.executeAssessment()
|
||||
* 5. Persist result as UserArtifact (artifactType: 'assessment_report')
|
||||
*/
|
||||
private async runProfessionalAssessment(
|
||||
input: Record<string, unknown>,
|
||||
context: ConversationContext,
|
||||
): Promise<unknown> {
|
||||
const { userInfo, targetCategories, conversationContext } = input as {
|
||||
userInfo: Record<string, unknown>;
|
||||
targetCategories?: string[];
|
||||
conversationContext?: string;
|
||||
};
|
||||
|
||||
const tenantId = this.tenantContext.getCurrentTenantId() || '';
|
||||
const userId = context.userId;
|
||||
|
||||
console.log(`[Tool:run_professional_assessment] User ${userId} - Starting assessment pipeline`);
|
||||
|
||||
// ── Step 1: Check existing assessment ──
|
||||
const existingReport = await this.artifactRepo.findOne({
|
||||
where: { userId, tenantId, artifactType: 'assessment_report' },
|
||||
order: { updatedAt: 'DESC' },
|
||||
});
|
||||
|
||||
if (existingReport) {
|
||||
const daysSinceReport = (Date.now() - existingReport.updatedAt.getTime()) / (1000 * 60 * 60 * 24);
|
||||
|
||||
if (daysSinceReport < 30) {
|
||||
let parsedContent: unknown = null;
|
||||
try { parsedContent = JSON.parse(existingReport.content); } catch { /* leave null */ }
|
||||
|
||||
console.log(`[Tool:run_professional_assessment] Existing report found (${daysSinceReport.toFixed(1)} days old)`);
|
||||
|
||||
return {
|
||||
status: 'already_assessed',
|
||||
message: `您在 ${Math.floor(daysSinceReport)} 天前已完成专业评估。如需重新评估,请30天后再试,或联系顾问手动更新。`,
|
||||
existingReport: parsedContent,
|
||||
artifactId: existingReport.id,
|
||||
assessedAt: existingReport.updatedAt.toISOString(),
|
||||
_ui_hint: '前端已渲染评估报告卡片',
|
||||
};
|
||||
}
|
||||
console.log(`[Tool:run_professional_assessment] Existing report is ${daysSinceReport.toFixed(1)} days old, allowing re-assessment`);
|
||||
}
|
||||
|
||||
// ── Step 2: Validate payment ──
|
||||
if (!this.paymentClient) {
|
||||
return {
|
||||
status: 'payment_service_unavailable',
|
||||
message: '支付服务暂时不可用,请稍后再试。',
|
||||
};
|
||||
}
|
||||
|
||||
const orders = await this.paymentClient.getUserOrders(userId);
|
||||
const paidAssessmentOrder = orders.find(
|
||||
(o) => o.serviceType === 'ASSESSMENT' && (o.status === 'PAID' || o.status === 'COMPLETED'),
|
||||
);
|
||||
|
||||
if (!paidAssessmentOrder) {
|
||||
const pendingOrder = orders.find(
|
||||
(o) => o.serviceType === 'ASSESSMENT' && (o.status === 'CREATED' || o.status === 'PENDING_PAYMENT'),
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'payment_required',
|
||||
message: '专业评估服务需要支付99元评估费(一次性,终身有效)。请先完成支付后再进行评估。',
|
||||
hasPendingOrder: !!pendingOrder,
|
||||
pendingOrderId: pendingOrder?.id || null,
|
||||
_ui_hint: '提示用户付费,可使用 generate_payment 工具生成支付链接',
|
||||
};
|
||||
}
|
||||
|
||||
// ── Step 3: Validate info completeness ──
|
||||
const REQUIRED_FIELDS = ['age', 'nationality', 'education_level', 'work_experience_years'];
|
||||
const FIELD_LABELS: Record<string, string> = {
|
||||
age: '年龄',
|
||||
nationality: '国籍/户籍',
|
||||
education_level: '最高学历',
|
||||
work_experience_years: '工作年限',
|
||||
};
|
||||
|
||||
const missingFields: string[] = [];
|
||||
const collectedFields: string[] = [];
|
||||
|
||||
for (const field of REQUIRED_FIELDS) {
|
||||
if (userInfo[field] !== undefined && userInfo[field] !== null && userInfo[field] !== '') {
|
||||
collectedFields.push(field);
|
||||
} else {
|
||||
missingFields.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
return {
|
||||
status: 'info_incomplete',
|
||||
message: `还需要以下信息才能进行准确评估:${missingFields.map(f => FIELD_LABELS[f] || f).join('、')}`,
|
||||
missingFields,
|
||||
missingFieldLabels: missingFields.map(f => FIELD_LABELS[f] || f),
|
||||
collectedFields,
|
||||
_ui_hint: '提示用户提供缺失信息',
|
||||
};
|
||||
}
|
||||
|
||||
// ── Step 4: Run assessment expert ──
|
||||
if (!this.assessmentExpert) {
|
||||
return {
|
||||
status: 'assessment_error',
|
||||
message: '评估服务暂时不可用,请稍后再试。',
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`[Tool:run_professional_assessment] Payment verified, info complete. Running assessment...`);
|
||||
|
||||
let assessmentResult: string;
|
||||
try {
|
||||
assessmentResult = await this.assessmentExpert.executeAssessment({
|
||||
userInfo,
|
||||
targetCategories,
|
||||
conversationContext,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Tool:run_professional_assessment] Assessment expert failed:', error);
|
||||
return {
|
||||
status: 'assessment_error',
|
||||
message: '评估过程中出现错误,请稍后重试。如问题持续,请联系客服。',
|
||||
};
|
||||
}
|
||||
|
||||
// Parse the JSON result from assessment expert
|
||||
let parsedResult: unknown;
|
||||
try {
|
||||
parsedResult = typeof assessmentResult === 'string'
|
||||
? JSON.parse(assessmentResult)
|
||||
: assessmentResult;
|
||||
} catch {
|
||||
console.error('[Tool:run_professional_assessment] Failed to parse assessment result');
|
||||
parsedResult = null;
|
||||
}
|
||||
|
||||
// ── Step 5: Persist as artifact ──
|
||||
const contentToSave = parsedResult ? JSON.stringify(parsedResult) : assessmentResult;
|
||||
const title = '移民资格评估报告';
|
||||
|
||||
if (existingReport) {
|
||||
// Update existing report (re-assessment after 30+ days)
|
||||
existingReport.content = contentToSave;
|
||||
existingReport.conversationId = context.conversationId;
|
||||
await this.artifactRepo.save(existingReport);
|
||||
|
||||
console.log(`[Tool:run_professional_assessment] Updated existing report: ${existingReport.id}`);
|
||||
|
||||
return {
|
||||
status: 'completed',
|
||||
assessment: parsedResult || assessmentResult,
|
||||
artifactId: existingReport.id,
|
||||
isReassessment: true,
|
||||
paidOrderId: paidAssessmentOrder.id,
|
||||
assessedAt: new Date().toISOString(),
|
||||
_ui_hint: '前端已渲染评估报告卡片。评估完成后建议自动创建材料清单和时间线。',
|
||||
};
|
||||
}
|
||||
|
||||
const artifact = this.artifactRepo.create({
|
||||
tenantId,
|
||||
userId,
|
||||
conversationId: context.conversationId,
|
||||
artifactType: 'assessment_report',
|
||||
title,
|
||||
documentType: 'report',
|
||||
content: contentToSave,
|
||||
});
|
||||
const saved = await this.artifactRepo.save(artifact);
|
||||
|
||||
console.log(`[Tool:run_professional_assessment] Assessment saved: ${saved.id}`);
|
||||
|
||||
return {
|
||||
status: 'completed',
|
||||
assessment: parsedResult || assessmentResult,
|
||||
artifactId: saved.id,
|
||||
isReassessment: false,
|
||||
paidOrderId: paidAssessmentOrder.id,
|
||||
assessedAt: new Date().toISOString(),
|
||||
_ui_hint: '前端已渲染评估报告卡片。评估完成后建议自动创建材料清单和时间线。',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -398,6 +398,108 @@ function ToolCallResult({
|
|||
}
|
||||
}
|
||||
|
||||
if (toolCall.name === 'run_professional_assessment') {
|
||||
const result = toolCall.result as {
|
||||
status?: string;
|
||||
message?: string;
|
||||
assessment?: unknown;
|
||||
existingReport?: unknown;
|
||||
artifactId?: string;
|
||||
assessedAt?: string;
|
||||
isReassessment?: boolean;
|
||||
missingFields?: string[];
|
||||
missingFieldLabels?: string[];
|
||||
collectedFields?: string[];
|
||||
hasPendingOrder?: boolean;
|
||||
pendingOrderId?: string;
|
||||
};
|
||||
|
||||
if (result.status === 'completed' || result.status === 'already_assessed') {
|
||||
const assessmentData = result.status === 'completed'
|
||||
? result.assessment
|
||||
: result.existingReport;
|
||||
const data = typeof assessmentData === 'string'
|
||||
? (() => { try { return JSON.parse(assessmentData); } catch { return null; } })()
|
||||
: assessmentData;
|
||||
|
||||
return (
|
||||
<div className="mt-3">
|
||||
{result.status === 'already_assessed' && (
|
||||
<div className="mb-2 px-3 py-2 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-xs text-blue-700">
|
||||
已有评估报告({result.assessedAt ? new Date(result.assessedAt).toLocaleDateString('zh-CN') : ''})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{result.isReassessment && (
|
||||
<div className="mb-2 px-3 py-2 bg-green-50 border border-green-200 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
<span className="text-xs text-green-700">评估报告已更新</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data?.assessments && Array.isArray(data.assessments) ? (
|
||||
<AssessmentResultCard data={data} />
|
||||
) : (
|
||||
<div className="p-3 bg-secondary-50 rounded-lg border border-secondary-200">
|
||||
<p className="text-sm text-secondary-600">{result.message || '评估已完成'}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (result.status === 'payment_required') {
|
||||
return (
|
||||
<div className="mt-3 p-3 bg-amber-50 rounded-lg border border-amber-200">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<ShoppingBag className="w-4 h-4 text-amber-600" />
|
||||
<span className="text-sm font-medium text-amber-800">需要支付评估费用</span>
|
||||
</div>
|
||||
<p className="text-xs text-amber-700">{result.message}</p>
|
||||
{result.hasPendingOrder && (
|
||||
<p className="mt-1 text-xs text-amber-600">
|
||||
您有一个待支付的订单,请完成支付后重试。
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (result.status === 'info_incomplete') {
|
||||
return (
|
||||
<div className="mt-3 p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<AlertCircle className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-sm font-medium text-blue-800">信息待补充</span>
|
||||
</div>
|
||||
<p className="text-xs text-blue-700 mb-2">{result.message}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{result.missingFieldLabels?.map((label, i) => (
|
||||
<span key={i} className="px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-full">
|
||||
{label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// assessment_error / payment_service_unavailable / other
|
||||
return (
|
||||
<div className="mt-3 p-3 bg-red-50 rounded-lg border border-red-200">
|
||||
<div className="flex items-center gap-2">
|
||||
<XCircle className="w-4 h-4 text-red-500" />
|
||||
<span className="text-sm text-red-600">{result.message || '评估服务暂时不可用'}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (toolCall.name === 'cancel_order') {
|
||||
const result = toolCall.result as {
|
||||
success?: boolean;
|
||||
|
|
|
|||
Loading…
Reference in New Issue