feat: 프롬프트 입력창을 Univer 위 오버레이로 변경 및 기본적으로 항상 표시되도록 개선\n\n- 프롬프트 입력창을 Univer 시트 위에 반투명 오버레이로 구현\n- 우하단 플로팅 버튼으로 토글 가능 (기본값: 항상 표시)\n- 입력창 디자인을 컴팩트하게 개선, 반응형 오버레이 적용\n- 기존 하단 고정 방식 제거, Univer 전체 화면 활용\n- 사용자 경험 및 접근성 향상
This commit is contained in:
@@ -332,6 +332,7 @@ const TestSheetViewer: React.FC = () => {
|
|||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
const [currentFile, setCurrentFile] = useState<File | null>(null);
|
const [currentFile, setCurrentFile] = useState<File | null>(null);
|
||||||
const [prompt, setPrompt] = useState("");
|
const [prompt, setPrompt] = useState("");
|
||||||
|
const [showPromptInput, setShowPromptInput] = useState(true);
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
@@ -862,241 +863,290 @@ const TestSheetViewer: React.FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-screen flex flex-col relative">
|
<div className="min-h-screen bg-gray-50 flex flex-col relative">
|
||||||
{/* 헤더 */}
|
{/* 헤더 - App.tsx와 동일한 스타일 적용 */}
|
||||||
<div className="bg-white border-b p-4 flex-shrink-0 relative z-10">
|
<header className="bg-white shadow-sm border-b">
|
||||||
<div
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
className="mt-2 flex items-center gap-4"
|
<div className="flex justify-between items-center h-16">
|
||||||
style={{ position: "relative" }}
|
<div className="flex items-center">
|
||||||
>
|
{currentFile && (
|
||||||
{currentFile && (
|
<span className="text-sm text-blue-600 font-medium">
|
||||||
<span className="text-sm text-blue-600 font-medium">
|
📄 {currentFile.name}
|
||||||
📄 {currentFile.name}
|
</span>
|
||||||
</span>
|
)}
|
||||||
)}
|
</div>
|
||||||
<button
|
<div className="flex items-center space-x-4">
|
||||||
onClick={handleNewUpload}
|
<button
|
||||||
className="text-xs px-2 py-1 bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors"
|
onClick={handleNewUpload}
|
||||||
style={{
|
className="px-3 py-1.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
|
||||||
position: "absolute",
|
>
|
||||||
right: "0%",
|
새 파일 업로드
|
||||||
top: "50%",
|
</button>
|
||||||
transform: "translateY(-50%)",
|
</div>
|
||||||
}}
|
</div>
|
||||||
>
|
|
||||||
새 파일 업로드
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
{/* Univer 컨테이너 (항상 렌더링) */}
|
{/* 메인 콘텐츠 - App.tsx와 동일한 패턴 */}
|
||||||
<div
|
<main className="h-[calc(100vh-4rem)]">
|
||||||
ref={containerRef}
|
<div className="h-full">
|
||||||
className="flex-1 relative"
|
{/* Univer 컨테이너 - 전체 화면 사용 */}
|
||||||
style={{
|
|
||||||
minHeight: "0",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
flexGrow: 0.85,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Univer와 입력창 사이 여백 */}
|
|
||||||
<div style={{ height: "1rem" }} />
|
|
||||||
|
|
||||||
{/* 프롬프트 입력창 - Univer 하단에 이어서 */}
|
|
||||||
<PromptInput
|
|
||||||
value={prompt}
|
|
||||||
onChange={(e) => setPrompt(e.target.value)}
|
|
||||||
onExecute={handlePromptExecute}
|
|
||||||
onHistoryToggle={handleHistoryToggle}
|
|
||||||
historyCount={history.length}
|
|
||||||
disabled={isProcessing}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 히스토리 패널 - 파일이 업로드된 후에만 표시 */}
|
|
||||||
{!showUploadOverlay && (
|
|
||||||
<HistoryPanel
|
|
||||||
isOpen={isHistoryOpen}
|
|
||||||
onClose={() => setIsHistoryOpen(false)}
|
|
||||||
history={history}
|
|
||||||
onReapply={handleHistoryReapply}
|
|
||||||
onClear={handleHistoryClear}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 파일 업로드 오버레이 - 레이어 분리 */}
|
|
||||||
{showUploadOverlay && (
|
|
||||||
<>
|
|
||||||
{/* 1. Univer CE 영역만 흐리게 하는 반투명 레이어 */}
|
|
||||||
<div
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="relative bg-white"
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
minHeight: "0",
|
||||||
top: "0px", // 헤더 높이만큼 아래로 (헤더는 약 80px)
|
width: "100%",
|
||||||
left: 0,
|
height: "100%", // 전체 높이 사용
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
zIndex: 40,
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.01)",
|
|
||||||
backdropFilter: "blur(8px)",
|
|
||||||
WebkitBackdropFilter: "blur(8px)", // Safari 지원
|
|
||||||
pointerEvents: "auto",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 2. Univer 영역 중앙의 업로드 UI */}
|
{/* 히스토리 패널 - 파일이 업로드된 후에만 표시 */}
|
||||||
<div
|
{!showUploadOverlay && (
|
||||||
style={{
|
<HistoryPanel
|
||||||
position: "absolute",
|
isOpen={isHistoryOpen}
|
||||||
top: "-100px", // 헤더 높이만큼 아래로
|
onClose={() => setIsHistoryOpen(false)}
|
||||||
left: 0,
|
history={history}
|
||||||
right: 0,
|
onReapply={handleHistoryReapply}
|
||||||
bottom: 0,
|
onClear={handleHistoryClear}
|
||||||
display: "flex",
|
/>
|
||||||
alignItems: "center",
|
)}
|
||||||
justifyContent: "center",
|
|
||||||
padding: "1rem",
|
{/* 파일 업로드 오버레이 - 레이어 분리 */}
|
||||||
zIndex: 50,
|
{showUploadOverlay && (
|
||||||
}}
|
<>
|
||||||
>
|
{/* 1. Univer CE 영역만 흐리게 하는 반투명 레이어 */}
|
||||||
<div
|
|
||||||
className="max-w-2xl w-full"
|
|
||||||
style={{ transform: "scale(0.8)" }}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="bg-white rounded-lg shadow-xl border p-8 md:p-12"
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "#ffffff",
|
position: "absolute",
|
||||||
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
|
top: "0px", // 헤더 높이만큼 아래로 (헤더는 약 80px)
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
zIndex: 40,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.01)",
|
||||||
|
backdropFilter: "blur(8px)",
|
||||||
|
WebkitBackdropFilter: "blur(8px)", // Safari 지원
|
||||||
|
pointerEvents: "auto",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 2. Univer 영역 중앙의 업로드 UI */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "-100px", // 헤더 높이만큼 아래로
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: "1rem",
|
||||||
|
zIndex: 50,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-center">
|
<div
|
||||||
{/* 아이콘 및 제목 */}
|
className="max-w-2xl w-full"
|
||||||
<div className="mb-8">
|
style={{ transform: "scale(0.8)" }}
|
||||||
<div
|
>
|
||||||
className={cn(
|
|
||||||
"mx-auto h-20 w-20 md:h-24 md:w-24 rounded-full flex items-center justify-center mb-4",
|
|
||||||
isProcessing ? "bg-blue-100" : "bg-blue-50",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{isProcessing ? (
|
|
||||||
<svg
|
|
||||||
className="h-10 w-10 md:h-12 md:w-12 text-blue-600 animate-spin"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
className="opacity-25"
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="10"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="4"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
className="opacity-75"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<svg
|
|
||||||
className="h-10 w-10 md:h-12 md:w-12 text-blue-600"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<h2 className="text-xl md:text-2xl font-semibold mb-2 text-gray-900">
|
|
||||||
{isProcessing
|
|
||||||
? "파일 처리 중..."
|
|
||||||
: "Excel 파일을 업로드하세요"}
|
|
||||||
</h2>
|
|
||||||
<p className="text-sm md:text-base text-gray-600 mb-6">
|
|
||||||
{isProcessing ? (
|
|
||||||
<span className="text-blue-600">
|
|
||||||
잠시만 기다려주세요...
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="font-medium text-gray-900">
|
|
||||||
.xlsx
|
|
||||||
</span>{" "}
|
|
||||||
파일을 드래그 앤 드롭하거나 클릭하여 업로드
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 드래그 앤 드롭 영역 */}
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className="bg-white rounded-lg shadow-xl border p-8 md:p-12"
|
||||||
"border-2 border-dashed rounded-lg p-8 md:p-12 transition-all duration-200 cursor-pointer",
|
style={{
|
||||||
"hover:border-blue-400 hover:bg-blue-50",
|
backgroundColor: "#ffffff",
|
||||||
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
|
||||||
isDragOver
|
}}
|
||||||
? "border-blue-500 bg-blue-100 scale-105"
|
|
||||||
: "border-gray-300",
|
|
||||||
isProcessing && "opacity-50 cursor-not-allowed",
|
|
||||||
)}
|
|
||||||
onDragEnter={handleDragEnter}
|
|
||||||
onDragLeave={handleDragLeave}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
onDrop={handleDrop}
|
|
||||||
onClick={handleFilePickerClick}
|
|
||||||
tabIndex={isProcessing ? -1 : 0}
|
|
||||||
role="button"
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center justify-center space-y-4">
|
<div className="text-center">
|
||||||
<div className="text-4xl md:text-6xl">
|
{/* 아이콘 및 제목 */}
|
||||||
{isDragOver ? "📂" : "📄"}
|
<div className="mb-8">
|
||||||
</div>
|
<div
|
||||||
<div className="text-center">
|
className={cn(
|
||||||
<p className="text-base md:text-lg font-medium mb-2 text-gray-900">
|
"mx-auto h-20 w-20 md:h-24 md:w-24 rounded-full flex items-center justify-center mb-4",
|
||||||
{isDragOver
|
isProcessing ? "bg-blue-100" : "bg-blue-50",
|
||||||
? "파일을 여기에 놓으세요"
|
)}
|
||||||
: "파일을 드래그하거나 클릭하세요"}
|
>
|
||||||
|
{isProcessing ? (
|
||||||
|
<svg
|
||||||
|
className="h-10 w-10 md:h-12 md:w-12 text-blue-600 animate-spin"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
className="opacity-25"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg
|
||||||
|
className="h-10 w-10 md:h-12 md:w-12 text-blue-600"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h2 className="text-xl md:text-2xl font-semibold mb-2 text-gray-900">
|
||||||
|
{isProcessing
|
||||||
|
? "파일 처리 중..."
|
||||||
|
: "Excel 파일을 업로드하세요"}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm md:text-base text-gray-600 mb-6">
|
||||||
|
{isProcessing ? (
|
||||||
|
<span className="text-blue-600">
|
||||||
|
잠시만 기다려주세요...
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="font-medium text-gray-900">
|
||||||
|
.xlsx
|
||||||
|
</span>{" "}
|
||||||
|
파일을 드래그 앤 드롭하거나 클릭하여 업로드
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">
|
</div>
|
||||||
최대 50MB까지 업로드 가능
|
|
||||||
|
{/* 드래그 앤 드롭 영역 */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"border-2 border-dashed rounded-lg p-8 md:p-12 transition-all duration-200 cursor-pointer",
|
||||||
|
"hover:border-blue-400 hover:bg-blue-50",
|
||||||
|
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
||||||
|
isDragOver
|
||||||
|
? "border-blue-500 bg-blue-100 scale-105"
|
||||||
|
: "border-gray-300",
|
||||||
|
isProcessing && "opacity-50 cursor-not-allowed",
|
||||||
|
)}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onClick={handleFilePickerClick}
|
||||||
|
tabIndex={isProcessing ? -1 : 0}
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center justify-center space-y-4">
|
||||||
|
<div className="text-4xl md:text-6xl">
|
||||||
|
{isDragOver ? "📂" : "📄"}
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-base md:text-lg font-medium mb-2 text-gray-900">
|
||||||
|
{isDragOver
|
||||||
|
? "파일을 여기에 놓으세요"
|
||||||
|
: "파일을 드래그하거나 클릭하세요"}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
최대 50MB까지 업로드 가능
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 숨겨진 파일 입력 */}
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept=".xlsx"
|
||||||
|
onChange={handleFileInputChange}
|
||||||
|
className="hidden"
|
||||||
|
disabled={isProcessing}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 지원 형식 안내 */}
|
||||||
|
<div className="mt-6 text-xs text-gray-500">
|
||||||
|
<p>지원 형식: Excel (.xlsx)</p>
|
||||||
|
<p>최대 파일 크기: 50MB</p>
|
||||||
|
<p className="mt-2 text-blue-600">
|
||||||
|
💡 브라우저 콘솔에서 window.__UNIVER_DEBUG__ 로 디버깅
|
||||||
|
가능
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 숨겨진 파일 입력 */}
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
type="file"
|
|
||||||
accept=".xlsx"
|
|
||||||
onChange={handleFileInputChange}
|
|
||||||
className="hidden"
|
|
||||||
disabled={isProcessing}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 지원 형식 안내 */}
|
|
||||||
<div className="mt-6 text-xs text-gray-500">
|
|
||||||
<p>지원 형식: Excel (.xlsx)</p>
|
|
||||||
<p>최대 파일 크기: 50MB</p>
|
|
||||||
<p className="mt-2 text-blue-600">
|
|
||||||
💡 브라우저 콘솔에서 window.__UNIVER_DEBUG__ 로 디버깅
|
|
||||||
가능
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 프롬프트 입력창 - Univer 위 오버레이 */}
|
||||||
|
{showPromptInput && (
|
||||||
|
<div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 z-40">
|
||||||
|
<div className="bg-white/95 backdrop-blur-sm border border-gray-200 rounded-2xl shadow-2xl p-4 max-w-4xl w-[90vw] sm:w-[80vw] md:w-[70vw] lg:w-[60vw]">
|
||||||
|
<PromptInput
|
||||||
|
value={prompt}
|
||||||
|
onChange={(e) => setPrompt(e.target.value)}
|
||||||
|
onExecute={handlePromptExecute}
|
||||||
|
onHistoryToggle={handleHistoryToggle}
|
||||||
|
historyCount={history.length}
|
||||||
|
disabled={isProcessing}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</>
|
|
||||||
)}
|
{/* AI 프롬프트 토글 버튼 - 플로팅 */}
|
||||||
|
{!showUploadOverlay && (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowPromptInput(!showPromptInput)}
|
||||||
|
className={`fixed bottom-6 right-6 z-50 w-14 h-14 rounded-full shadow-2xl transition-all duration-300 flex items-center justify-center ${
|
||||||
|
showPromptInput
|
||||||
|
? "bg-red-500 hover:bg-red-600 text-white"
|
||||||
|
: "bg-green-500 hover:bg-green-600 text-white"
|
||||||
|
}`}
|
||||||
|
aria-label={
|
||||||
|
showPromptInput ? "AI 프롬프트 닫기" : "AI 프롬프트 열기"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{showPromptInput ? (
|
||||||
|
<svg
|
||||||
|
className="w-6 h-6"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg
|
||||||
|
className="w-6 h-6"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { useAppStore } from "../../stores/useAppStore";
|
import { useAppStore } from "../../stores/useAppStore";
|
||||||
import { aiProcessor } from "../../utils/aiProcessor";
|
import { aiProcessor } from "../../utils/aiProcessor";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
interface PromptInputProps {
|
interface PromptInputProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -176,45 +177,43 @@ const PromptInput: React.FC<PromptInputProps> = ({
|
|||||||
}, [cellAddressToInsert, onChange, setCellAddressToInsert]);
|
}, [cellAddressToInsert, onChange, setCellAddressToInsert]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[60%] mx-auto bg-white z-10 flex flex-col items-center py-4 px-2">
|
<div className="w-full z-10 flex flex-col items-center">
|
||||||
<div className="w-full max-w-3xl flex items-end gap-2">
|
<div className="w-full flex items-end gap-3">
|
||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
className="flex-1 resize-none rounded-lg border border-gray-300 bg-gray-50 px-4 py-3 text-base text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-400 disabled:bg-gray-100 disabled:cursor-not-allowed min-h-[48px] max-h-32 shadow-sm"
|
className="flex-1 resize-none rounded-xl border border-gray-300 bg-white px-4 py-3 text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-400 disabled:bg-gray-100 disabled:cursor-not-allowed min-h-[44px] max-h-28 shadow-sm"
|
||||||
placeholder="질문을 입력하세요.
|
placeholder="AI에게 명령하세요...
|
||||||
예시) A1부터 A10까지 합계를 구해서 B1에 입력하는 수식을 입력해줘
|
예: A1부터 A10까지 합계를 B1에 입력해줘"
|
||||||
|
|
||||||
💡 팁: 시트에서 셀을 선택하면 자동으로 주소가 입력됩니다"
|
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
rows={5}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
<div style={{ width: "1rem" }} />
|
<div style={{ width: "1rem" }} />
|
||||||
|
|
||||||
{/* 버튼들을 세로로 배치 */}
|
{/* 버튼들을 세로로 배치 - 오버레이에 맞게 컴팩트 */}
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-2">
|
||||||
{/* 히스토리 버튼 - 맨 위 */}
|
{/* 히스토리 버튼 */}
|
||||||
{onHistoryToggle && (
|
{onHistoryToggle && (
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 rounded-lg text-gray-600 border border-gray-300 hover:bg-gray-50 font-semibold text-base shadow transition disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
className="px-2 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-1"
|
||||||
onClick={onHistoryToggle}
|
onClick={onHistoryToggle}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
aria-label="작업 히스토리 보기"
|
aria-label="작업 히스토리 보기"
|
||||||
>
|
>
|
||||||
📝
|
📝
|
||||||
{historyCount !== undefined && historyCount > 0 && (
|
{historyCount !== undefined && historyCount > 0 && (
|
||||||
<span className="text-xs bg-blue-500 text-white rounded-full px-2 py-0.5 min-w-[20px] h-5 flex items-center justify-center">
|
<span className="text-xs bg-blue-500 text-white rounded-full px-1.5 py-0.5 min-w-[16px] h-4 flex items-center justify-center text-[10px]">
|
||||||
{historyCount > 99 ? "99+" : historyCount}
|
{historyCount > 99 ? "99+" : historyCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* UNDO 버튼 - 중간 */}
|
{/* UNDO 버튼 */}
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 rounded-lg text-gray-600 border border-gray-300 hover:bg-gray-50 font-semibold text-base shadow transition disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
className="px-2 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// TODO: UNDO 기능 구현
|
// TODO: UNDO 기능 구현
|
||||||
console.log("🔄 UNDO 버튼 클릭");
|
console.log("🔄 UNDO 버튼 클릭");
|
||||||
@@ -225,25 +224,25 @@ const PromptInput: React.FC<PromptInputProps> = ({
|
|||||||
↶
|
↶
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 전송하기 버튼 - 맨 아래 */}
|
{/* 전송하기 버튼 */}
|
||||||
<button
|
<Button
|
||||||
className="px-6 py-2 rounded-lg text-white font-semibold text-base shadow transition disabled:opacity-60 disabled:cursor-not-allowed"
|
variant="outline"
|
||||||
style={{
|
size="sm"
|
||||||
background: isProcessing
|
className={`text-xs px-3 py-1.5 ${
|
||||||
? "#6b7280"
|
isProcessing || !value.trim()
|
||||||
: "linear-gradient(90deg, #a18fff 0%, #6f6fff 100%)",
|
? "bg-gray-400 text-white cursor-not-allowed border-gray-400"
|
||||||
}}
|
: "bg-green-500 hover:bg-green-600 text-white border-green-500"
|
||||||
|
}`}
|
||||||
onClick={handleExecute}
|
onClick={handleExecute}
|
||||||
disabled={isProcessing || !value.trim()}
|
disabled={isProcessing || !value.trim()}
|
||||||
>
|
>
|
||||||
{isProcessing ? "처리 중..." : "전송하기"}
|
{isProcessing ? "처리중" : "전송"}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full max-w-3xl flex justify-between items-center mt-1 px-1">
|
<div className="w-full flex justify-between items-center mt-2 px-1">
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
Press Enter to send, Shift+Enter for new line | 시트에서 셀 클릭하여
|
시트에서 셀 클릭시 자동 입력 | Enter로 전송
|
||||||
주소 자동 입력
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-400">
|
<span className="text-xs text-gray-400">
|
||||||
{value.length}/{maxLength}
|
{value.length}/{maxLength}
|
||||||
|
|||||||
Reference in New Issue
Block a user