fix: server recommendation issues and __DIRECT__ tag visibility
- Fix USD price display: all prices now show in KRW (₩) - Add Korea region auto-detection: extracts region preference from user messages - Fix low-spec recommendation for high-performance requirements: - Add extractTechStack() to detect PostgreSQL, Redis, MongoDB keywords - Enhance inferExpectedUsers() to consider tech stack complexity - SaaS/B2B services now recommend 4GB+ RAM servers - Fix __DIRECT__ tag appearing in output: - Reorder message concatenation in server-agent.ts - Add stripping logic in conversation-service.ts and api.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -284,8 +284,8 @@ async function handleDepositDeduct(request: Request, env: Env): Promise<Response
|
||||
* @returns JSON response with AI response
|
||||
*/
|
||||
async function handleTestApi(request: Request, env: Env): Promise<Response> {
|
||||
// 프로덕션 환경에서는 비활성화
|
||||
if (env.ENVIRONMENT === 'production') {
|
||||
// 개발/테스트 환경에서만 활성화 (명시적 설정 필수)
|
||||
if (env.ENVIRONMENT !== 'development' && env.ENVIRONMENT !== 'test') {
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
@@ -310,8 +310,8 @@ async function handleTestApi(request: Request, env: Env): Promise<Response> {
|
||||
|
||||
const body = parseResult.data;
|
||||
|
||||
// 간단한 인증
|
||||
if (body.secret !== env.WEBHOOK_SECRET) {
|
||||
// 인증 (Timing-safe comparison 사용)
|
||||
if (!timingSafeEqual(body.secret || '', env.WEBHOOK_SECRET || '')) {
|
||||
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
@@ -339,6 +339,12 @@ async function handleTestApi(request: Request, env: Env): Promise<Response> {
|
||||
// 2. AI 응답 생성
|
||||
responseText = await generateAIResponse(env, userId, chatIdStr, body.text, telegramUserId);
|
||||
|
||||
// __DIRECT__ 마커 제거 (AI가 그대로 전달한 경우 대비)
|
||||
if (responseText.includes('__DIRECT__')) {
|
||||
const directIndex = responseText.indexOf('__DIRECT__');
|
||||
responseText = responseText.slice(directIndex + '__DIRECT__'.length).trim();
|
||||
}
|
||||
|
||||
// 3. 봇 응답 버퍼에 추가
|
||||
await addToBuffer(env.DB, userId, chatIdStr, 'bot', responseText);
|
||||
|
||||
@@ -417,6 +423,209 @@ async function handleChatApi(request: Request, env: Env): Promise<Response> {
|
||||
|
||||
let responseText: string;
|
||||
|
||||
// 서버 삭제 확인 처리 (텍스트 기반)
|
||||
if (body.message.trim() === '삭제') {
|
||||
const deleteSessionKey = `delete_confirm:${telegramUserId}`;
|
||||
const deleteSessionData = await env.SESSION_KV.get(deleteSessionKey);
|
||||
|
||||
if (deleteSessionData) {
|
||||
try {
|
||||
const { orderId } = JSON.parse(deleteSessionData);
|
||||
|
||||
// Import and execute server deletion
|
||||
const { executeServerDelete } = await import('../tools/server-tool');
|
||||
const result = await executeServerDelete(orderId, telegramUserId, env);
|
||||
|
||||
// Delete session after execution
|
||||
await env.SESSION_KV.delete(deleteSessionKey);
|
||||
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: result.message,
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Chat API - 서버 삭제 처리 오류', toError(error));
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: '🚫 서버 삭제 중 오류가 발생했습니다. 다시 시도해주세요.',
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 서버 삭제 취소 처리 (다른 메시지 입력 시)
|
||||
const deleteSessionKey = `delete_confirm:${telegramUserId}`;
|
||||
const deleteSessionData = await env.SESSION_KV.get(deleteSessionKey);
|
||||
|
||||
if (deleteSessionData && body.message.trim() !== '삭제') {
|
||||
try {
|
||||
const { label } = JSON.parse(deleteSessionData);
|
||||
await env.SESSION_KV.delete(deleteSessionKey);
|
||||
|
||||
// Don't show cancellation message if it's a command (let command handler process it)
|
||||
if (!body.message.startsWith('/')) {
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: `⏹️ 서버 삭제가 취소되었습니다.\n\n삭제하려던 서버: ${label}`,
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Chat API - 삭제 세션 취소 오류', toError(error));
|
||||
}
|
||||
}
|
||||
|
||||
// 서버 신청 확인 처리 (텍스트 기반) - Queue 기반
|
||||
if (body.message.trim() === '신청') {
|
||||
const orderSessionKey = `server_order_confirm:${telegramUserId}`;
|
||||
logger.info('신청 세션 확인', { orderSessionKey, telegramUserId });
|
||||
const orderSessionData = await env.SESSION_KV.get(orderSessionKey);
|
||||
logger.info('신청 세션 데이터', { found: !!orderSessionData, data: orderSessionData?.slice(0, 100) });
|
||||
|
||||
if (orderSessionData) {
|
||||
try {
|
||||
const orderData = JSON.parse(orderSessionData);
|
||||
|
||||
// 1. 서버 세션에서 가격 정보 가져오기
|
||||
const { getServerSession, deleteServerSession } = await import('../server-agent');
|
||||
const session = await getServerSession(env.DB, telegramUserId);
|
||||
|
||||
if (!session || !session.lastRecommendation) {
|
||||
await env.SESSION_KV.delete(orderSessionKey);
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: '❌ 세션이 만료되었습니다.\n다시 "서버 추천"을 시작해주세요.',
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
}
|
||||
|
||||
const selected = session.lastRecommendation.recommendations[orderData.index];
|
||||
if (!selected) {
|
||||
await env.SESSION_KV.delete(orderSessionKey);
|
||||
await deleteServerSession(env.DB, telegramUserId);
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: '❌ 선택한 서버를 찾을 수 없습니다.',
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
}
|
||||
|
||||
const price = selected.price?.monthly_krw || 0;
|
||||
|
||||
// 2. 잔액 확인
|
||||
const deposit = await env.DB.prepare(
|
||||
'SELECT balance FROM user_deposits WHERE user_id = ?'
|
||||
).bind(userId).first<{ balance: number }>();
|
||||
|
||||
if (!deposit || deposit.balance < price) {
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response:
|
||||
`❌ 잔액이 부족합니다.\n\n` +
|
||||
`• 서버 가격: ${price.toLocaleString()}원/월\n` +
|
||||
`• 현재 잔액: ${(deposit?.balance || 0).toLocaleString()}원\n` +
|
||||
`• 부족 금액: ${(price - (deposit?.balance || 0)).toLocaleString()}원\n\n` +
|
||||
`잔액을 충전 후 다시 시도해주세요.`,
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Queue 확인
|
||||
if (!env.SERVER_PROVISION_QUEUE) {
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: '❌ 서버 프로비저닝 시스템이 준비되지 않았습니다.',
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
}
|
||||
|
||||
// 4. 주문 생성 및 Queue 전송
|
||||
const { createServerOrder, sendProvisionMessage } = await import('../server-provision');
|
||||
|
||||
const orderId = await createServerOrder(
|
||||
env.DB,
|
||||
userId,
|
||||
telegramUserId,
|
||||
selected.pricing_id,
|
||||
selected.region.code,
|
||||
'anvil',
|
||||
price,
|
||||
`${selected.plan_name} - ${orderData.label || session.collectedInfo?.useCase || 'server'}`
|
||||
);
|
||||
|
||||
await sendProvisionMessage(env.SERVER_PROVISION_QUEUE, orderId, userId, telegramUserId);
|
||||
|
||||
// 5. 세션 정리
|
||||
await env.SESSION_KV.delete(orderSessionKey);
|
||||
await deleteServerSession(env.DB, telegramUserId);
|
||||
|
||||
// 6. 즉시 응답
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response:
|
||||
`📋 <b>서버 주문 접수 완료!</b> (주문 #${orderId})\n\n` +
|
||||
`• 서버: ${selected.plan_name}\n` +
|
||||
`• 리전: ${selected.region.name} (${selected.region.code})\n` +
|
||||
`• 가격: ${price.toLocaleString()}원/월\n\n` +
|
||||
`⏳ 서버를 생성하고 있습니다... (1-2분 소요)\n` +
|
||||
`완료되면 메시지로 알려드릴게요.`,
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Chat API - 서버 신청 처리 오류', toError(error));
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: '🚫 서버 신청 중 오류가 발생했습니다. 다시 시도해주세요.',
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 서버 신청 취소 처리 (다른 메시지 입력 시)
|
||||
const orderSessionKey = `server_order_confirm:${telegramUserId}`;
|
||||
const orderSessionData = await env.SESSION_KV.get(orderSessionKey);
|
||||
|
||||
if (orderSessionData && body.message.trim() !== '신청') {
|
||||
try {
|
||||
const { plan } = JSON.parse(orderSessionData);
|
||||
await env.SESSION_KV.delete(orderSessionKey);
|
||||
|
||||
// Don't show cancellation message if it's a command
|
||||
if (!body.message.startsWith('/')) {
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: `⏹️ 서버 신청이 취소되었습니다.\n\n신청하려던 서버: ${plan}`,
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Chat API - 신청 세션 취소 오류', toError(error));
|
||||
}
|
||||
}
|
||||
|
||||
// 명령어 처리
|
||||
if (body.message.startsWith('/')) {
|
||||
const [command, ...argParts] = body.message.split(' ');
|
||||
@@ -429,6 +638,12 @@ async function handleChatApi(request: Request, env: Env): Promise<Response> {
|
||||
// 2. AI 응답 생성
|
||||
responseText = await generateAIResponse(env, userId, chatIdStr, body.message, telegramUserId);
|
||||
|
||||
// __DIRECT__ 마커 제거 (AI가 그대로 전달한 경우 대비)
|
||||
if (responseText.includes('__DIRECT__')) {
|
||||
const directIndex = responseText.indexOf('__DIRECT__');
|
||||
responseText = responseText.slice(directIndex + '__DIRECT__'.length).trim();
|
||||
}
|
||||
|
||||
// 3. 봇 응답 버퍼에 추가
|
||||
await addToBuffer(env.DB, userId, chatIdStr, 'bot', responseText);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user