feat: add server selection and order flow after recommendation
- Add 'selecting' and 'ordering' status to ServerSession - Add lastRecommendation field to store recommendation results - Keep session alive after recommendation (don't delete immediately) - Add selection pattern matching (1번, 첫번째, 1번 선택 등) - Add order confirmation message with inline buttons - Add server_order/server_cancel callback handlers - Add ServerOrderKeyboardData type for button data Flow: recommend → select number → confirm with buttons → order/cancel Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
* - 대화형 서버 추천 상담
|
||||
* - 세션 기반 정보 수집
|
||||
* - 충분한 정보 수집 시 자동 추천
|
||||
* - 추천 후 사용자 선택 및 주문 흐름
|
||||
* - Brave Search / Context7 도구로 최신 트렌드 반영
|
||||
*/
|
||||
|
||||
@@ -390,6 +391,56 @@ export async function processServerConsultation(
|
||||
status: session.status
|
||||
});
|
||||
|
||||
// 선택 단계 처리
|
||||
if (session.status === 'selecting' && session.lastRecommendation) {
|
||||
const selectionMatch = userMessage.match(/(\d+)(?:번|번째)?|첫\s*번째|두\s*번째|세\s*번째/);
|
||||
|
||||
if (selectionMatch) {
|
||||
let selectedIndex = -1;
|
||||
|
||||
// 숫자 추출
|
||||
if (selectionMatch[1]) {
|
||||
selectedIndex = parseInt(selectionMatch[1], 10) - 1;
|
||||
} else if (userMessage.includes('첫')) {
|
||||
selectedIndex = 0;
|
||||
} else if (userMessage.includes('두')) {
|
||||
selectedIndex = 1;
|
||||
} else if (userMessage.includes('세')) {
|
||||
selectedIndex = 2;
|
||||
}
|
||||
|
||||
// 유효성 검증
|
||||
if (selectedIndex >= 0 && selectedIndex < session.lastRecommendation.recommendations.length) {
|
||||
const selected = session.lastRecommendation.recommendations[selectedIndex];
|
||||
|
||||
// Mark session as ordering
|
||||
session.status = 'ordering';
|
||||
await saveServerSession(env.SESSION_KV, session.telegramUserId, session);
|
||||
|
||||
// 주문 확인 메시지 생성 (인라인 버튼 포함)
|
||||
const keyboardData = JSON.stringify({
|
||||
type: 'server_order',
|
||||
userId: session.telegramUserId,
|
||||
index: selectedIndex,
|
||||
plan: selected.plan_name
|
||||
});
|
||||
|
||||
return `🖥️ ${selected.plan_name} 신청 확인\n\n` +
|
||||
`• 제공사: ${selected.provider}\n` +
|
||||
`• 스펙: ${selected.specs.vcpu}vCPU / ${selected.specs.ram_gb}GB / ${selected.specs.storage_gb}GB\n` +
|
||||
`• 리전: ${selected.region.name} (${selected.region.code})\n` +
|
||||
`• 가격: ₩${selected.price.monthly_krw.toLocaleString()}/월\n\n` +
|
||||
`신청하시겠습니까?\n\n` +
|
||||
`__KEYBOARD__${keyboardData}__END__`;
|
||||
} else {
|
||||
return `번호를 다시 확인해주세요. 1번부터 ${session.lastRecommendation.recommendations.length}번 중에서 선택해주세요.`;
|
||||
}
|
||||
}
|
||||
|
||||
// 선택하지 않고 다른 질문을 한 경우
|
||||
return '서버 번호를 선택해주세요. (예: 1번)\n또는 "취소"라고 말씀하시면 처음부터 다시 시작합니다.';
|
||||
}
|
||||
|
||||
// Add user message to history
|
||||
session.messages.push({ role: 'user', content: userMessage });
|
||||
|
||||
@@ -410,7 +461,7 @@ export async function processServerConsultation(
|
||||
// Call recommendation API
|
||||
logger.info('추천 API 호출', { collectedInfo: session.collectedInfo });
|
||||
|
||||
const { executeServerAction } = await import('./tools/server-tool');
|
||||
const { executeServerAction, getRecommendationData } = await import('./tools/server-tool');
|
||||
|
||||
const techStack = session.collectedInfo.useCase
|
||||
? inferTechStack(session.collectedInfo.useCase)
|
||||
@@ -420,8 +471,7 @@ export async function processServerConsultation(
|
||||
? inferExpectedUsers(session.collectedInfo.scale)
|
||||
: 100;
|
||||
|
||||
const recommendation = await executeServerAction(
|
||||
'recommend',
|
||||
const recommendationData = await getRecommendationData(
|
||||
{
|
||||
tech_stack: techStack,
|
||||
expected_users: expectedUsers,
|
||||
@@ -430,15 +480,60 @@ export async function processServerConsultation(
|
||||
budget_limit: session.collectedInfo.budgetLimit,
|
||||
lang: 'ko',
|
||||
},
|
||||
env,
|
||||
session.telegramUserId
|
||||
env
|
||||
);
|
||||
|
||||
// Mark session as completed and delete
|
||||
session.status = 'completed';
|
||||
await deleteServerSession(env.SESSION_KV, session.telegramUserId);
|
||||
// 추천 결과를 세션에 저장
|
||||
if (recommendationData && recommendationData.recommendations && recommendationData.recommendations.length > 0) {
|
||||
session.lastRecommendation = {
|
||||
recommendations: recommendationData.recommendations.slice(0, 3).map(rec => ({
|
||||
plan_name: rec.server.instance_name,
|
||||
provider: rec.server.provider_name,
|
||||
specs: {
|
||||
vcpu: rec.server.vcpu,
|
||||
ram_gb: rec.server.memory_gb,
|
||||
storage_gb: rec.server.storage_gb
|
||||
},
|
||||
region: {
|
||||
code: rec.server.region_code,
|
||||
name: rec.server.region_name
|
||||
},
|
||||
price: {
|
||||
monthly_krw: Math.round(rec.server.monthly_price),
|
||||
bandwidth_tb: rec.server.transfer_tb
|
||||
},
|
||||
score: rec.score,
|
||||
max_users: rec.estimated_capacity?.max_concurrent_users || 0
|
||||
})),
|
||||
createdAt: Date.now()
|
||||
};
|
||||
|
||||
return `${aiResult.message}\n\n${recommendation}`;
|
||||
// Mark session as selecting (사용자 선택 대기)
|
||||
session.status = 'selecting';
|
||||
await saveServerSession(env.SESSION_KV, session.telegramUserId, session);
|
||||
|
||||
const formattedRecommendation = await executeServerAction(
|
||||
'recommend',
|
||||
{
|
||||
tech_stack: techStack,
|
||||
expected_users: expectedUsers,
|
||||
use_case: session.collectedInfo.useCase || '웹 서비스',
|
||||
region_preference: session.collectedInfo.regionPreference,
|
||||
budget_limit: session.collectedInfo.budgetLimit,
|
||||
lang: 'ko',
|
||||
},
|
||||
env,
|
||||
session.telegramUserId
|
||||
);
|
||||
|
||||
return `${aiResult.message}\n\n${formattedRecommendation}\n\n💡 원하는 서버 번호를 선택해주세요 (예: 1번)`;
|
||||
} else {
|
||||
// 추천 결과 없음 - 세션 삭제
|
||||
session.status = 'completed';
|
||||
await deleteServerSession(env.SESSION_KV, session.telegramUserId);
|
||||
|
||||
return `${aiResult.message}\n\n조건에 맞는 서버를 찾지 못했습니다.`;
|
||||
}
|
||||
} else {
|
||||
// Continue gathering information
|
||||
session.status = 'gathering';
|
||||
|
||||
Reference in New Issue
Block a user