summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx131
-rw-r--r--app/[lng]/partners/(partners)/sales-force-test/AF_poc.html118
-rw-r--r--app/[lng]/test/agentforce-poc-faq/page.tsx62
-rw-r--r--app/[lng]/test/agentforce-poc-summary/page.tsx291
4 files changed, 353 insertions, 249 deletions
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx
deleted file mode 100644
index a3507dd0..00000000
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
-import { ChevronDown, ChevronUp } from "lucide-react"
-
-export function TruncatedText({
- text,
- maxLength = 50,
- showTooltip = true
-}: {
- text: string | null
- maxLength?: number
- showTooltip?: boolean
-}) {
- if (!text) return <span className="text-muted-foreground">-</span>
-
- if (text.length <= maxLength) {
- return <span>{text}</span>
- }
-
- const truncated = text.slice(0, maxLength) + "..."
-
- if (!showTooltip) {
- return <span>{truncated}</span>
- }
-
- return (
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger asChild>
- <span className="cursor-help border-b border-dotted border-gray-400">
- {truncated}
- </span>
- </TooltipTrigger>
- <TooltipContent className="max-w-xs">
- <p className="whitespace-pre-wrap">{text}</p>
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- )
-}
-
-export function ExpandableText({
- text,
- maxLength = 100,
- className = ""
-}: {
- text: string | null
- maxLength?: number
- className?: string
-}) {
- const [isExpanded, setIsExpanded] = useState(false)
-
- if (!text) return <span className="text-muted-foreground">-</span>
-
- if (text.length <= maxLength) {
- return <span className={className}>{text}</span>
- }
-
- return (
- <Collapsible open={isExpanded} onOpenChange={setIsExpanded}>
- <div className={className}>
- <CollapsibleTrigger asChild>
- <button className="text-left w-full group">
- <span className="whitespace-pre-wrap">
- {isExpanded ? text : text.slice(0, maxLength) + "..."}
- </span>
- <span className="inline-flex items-center ml-2 text-blue-600 hover:text-blue-800">
- {isExpanded ? (
- <>
- <ChevronUp className="w-3 h-3" />
- <span className="text-xs ml-1">접기</span>
- </>
- ) : (
- <>
- <ChevronDown className="w-3 h-3" />
- <span className="text-xs ml-1">더보기</span>
- </>
- )}
- </span>
- </button>
- </CollapsibleTrigger>
- </div>
- </Collapsible>
- )
-}
-
-export function AddressDisplay({
- address,
- addressEng,
- postalCode,
- addressDetail
-}: {
- address: string | null
- addressEng: string | null
- postalCode: string | null
- addressDetail: string | null
-}) {
- const hasAnyAddress = address || addressEng || postalCode || addressDetail
-
- if (!hasAnyAddress) {
- return <span className="text-muted-foreground">-</span>
- }
-
- return (
- <div className="space-y-1">
- {postalCode && (
- <div className="text-xs text-muted-foreground">
- 우편번호: {postalCode}
- </div>
- )}
- {address && (
- <div className="font-medium break-words">
- {address}
- </div>
- )}
- {addressDetail && (
- <div className="text-sm text-muted-foreground break-words">
- {addressDetail}
- </div>
- )}
- {addressEng && (
- <div className="text-sm text-muted-foreground break-words italic">
- {addressEng}
- </div>
- )}
- </div>
- )
-} \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/sales-force-test/AF_poc.html b/app/[lng]/partners/(partners)/sales-force-test/AF_poc.html
deleted file mode 100644
index 60e047ed..00000000
--- a/app/[lng]/partners/(partners)/sales-force-test/AF_poc.html
+++ /dev/null
@@ -1,118 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Salesforce LWC Loader</title>
-</head>
-<body>
- <script src="https://connect-flow-8014--ps.sandbox.lightning.force.com/lightning/lightning.out.js"></script>
- <script>
- const myApiUrl = 'https://pyheroku-d21d18e4f257.herokuapp.com/api/getToken';
-
- const salesforceUrl = 'https://connect-flow-8014--ps.sandbox.lightning.force.com/';
- const appName = 'c:zzChatBot_Aura';
- const componentName = 'c:sj_Chatbot';
-
- const lwcAttributes = {
- isDarkMode: false,
- chatbot_width: '350px',
- chatbot_height: '600px'
- };
-
- // 에러 메시지를 화면에 표시하는 함수
- function displayError(message) {
- const errorDiv = document.getElementById('errorMessage');
- errorDiv.textContent = message;
- errorDiv.style.display = 'block';
- }
-
- const salesforceApiUrl = 'https://connect-flow-8014--ps.sandbox.my.salesforce.com/services/apexrest/summarizeChat/';
-
- const chatData = {
- chatlog: `[
- {
- "sender": "SHI",
- "message": "최근 입고된 발전기 제어반 견적서에서 납기 조건 누락을 확인했습니다.
- 계약 전 꼭 납기 포함한 수정본 제출 부탁드립니다.",
- },
- {
- "sender": "Partner",
- "message": "네, 누락 사항 확인 후 납기 조건 명시하여 오늘 중으로 재제출 하겠습니다.",
- },
- {
- "sender": "SHI",
- "message": "감사합니다. 납기 조건은 계약 핵심 조항입니다. 반드시 반영해 주세요.",
- }
- ]
- `
- };
-
- // API를 호출하여 토큰을 가져오고 LWC를 로드하는 로직
- async function getToken(){
- let accessToken = '';
- await fetch(myApiUrl, { method: 'POST' }) // 프록시 서버에 POST 요청을 보냅니다.
- .then(response => {
- if (!response.ok) {
- return response.json().then(errorData => {
- throw new Error(`백엔드 API 에러 (Status: ${response.status}): ${JSON.stringify(errorData)}`);
- });
- }
- return response.json();
- })
- .then(data => {
- accessToken = data.access_token;
- if (!accessToken) {
- throw new Error("응답 데이터에 access_token이 없습니다.");
- }
-
- console.log("Heroku 프록시를 통해 안전하게 토큰을 받았습니다:", accessToken);
- })
- .catch(error => {
- console.error('전체 프로세스 호출 실패:', error);
- displayError(`오류가 발생했습니다: ${error.message}`);
- });
-
- return accessToken;
- }
-
- async function getChatSummary(accessToken){
- console.log("토큰")
- console.log(accessToken)
- fetch(salesforceApiUrl, {
- method: 'POST',
- headers: {
- // Bearer 뒤에 공백이 중요합니다.
- 'Authorization': `Bearer ${accessToken}`,
- 'Content-Type': 'application/json'
- },
- // JavaScript 객체를 JSON 문자열로 변환합니다.
- body: JSON.stringify(chatData)
- })
- .then(response => {
- if (!response.ok) {
- // API 호출이 실패하면 에러를 발생시킵니다.
- throw new Error(`Salesforce API 에러: ${response.status}`);
- }
- return response.json(); // 응답을 JSON 형태로 파싱합니다.
- })
- .then(result => {
- // 성공적인 응답을 콘솔에 출력합니다.
- console.log('Salesforce API 응답:', result);
- })
- .catch(error => {
- // API 호출 중 발생한 에러를 처리합니다.
- console.error('Salesforce API 호출 실패:', error);
- displayError(`Salesforce API 호출 중 오류가 발생했습니다: ${error.message}`);
- })
- }
-
- async function getMain(){
- accessToken = await getToken();
- await getChatSummary(accessToken);
- }
-
- getMain();
- </script>
-</body>
-</html> \ No newline at end of file
diff --git a/app/[lng]/test/agentforce-poc-faq/page.tsx b/app/[lng]/test/agentforce-poc-faq/page.tsx
new file mode 100644
index 00000000..493828d6
--- /dev/null
+++ b/app/[lng]/test/agentforce-poc-faq/page.tsx
@@ -0,0 +1,62 @@
+'use client'
+
+import { useState, useRef } from 'react'
+
+export default function AgentforcePocPage() {
+ const [isLoading, setIsLoading] = useState(true)
+ const iframeRef = useRef<HTMLIFrameElement>(null)
+
+ const handleIframeLoad = () => {
+ setIsLoading(false)
+ }
+
+ const refreshIframe = () => {
+ if (iframeRef.current) {
+ setIsLoading(true)
+ iframeRef.current.src = iframeRef.current.src
+ }
+ }
+
+ return (
+ <div className="min-h-screen bg-gray-50 p-4">
+ <div className="max-w-7xl mx-auto">
+ <div className="mb-6 flex items-center justify-between">
+ <h1 className="text-3xl font-bold text-gray-900">
+ Agentforce POC
+ </h1>
+ <button
+ onClick={refreshIframe}
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
+ >
+ 새로고침
+ </button>
+ </div>
+
+ <div className="bg-white rounded-lg shadow-lg overflow-hidden">
+ {isLoading && (
+ <div className="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center z-10">
+ <div className="text-center">
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
+ <p className="text-gray-600">로딩 중...</p>
+ </div>
+ </div>
+ )}
+
+ <div className="relative" style={{ height: '800px' }}>
+ <iframe
+ ref={iframeRef}
+ src="/agentforce-poc.html"
+ title="Agentforce POC"
+ className="w-full h-full border-0"
+ onLoad={handleIframeLoad}
+ />
+ </div>
+ </div>
+
+ <div className="mt-4 text-sm text-gray-500 text-center">
+ 이 페이지는 Salesforce Agentforce POC를 iframe으로 표시합니다.
+ </div>
+ </div>
+ </div>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/test/agentforce-poc-summary/page.tsx b/app/[lng]/test/agentforce-poc-summary/page.tsx
new file mode 100644
index 00000000..fc504cd3
--- /dev/null
+++ b/app/[lng]/test/agentforce-poc-summary/page.tsx
@@ -0,0 +1,291 @@
+"use client";
+
+import * as React from "react";
+import { useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Plus, Trash2, Send, Loader2 } from "lucide-react";
+import { toast } from "sonner";
+
+interface ChatMessage {
+ id: string;
+ sender: "SHI" | "Partner";
+ message: string;
+}
+
+interface SummaryResponse {
+ issue: string;
+ request: string;
+ summary: string;
+}
+
+export default function AgentForcePOCSummaryPage() {
+ const [messages, setMessages] = useState<ChatMessage[]>([
+ {
+ id: "1",
+ sender: "SHI",
+ message: "최근 입고된 발전기 제어반 견적서에서 납기 조건 누락을 확인했습니다. 계약 전 꼭 납기 포함한 수정본 제출 부탁드립니다."
+ },
+ {
+ id: "2",
+ sender: "Partner",
+ message: "네, 누락 사항 확인 후 납기 조건 명시하여 오늘 중으로 재제출 하겠습니다."
+ },
+ {
+ id: "3",
+ sender: "SHI",
+ message: "감사합니다. 납기 조건은 계약 핵심 조항입니다. 반드시 반영해 주세요."
+ }
+ ]);
+
+ const [summaryResult, setSummaryResult] = useState<SummaryResponse | null>(null);
+ const [isLoading, setIsLoading] = useState(false);
+
+ // 새 메시지 추가
+ const addMessage = () => {
+ const newMessage: ChatMessage = {
+ id: Date.now().toString(),
+ sender: "SHI",
+ message: ""
+ };
+ setMessages([...messages, newMessage]);
+ };
+
+ // 메시지 삭제
+ const removeMessage = (id: string) => {
+ if (messages.length > 1) {
+ setMessages(messages.filter(msg => msg.id !== id));
+ } else {
+ toast.error("최소 1개의 메시지는 유지해야 합니다.");
+ }
+ };
+
+ // 메시지 업데이트
+ const updateMessage = (id: string, field: keyof ChatMessage, value: string) => {
+ setMessages(messages.map(msg =>
+ msg.id === id ? { ...msg, [field]: value } : msg
+ ));
+ };
+
+ // 요약 API 호출
+ const handleSummarize = async () => {
+ // 유효성 검사
+ const emptyMessages = messages.filter(msg => !msg.message.trim());
+ if (emptyMessages.length > 0) {
+ toast.error("모든 메시지를 입력해주세요.");
+ return;
+ }
+
+ setIsLoading(true);
+ try {
+ // 토큰 가져오기
+ const tokenResponse = await fetch('https://pyheroku-d21d18e4f257.herokuapp.com/api/getToken', {
+ method: 'POST'
+ });
+
+ if (!tokenResponse.ok) {
+ throw new Error('토큰을 가져오는데 실패했습니다.');
+ }
+
+ const tokenData = await tokenResponse.json();
+ const accessToken = tokenData.access_token;
+
+ if (!accessToken) {
+ throw new Error('토큰이 없습니다.');
+ }
+
+ // 요약 API 호출
+ const summaryResponse = await fetch(`https://connect-flow-8014--ps.sandbox.my.salesforce.com/services/apexrest/summarizeChat/`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${accessToken}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ chatlog: JSON.stringify(messages.map(msg => ({
+ sender: msg.sender,
+ message: msg.message
+ })))
+ })
+ });
+
+ if (!summaryResponse.ok) {
+ throw new Error(`API 호출 실패: ${summaryResponse.status}`);
+ }
+
+ const result = await summaryResponse.json();
+ setSummaryResult(result);
+ toast.success("요약이 완료되었습니다.");
+ } catch (error) {
+ console.error('요약 API 호출 실패:', error);
+ toast.error(`요약 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+ <div className="container mx-auto py-8 space-y-6">
+ <div className="text-center space-y-2">
+ <h1 className="text-3xl font-bold">AgentForce POC 요약 테스트</h1>
+ <p className="text-muted-foreground">
+ 대화 기록을 입력하고 AI 요약 기능을 테스트해보세요
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+ {/* 입력 섹션 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="flex items-center justify-between">
+ 대화 기록 입력
+ <Button onClick={addMessage} size="sm" variant="outline">
+ <Plus className="h-4 w-4 mr-2" />
+ 메시지 추가
+ </Button>
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ {messages.map((message, index) => (
+ <div key={message.id} className="space-y-2 p-4 border rounded-lg">
+ <div className="flex items-center justify-between">
+ <div className="flex items-center gap-2">
+ <Badge variant={message.sender === "SHI" ? "default" : "secondary"}>
+ {message.sender}
+ </Badge>
+ <span className="text-sm text-muted-foreground">메시지 {index + 1}</span>
+ </div>
+ <Button
+ onClick={() => removeMessage(message.id)}
+ size="sm"
+ variant="ghost"
+ className="text-red-500 hover:text-red-700"
+ >
+ <Trash2 className="h-4 w-4" />
+ </Button>
+ </div>
+
+ <div className="space-y-2">
+ <div className="flex gap-2">
+ <Button
+ size="sm"
+ variant={message.sender === "SHI" ? "default" : "outline"}
+ onClick={() => updateMessage(message.id, "sender", "SHI")}
+ >
+ SHI
+ </Button>
+ <Button
+ size="sm"
+ variant={message.sender === "Partner" ? "default" : "outline"}
+ onClick={() => updateMessage(message.id, "sender", "Partner")}
+ >
+ Partner
+ </Button>
+ </div>
+
+ <Textarea
+ value={message.message}
+ onChange={(e) => updateMessage(message.id, "message", e.target.value)}
+ placeholder="메시지를 입력하세요..."
+ className="min-h-20"
+ />
+ </div>
+ </div>
+ ))}
+
+ <Button
+ onClick={handleSummarize}
+ disabled={isLoading}
+ className="w-full"
+ >
+ {isLoading ? (
+ <>
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
+ 요약 중...
+ </>
+ ) : (
+ <>
+ <Send className="h-4 w-4 mr-2" />
+ 요약하기
+ </>
+ )}
+ </Button>
+ </CardContent>
+ </Card>
+
+ {/* 결과 섹션 */}
+ <Card>
+ <CardHeader>
+ <CardTitle>요약 결과</CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ {summaryResult ? (
+ <div className="space-y-4">
+ <div className="space-y-2">
+ <h4 className="font-semibold text-sm text-muted-foreground">이슈</h4>
+ <div className="p-3 bg-muted rounded-md">
+ {summaryResult.issue}
+ </div>
+ </div>
+
+ <div className="space-y-2">
+ <h4 className="font-semibold text-sm text-muted-foreground">요청</h4>
+ <div className="p-3 bg-muted rounded-md">
+ {summaryResult.request}
+ </div>
+ </div>
+
+ <div className="space-y-2">
+ <h4 className="font-semibold text-sm text-muted-foreground">요약 메시지</h4>
+ <div className="p-3 bg-muted rounded-md">
+ {summaryResult.summary}
+ </div>
+ </div>
+ </div>
+ ) : (
+ <div className="text-center py-8 text-muted-foreground">
+ <Send className="h-12 w-12 mx-auto mb-4 opacity-50" />
+ <p>요약하기 버튼을 클릭하여 결과를 확인하세요</p>
+ </div>
+ )}
+ </CardContent>
+ </Card>
+ </div>
+
+ {/* 미리보기 섹션 */}
+ <Card>
+ <CardHeader>
+ <CardTitle>API 요청 미리보기</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="space-y-4">
+ <div>
+ <h4 className="font-semibold text-sm text-muted-foreground mb-2">Request Body</h4>
+ <pre className="bg-muted p-4 rounded-md text-sm overflow-x-auto">
+ {JSON.stringify({
+ conversation: JSON.stringify(messages.map(msg => ({
+ sender: msg.sender,
+ message: msg.message
+ })))
+ }, null, 2)}
+ </pre>
+ </div>
+
+ {/* <div>
+ <h4 className="font-semibold text-sm text-muted-foreground mb-2">Headers</h4>
+ <pre className="bg-muted p-4 rounded-md text-sm overflow-x-auto">
+ {JSON.stringify({
+ 'Authorization': 'Bearer {access_token}',
+ 'Content-Type': 'application/json'
+ }, null, 2)}
+ </pre>
+ </div> */}
+ </div>
+ </CardContent>
+ </Card>
+ </div>
+ );
+}