feat: 프롬프트 입력창을 Univer 위 오버레이로 변경 및 기본적으로 항상 표시되도록 개선\n\n- 프롬프트 입력창을 Univer 시트 위에 반투명 오버레이로 구현\n- 우하단 플로팅 버튼으로 토글 가능 (기본값: 항상 표시)\n- 입력창 디자인을 컴팩트하게 개선, 반응형 오버레이 적용\n- 기존 하단 고정 방식 제거, Univer 전체 화면 활용\n- 사용자 경험 및 접근성 향상

This commit is contained in:
sheetEasy AI Team
2025-06-27 16:22:58 +09:00
parent 1419bf415f
commit 3d0a5799ff
2 changed files with 291 additions and 242 deletions

View File

@@ -332,6 +332,7 @@ const TestSheetViewer: React.FC = () => {
const [isProcessing, setIsProcessing] = useState(false);
const [currentFile, setCurrentFile] = useState<File | null>(null);
const [prompt, setPrompt] = useState("");
const [showPromptInput, setShowPromptInput] = useState(true);
const appStore = useAppStore();
@@ -862,58 +863,44 @@ const TestSheetViewer: React.FC = () => {
}, []);
return (
<div className="w-full h-screen flex flex-col relative">
{/* 헤더 */}
<div className="bg-white border-b p-4 flex-shrink-0 relative z-10">
<div
className="mt-2 flex items-center gap-4"
style={{ position: "relative" }}
>
<div className="min-h-screen bg-gray-50 flex flex-col relative">
{/* 헤더 - App.tsx와 동일한 스타일 적용 */}
<header className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex items-center">
{currentFile && (
<span className="text-sm text-blue-600 font-medium">
📄 {currentFile.name}
</span>
)}
</div>
<div className="flex items-center space-x-4">
<button
onClick={handleNewUpload}
className="text-xs px-2 py-1 bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors"
style={{
position: "absolute",
right: "0%",
top: "50%",
transform: "translateY(-50%)",
}}
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"
>
</button>
</div>
</div>
</div>
</header>
{/* Univer 컨테이너 (항상 렌더링) */}
{/* 메인 콘텐츠 - App.tsx와 동일한 패턴 */}
<main className="h-[calc(100vh-4rem)]">
<div className="h-full">
{/* Univer 컨테이너 - 전체 화면 사용 */}
<div
ref={containerRef}
className="flex-1 relative"
className="relative bg-white"
style={{
minHeight: "0",
width: "100%",
height: "100%",
flexGrow: 0.85,
height: "100%", // 전체 높이 사용
}}
/>
{/* 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
@@ -1097,6 +1084,69 @@ const TestSheetViewer: React.FC = () => {
</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>
)}
{/* 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>
);
};

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useRef, useState } from "react";
import { useAppStore } from "../../stores/useAppStore";
import { aiProcessor } from "../../utils/aiProcessor";
import { Button } from "../ui/button";
interface PromptInputProps {
value: string;
@@ -176,45 +177,43 @@ const PromptInput: React.FC<PromptInputProps> = ({
}, [cellAddressToInsert, onChange, setCellAddressToInsert]);
return (
<div className="w-[60%] mx-auto bg-white z-10 flex flex-col items-center py-4 px-2">
<div className="w-full max-w-3xl flex items-end gap-2">
<div className="w-full z-10 flex flex-col items-center">
<div className="w-full flex items-end gap-3">
<textarea
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"
placeholder="질문을 입력하세요.
시) A1부터 A10까지 합계를 구해서 B1에 입력하는 수식을 입력해줘
💡 팁: 시트에서 셀을 선택하면 자동으로 주소가 입력됩니다"
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="AI에게 명령하세요...
: A1부터 A10까지 합계를 B1에 입력해줘"
value={value}
onChange={onChange}
disabled={isProcessing}
maxLength={maxLength}
rows={5}
rows={3}
/>
<div style={{ width: "1rem" }} />
{/* 버튼들을 세로로 배치 */}
<div className="flex flex-col gap-3">
{/* 히스토리 버튼 - 맨 위 */}
{/* 버튼들을 세로로 배치 - 오버레이에 맞게 컴팩트 */}
<div className="flex flex-col gap-2">
{/* 히스토리 버튼 */}
{onHistoryToggle && (
<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}
disabled={isProcessing}
aria-label="작업 히스토리 보기"
>
📝
{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}
</span>
)}
</button>
)}
{/* UNDO 버튼 - 중간 */}
{/* UNDO 버튼 */}
<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={() => {
// TODO: UNDO 기능 구현
console.log("🔄 UNDO 버튼 클릭");
@@ -225,25 +224,25 @@ const PromptInput: React.FC<PromptInputProps> = ({
</button>
{/* 전송하기 버튼 - 맨 아래 */}
<button
className="px-6 py-2 rounded-lg text-white font-semibold text-base shadow transition disabled:opacity-60 disabled:cursor-not-allowed"
style={{
background: isProcessing
? "#6b7280"
: "linear-gradient(90deg, #a18fff 0%, #6f6fff 100%)",
}}
{/* 전송하기 버튼 */}
<Button
variant="outline"
size="sm"
className={`text-xs px-3 py-1.5 ${
isProcessing || !value.trim()
? "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}
disabled={isProcessing || !value.trim()}
>
{isProcessing ? "처리 중..." : "전송하기"}
</button>
{isProcessing ? "처리" : "전송"}
</Button>
</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">
Press Enter to send, Shift+Enter for new line |
| Enter로
</span>
<span className="text-xs text-gray-400">
{value.length}/{maxLength}