From 2f3515985d603739c47a22d95ff9f3791111ac8a Mon Sep 17 00:00:00 2001 From: sheetEasy AI Team Date: Tue, 1 Jul 2025 15:47:26 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=8A=9C=ED=86=A0=EB=A6=AC=EC=96=BC=20?= =?UTF-8?q?UX=20=EA=B0=9C=EC=84=A0=20-=20=EB=84=A4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=B0=8F=20=ED=94=84=EB=A1=AC=ED=94=84?= =?UTF-8?q?=ED=8A=B8=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ”ง ์ฃผ์š” ๊ฐœ์„ ์‚ฌํ•ญ: 1. Topbar ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฌธ์ œ ํ•ด๊ฒฐ - ํŠœํ† ๋ฆฌ์–ผ ํŽ˜์ด์ง€์—์„œ ๋ฉ”๋‰ด ํ•ญ๋ชฉ ํด๋ฆญ ์‹œ ์˜ฌ๋ฐ”๋ฅธ ๋ผ์šฐํŒ… ๊ตฌํ˜„ - Tutorial ๋ฉ”๋‰ด ํด๋ฆญ ์‹œ ํŽ˜์ด์ง€ ๋ฆฌ๋กœ๋“œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ (์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋งˆ์šดํŠธ) - ๋ผ์šฐํŒ… ์šฐ์„ , ์Šคํฌ๋กค ํด๋ฐฑ ํŒจํ„ด ์ ์šฉ 2. PromptInput ํ”Œ๋ ˆ์ด์Šคํ™€๋” ๊ฐœ์„  - ํŠœํ† ๋ฆฌ์–ผ ์‹คํ–‰ ํ›„ ์‹ค์ œ ์‚ฌ์šฉ๋œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ํ”Œ๋ ˆ์ด์Šคํ™€๋”์— ํ‘œ์‹œ - ๋ช…ํ™•ํ•œ ํ”„๋กฌํ”„ํŠธ โ†’ ์‹คํ–‰ โ†’ ๊ฒฐ๊ณผ ์ถ”์  ๊ฐ€๋Šฅ - ์ƒˆ ํŠœํ† ๋ฆฌ์–ผ ์„ ํƒ ์‹œ ์ด์ „ ํ”„๋กฌํ”„ํŠธ ์ดˆ๊ธฐํ™” 3. ์ƒˆ๋กœ์šด ํŠœํ† ๋ฆฌ์–ผ ์‹œ์Šคํ…œ ๊ตฌ์ถ• - TutorialSheetViewer: ๋‹จ๊ณ„๋ณ„ ํŠœํ† ๋ฆฌ์–ผ ํ”Œ๋กœ์šฐ ๊ตฌํ˜„ - TutorialCard: ๊ฐœ๋ณ„ ํŠœํ† ๋ฆฌ์–ผ ์นด๋“œ ์ปดํฌ๋„ŒํŠธ - TutorialExecutor: ํŠœํ† ๋ฆฌ์–ผ ์‹คํ–‰ ์—”์ง„ - TutorialDataGenerator: 10๊ฐœ Excel ํ•จ์ˆ˜ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๐Ÿ“ ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ๋“ค: - src/App.tsx: ๋„ค๋น„๊ฒŒ์ด์…˜ ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€ - src/components/ui/topbar.tsx: ๋ผ์šฐํŒ… ๊ธฐ๋ฐ˜ ๋„ค๋น„๊ฒŒ์ด์…˜ ๊ตฌํ˜„ - src/components/sheet/PromptInput.tsx: ๋™์  ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ถ”๊ฐ€ - src/components/TutorialSheetViewer.tsx: ํŠœํ† ๋ฆฌ์–ผ ์ „์šฉ ๋ทฐ์–ด ๊ตฌํ˜„ - src/types/tutorial.ts: ํŠœํ† ๋ฆฌ์–ผ ํƒ€์ž… ์ •์˜ - .cursor/rules/tutorial-navigation-fix.mdc: ๊ตฌํ˜„ ํŒจํ„ด ๋ฌธ์„œํ™” โœ… ๊ฒ€์ฆ ์™„๋ฃŒ: - ๋ชจ๋“  topbar ๋ฉ”๋‰ด ์ •์ƒ ๋„ค๋น„๊ฒŒ์ด์…˜ - ํŠœํ† ๋ฆฌ์–ผ ํŽ˜์ด์ง€ ๋ฆฌ๋กœ๋“œ ๊ธฐ๋Šฅ ์ž‘๋™ - ์‹คํ–‰๋œ ํ”„๋กฌํ”„ํŠธ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ‘œ์‹œ - AI ์›Œํฌํ”Œ๋กœ์šฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„์„ฑ --- .cursor/rules/tutorial-navigation-fix.mdc | 5 + .cursor/rules/tutorial-simulation.mdc | 5 + .cursor/rules/univer-presets-api.mdc | 5 + src/App.tsx | 127 ++++- src/components/LandingPage.tsx | 9 +- src/components/TutorialSheetViewer.tsx | 608 ++++++++++++++++++++++ src/components/sheet/EditSheetViewer.tsx | 95 +++- src/components/sheet/PromptInput.tsx | 9 +- src/components/ui/topbar.tsx | 66 ++- src/components/ui/tutorial-card.tsx | 158 ++++++ src/components/ui/tutorial-section.tsx | 221 ++++++++ src/stores/useAppStore.ts | 73 +++ src/types/tutorial.ts | 97 ++++ src/utils/tutorialDataGenerator.ts | 498 ++++++++++++++++++ src/utils/tutorialExecutor.ts | 404 ++++++++++++++ 15 files changed, 2367 insertions(+), 13 deletions(-) create mode 100644 .cursor/rules/tutorial-navigation-fix.mdc create mode 100644 .cursor/rules/tutorial-simulation.mdc create mode 100644 .cursor/rules/univer-presets-api.mdc create mode 100644 src/components/TutorialSheetViewer.tsx create mode 100644 src/components/ui/tutorial-card.tsx create mode 100644 src/components/ui/tutorial-section.tsx create mode 100644 src/types/tutorial.ts create mode 100644 src/utils/tutorialDataGenerator.ts create mode 100644 src/utils/tutorialExecutor.ts diff --git a/.cursor/rules/tutorial-navigation-fix.mdc b/.cursor/rules/tutorial-navigation-fix.mdc new file mode 100644 index 0000000..b93c988 --- /dev/null +++ b/.cursor/rules/tutorial-navigation-fix.mdc @@ -0,0 +1,5 @@ +--- +description: +globs: +alwaysApply: false +--- diff --git a/.cursor/rules/tutorial-simulation.mdc b/.cursor/rules/tutorial-simulation.mdc new file mode 100644 index 0000000..b93c988 --- /dev/null +++ b/.cursor/rules/tutorial-simulation.mdc @@ -0,0 +1,5 @@ +--- +description: +globs: +alwaysApply: false +--- diff --git a/.cursor/rules/univer-presets-api.mdc b/.cursor/rules/univer-presets-api.mdc new file mode 100644 index 0000000..b93c988 --- /dev/null +++ b/.cursor/rules/univer-presets-api.mdc @@ -0,0 +1,5 @@ +--- +description: +globs: +alwaysApply: false +--- diff --git a/src/App.tsx b/src/App.tsx index d2d722b..f0ce46c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,12 @@ import LandingPage from "./components/LandingPage"; import { SignUpPage } from "./components/auth/SignUpPage"; import { SignInPage } from "./components/auth/SignInPage"; import { useAppStore } from "./stores/useAppStore"; +import type { TutorialItem } from "./types/tutorial"; + +// TutorialSheetViewer ๋™์  import +const TutorialSheetViewer = lazy( + () => import("./components/TutorialSheetViewer"), +); // ๋™์  import๋กœ EditSheetViewer ๋กœ๋“œ (ํ•„์š”ํ•  ๋•Œ๋งŒ) const EditSheetViewer = lazy( @@ -13,12 +19,24 @@ const EditSheetViewer = lazy( ); // ์•ฑ ์ƒํƒœ ํƒ€์ž… ์ •์˜ -type AppView = "landing" | "signUp" | "signIn" | "editor" | "account"; +type AppView = + | "landing" + | "signUp" + | "signIn" + | "editor" + | "account" + | "tutorial"; function App() { const [currentView, setCurrentView] = useState("landing"); - const { isAuthenticated, setAuthenticated, setUser, user, currentFile } = - useAppStore(); + const { + isAuthenticated, + setAuthenticated, + setUser, + user, + currentFile, + startTutorial, + } = useAppStore(); // CTA ๋ฒ„ํŠผ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ - ์ธ์ฆ ์ƒํƒœ์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ const handleGetStarted = () => { @@ -65,6 +83,68 @@ function App() { setCurrentView("editor"); }; + // ํŠœํ† ๋ฆฌ์–ผ ํŽ˜์ด์ง€ ์ด๋™ ํ•ธ๋“ค๋Ÿฌ - ํŽ˜์ด์ง€ ๋ฆฌ๋กœ๋“œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ + const handleTutorialClick = () => { + if (currentView === "tutorial") { + // ์ด๋ฏธ ํŠœํ† ๋ฆฌ์–ผ ํŽ˜์ด์ง€์— ์žˆ๋Š” ๊ฒฝ์šฐ ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋งˆ์šดํŠธ๋ฅผ ์œ„ํ•ด key ๋ณ€๊ฒฝ + setCurrentView("landing"); + setTimeout(() => { + setCurrentView("tutorial"); + }, 10); + } else { + setCurrentView("tutorial"); + } + }; + + // ๋„ค๋น„๊ฒŒ์ด์…˜ ํ•ธ๋“ค๋Ÿฌ๋“ค - ๋ผ์šฐํŒ… ๊ธฐ๋ฐ˜ + const handleHomeClick = () => { + setCurrentView("landing"); + }; + + const handleFeaturesClick = () => { + setCurrentView("landing"); + // ๋ผ์šฐํŒ… ํ›„ ์Šคํฌ๋กค + setTimeout(() => { + const element = document.getElementById("features"); + if (element) { + element.scrollIntoView({ behavior: "smooth" }); + } + }, 100); + }; + + const handleFAQClick = () => { + setCurrentView("landing"); + // ๋ผ์šฐํŒ… ํ›„ ์Šคํฌ๋กค + setTimeout(() => { + const element = document.getElementById("faq"); + if (element) { + element.scrollIntoView({ behavior: "smooth" }); + } + }, 100); + }; + + const handlePricingClick = () => { + setCurrentView("landing"); + // ๋ผ์šฐํŒ… ํ›„ ์Šคํฌ๋กค + setTimeout(() => { + const element = document.getElementById("pricing"); + if (element) { + element.scrollIntoView({ behavior: "smooth" }); + } + }, 100); + }; + + // ํŠœํ† ๋ฆฌ์–ผ ์„ ํƒ ํ•ธ๋“ค๋Ÿฌ - ํŠœํ† ๋ฆฌ์–ผ ์‹œ์ž‘ ํ›„ ํŠœํ† ๋ฆฌ์–ผ ํŽ˜์ด์ง€๋กœ ์ „ํ™˜ + const handleTutorialSelect = (tutorial: TutorialItem) => { + console.log("๐ŸŽฏ ํŠœํ† ๋ฆฌ์–ผ ์„ ํƒ๋จ:", tutorial.metadata.title); + + // ์•ฑ ์Šคํ† ์–ด์— ์„ ํƒ๋œ ํŠœํ† ๋ฆฌ์–ผ ์„ค์ • + startTutorial(tutorial); + + // ํŠœํ† ๋ฆฌ์–ผ ํŽ˜์ด์ง€๋กœ ์ „ํ™˜ (ํ†ตํ•ฉ๋œ ์ง„์ž…์ ) + setCurrentView("tutorial"); + }; + // ๊ฐ€์ž… ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋Ÿฌ const handleSignUp = (email: string, password: string) => { // TODO: ์‹ค์ œ API ์—ฐ๋™ @@ -318,6 +398,41 @@ function App() { ); + case "tutorial": + return ( +
+ +
+
+ +
+
+

๐Ÿ“š ํŠœํ† ๋ฆฌ์–ผ ๋กœ๋”ฉ ์ค‘...

+
+
+ } + > + + +
+ + + ); + case "editor": return (
@@ -361,12 +476,18 @@ function App() { onSignInClick={handleGoToSignIn} onGetStartedClick={handleGetStarted} onAccountClick={handleAccountClick} + onTutorialClick={handleTutorialClick} + onHomeClick={handleHomeClick} + onFeaturesClick={handleFeaturesClick} + onFAQClick={handleFAQClick} + onPricingClick={handlePricingClick} />
); diff --git a/src/components/LandingPage.tsx b/src/components/LandingPage.tsx index 223ddd0..2471524 100644 --- a/src/components/LandingPage.tsx +++ b/src/components/LandingPage.tsx @@ -1,15 +1,18 @@ import * as React from "react"; import { HeroSection } from "./ui/hero-section"; import { FeaturesSection } from "./ui/features-section"; +import { TutorialSection } from "./ui/tutorial-section"; import { FAQSection } from "./ui/faq-section"; import { PricingSection } from "./ui/pricing-section"; import { Footer } from "./ui/footer"; +import type { TutorialItem } from "../types/tutorial"; interface LandingPageProps { onGetStarted?: () => void; onDownloadClick?: () => void; onAccountClick?: () => void; onDemoClick?: () => void; + onTutorialSelect?: (tutorial: TutorialItem) => void; } /** @@ -21,9 +24,8 @@ interface LandingPageProps { */ const LandingPage: React.FC = ({ onGetStarted, - onDownloadClick, - onAccountClick, onDemoClick, + onTutorialSelect, }) => { return (
@@ -35,6 +37,9 @@ const LandingPage: React.FC = ({ {/* Features Section - ์ฃผ์š” ๊ธฐ๋Šฅ ์†Œ๊ฐœ */} + {/* Tutorial Section - Excel ํ•จ์ˆ˜ ํŠœํ† ๋ฆฌ์–ผ */} + + {/* FAQ Section - ์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ */} diff --git a/src/components/TutorialSheetViewer.tsx b/src/components/TutorialSheetViewer.tsx new file mode 100644 index 0000000..33d0276 --- /dev/null +++ b/src/components/TutorialSheetViewer.tsx @@ -0,0 +1,608 @@ +import React, { useRef, useEffect, useState, useCallback } from "react"; +import { LocaleType } from "@univerjs/presets"; + +// Presets CSS import +import "@univerjs/presets/lib/styles/preset-sheets-core.css"; +import { cn } from "../lib/utils"; +import PromptInput from "./sheet/PromptInput"; +import HistoryPanel from "./ui/historyPanel"; +import { useAppStore } from "../stores/useAppStore"; +import { rangeToAddress } from "../utils/cellUtils"; +import { CellSelectionHandler } from "../utils/cellSelectionHandler"; +import { aiProcessor } from "../utils/aiProcessor"; +import { TutorialExecutor } from "../utils/tutorialExecutor"; +import { TutorialDataGenerator } from "../utils/tutorialDataGenerator"; +import { TutorialCard } from "./ui/tutorial-card"; +import type { HistoryEntry } from "../types/ai"; +import type { TutorialItem } from "../types/tutorial"; +import { UniverseManager } from "./sheet/EditSheetViewer"; + +// ํŠœํ† ๋ฆฌ์–ผ ๋‹จ๊ณ„ ํƒ€์ž… ์ •์˜ +type TutorialStep = "select" | "loaded" | "prompted" | "executed"; + +/** + * ํŠœํ† ๋ฆฌ์–ผ ์ „์šฉ ์‹œํŠธ ๋ทฐ์–ด + * - EditSheetViewer ๊ธฐ๋ฐ˜, ํŒŒ์ผ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ ์™„์ „ ์ œ๊ฑฐ + * - ๋‹จ๊ณ„๋ณ„ ํ”Œ๋กœ์šฐ: values โ†’ prompt โ†’ send โ†’ result + * - Univer ์ธ์Šคํ„ด์Šค ์ค‘๋ณต ์ดˆ๊ธฐํ™” ๋ฐฉ์ง€ + * - ์ผ๊ด€๋œ ๋„ค๋น„๊ฒŒ์ด์…˜ ๊ฒฝํ—˜ ์ œ๊ณต + */ +const TutorialSheetViewer: React.FC = () => { + const containerRef = useRef(null); + const mountedRef = useRef(false); + + const [isInitialized, setIsInitialized] = useState(false); + const [isProcessing, setIsProcessing] = useState(false); + const [prompt, setPrompt] = useState(""); + const [showPromptInput, setShowPromptInput] = useState(false); + const [currentStep, setCurrentStep] = useState("select"); + const [executedTutorialPrompt, setExecutedTutorialPrompt] = + useState(""); + + // ์„ ํƒ๋œ ํŠœํ† ๋ฆฌ์–ผ๊ณผ ํŠœํ† ๋ฆฌ์–ผ ๋ชฉ๋ก + const [selectedTutorial, setSelectedTutorial] = useState( + null, + ); + const [tutorialList] = useState(() => + TutorialDataGenerator.generateAllTutorials(), + ); + + const appStore = useAppStore(); + + // CellSelectionHandler ๋ฐ TutorialExecutor ์ธ์Šคํ„ด์Šค + const cellSelectionHandler = useRef(new CellSelectionHandler()); + const tutorialExecutor = useRef(new TutorialExecutor()); + + // ํžˆ์Šคํ† ๋ฆฌ ๊ด€๋ จ ์ƒํƒœ + const [isHistoryOpen, setIsHistoryOpen] = useState(false); + const [history, setHistory] = useState([]); + + // ์•ˆ์ „ํ•œ Univer ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ + const initializeUniver = useCallback(async () => { + if (!containerRef.current) { + console.warn("โš ๏ธ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์Œ"); + return false; + } + + if (isInitialized) { + console.log("โœ… ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋จ - ์Šคํ‚ต"); + return true; + } + + try { + console.log("๐Ÿš€ Univer ์ดˆ๊ธฐํ™” ์‹œ์ž‘ (Tutorial)"); + setIsProcessing(true); + + // UniverseManager๋ฅผ ํ†ตํ•œ ์ „์—ญ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ + const { univer, univerAPI } = await UniverseManager.createInstance( + containerRef.current, + ); + + // ๊ธฐ๋ณธ ์›Œํฌ๋ถ ๋ฐ์ดํ„ฐ (ํŠœํ† ๋ฆฌ์–ผ์šฉ) + const defaultWorkbook = { + id: `tutorial-workbook-${Date.now()}`, + locale: LocaleType.EN_US, + name: "Tutorial Workbook", + sheetOrder: ["tutorial-sheet"], + sheets: { + "tutorial-sheet": { + type: 0, + id: "tutorial-sheet", + name: "Tutorial Sheet", + tabColor: "", + hidden: 0, + rowCount: 100, + columnCount: 20, + zoomRatio: 1, + scrollTop: 0, + scrollLeft: 0, + defaultColumnWidth: 93, + defaultRowHeight: 27, + cellData: {}, + rowData: {}, + columnData: {}, + showGridlines: 1, + rowHeader: { width: 46, hidden: 0 }, + columnHeader: { height: 20, hidden: 0 }, + selections: ["A1"], + rightToLeft: 0, + }, + }, + }; + + // ์›Œํฌ๋ถ ์ƒ์„ฑ (TutorialSheetViewer์—์„œ ๋ˆ„๋ฝ๋œ ๋ถ€๋ถ„) + if (univerAPI) { + console.log("โœ… Presets ๊ธฐ๋ฐ˜ univerAPI ์ดˆ๊ธฐํ™” ์™„๋ฃŒ"); + + // ์ƒˆ ์›Œํฌ๋ถ ์ƒ์„ฑ (presets univerAPI ๋ฐฉ์‹) + const workbook = univerAPI.createWorkbook(defaultWorkbook); + console.log("โœ… ํŠœํ† ๋ฆฌ์–ผ์šฉ ์›Œํฌ๋ถ ์ƒ์„ฑ ์™„๋ฃŒ:", workbook?.getId()); + + // TutorialExecutor ์ดˆ๊ธฐํ™” + tutorialExecutor.current.setUniverAPI(univerAPI); + + // CellSelectionHandler ์ดˆ๊ธฐํ™” + cellSelectionHandler.current.initialize(univer); + + console.log("โœ… Univer ์ดˆ๊ธฐํ™” ์™„๋ฃŒ (Tutorial)"); + setIsInitialized(true); + return true; + } else { + console.warn("โš ๏ธ univerAPI๊ฐ€ ์ œ๊ณต๋˜์ง€ ์•Š์Œ"); + setIsInitialized(false); + return false; + } + } catch (error) { + console.error("โŒ Univer ์ดˆ๊ธฐํ™” ์‹คํŒจ:", error); + setIsInitialized(false); + return false; + } finally { + setIsProcessing(false); + } + }, [isInitialized]); + + // ํžˆ์Šคํ† ๋ฆฌ ๊ด€๋ จ ํ•ธ๋“ค๋Ÿฌ๋“ค + const handleHistoryToggle = () => { + console.log("๐Ÿ”„ ํžˆ์Šคํ† ๋ฆฌ ํ† ๊ธ€:", !isHistoryOpen); + setIsHistoryOpen(!isHistoryOpen); + }; + + const handleHistoryClear = () => { + if (window.confirm("๋ชจ๋“  ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) { + setHistory([]); + } + }; + + // ํžˆ์Šคํ† ๋ฆฌ ํ•ญ๋ชฉ ์ถ”๊ฐ€ ํ•จ์ˆ˜ + const addHistoryEntry = ( + prompt: string, + range: string, + sheetName: string, + actions: any[], + status: "success" | "error" | "pending", + error?: string, + ) => { + const newEntry: HistoryEntry = { + id: `tutorial-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + timestamp: new Date(), + prompt, + range, + sheetName, + actions, + status, + error, + }; + + setHistory((prev) => [newEntry, ...prev]); + return newEntry.id; + }; + + // ํžˆ์Šคํ† ๋ฆฌ ํ•ญ๋ชฉ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜ + const updateHistoryEntry = ( + id: string, + updates: Partial>, + ) => { + setHistory((prev) => + prev.map((entry) => (entry.id === id ? { ...entry, ...updates } : entry)), + ); + }; + + // ํŠœํ† ๋ฆฌ์–ผ ๊ณต์‹ ์ ์šฉ ํ•จ์ˆ˜ - ์‚ฌ์ „ ์ •์˜๋œ ๊ฒฐ๊ณผ๋ฅผ Univer์— ์ ์šฉ + const applyTutorialFormula = useCallback(async (tutorial: TutorialItem) => { + try { + if (!tutorialExecutor.current.isReady()) { + throw new Error("TutorialExecutor๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); + } + + console.log(`๐ŸŽฏ ํŠœํ† ๋ฆฌ์–ผ "${tutorial.metadata.title}" ๊ณต์‹ ์ ์šฉ ์‹œ์ž‘`); + + // TutorialExecutor์˜ applyTutorialResult ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ + const result = + await tutorialExecutor.current.applyTutorialResult(tutorial); + + console.log(`โœ… ํŠœํ† ๋ฆฌ์–ผ "${tutorial.metadata.title}" ๊ณต์‹ ์ ์šฉ ์™„๋ฃŒ`); + return result; + } catch (error) { + console.error("โŒ ํŠœํ† ๋ฆฌ์–ผ ๊ณต์‹ ์ ์šฉ ์‹คํŒจ:", error); + return { + success: false, + message: + error instanceof Error + ? error.message + : "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", + appliedActions: [], + }; + } + }, []); + + // ํžˆ์Šคํ† ๋ฆฌ ์žฌ์ ์šฉ ํ•ธ๋“ค๋Ÿฌ + const handleHistoryReapply = useCallback(async (entry: HistoryEntry) => { + console.log("๐Ÿ”„ ํžˆ์Šคํ† ๋ฆฌ ์žฌ์ ์šฉ ์‹œ์ž‘:", entry); + + setPrompt(entry.prompt); + + const confirmReapply = window.confirm( + `๋‹ค์Œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?\n\n"${entry.prompt}"\n\n๋ฒ”์œ„: ${entry.range} | ์‹œํŠธ: ${entry.sheetName}`, + ); + + if (!confirmReapply) return; + + const reapplyHistoryId = addHistoryEntry( + `[์žฌ์ ์šฉ] ${entry.prompt}`, + entry.range, + entry.sheetName, + [], + "pending", + ); + + try { + const result = await aiProcessor.processPrompt(entry.prompt, true); + + if (result.success) { + updateHistoryEntry(reapplyHistoryId, { + status: "success", + actions: + result.appliedCells?.map((cell) => ({ + type: "formula" as const, + range: cell, + formula: `=์žฌ์ ์šฉ ์ˆ˜์‹`, + })) || [], + }); + console.log(`โœ… ์žฌ์ ์šฉ ์„ฑ๊ณต: ${result.message}`); + } else { + updateHistoryEntry(reapplyHistoryId, { + status: "error", + error: result.message, + actions: [], + }); + alert(`โŒ ์žฌ์ ์šฉ ์‹คํŒจ: ${result.message}`); + } + } catch (error) { + const errorMessage = + error instanceof Error + ? error.message + : "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."; + updateHistoryEntry(reapplyHistoryId, { + status: "error", + error: errorMessage, + actions: [], + }); + alert(`โŒ ์žฌ์ ์šฉ ์˜ค๋ฅ˜: ${errorMessage}`); + } + }, []); + + // ๋‹จ๊ณ„๋ณ„ ํŠœํ† ๋ฆฌ์–ผ ํ”Œ๋กœ์šฐ ๊ตฌํ˜„ + const handleTutorialSelect = useCallback( + async (tutorial: TutorialItem) => { + if (isProcessing) { + console.log("โณ ์ด๋ฏธ ์ฒ˜๋ฆฌ ์ค‘์ด๋ฏ€๋กœ ์Šคํ‚ต"); + return; + } + + console.log("๐ŸŽฏ ํŠœํ† ๋ฆฌ์–ผ ์„ ํƒ:", tutorial.metadata.title); + setIsProcessing(true); + setSelectedTutorial(tutorial); + + // ์ƒˆ ํŠœํ† ๋ฆฌ์–ผ ์„ ํƒ ์‹œ ์ด์ „ ์‹คํ–‰ ํ”„๋กฌํ”„ํŠธ ์ดˆ๊ธฐํ™” + setExecutedTutorialPrompt(""); + + try { + // Step 1: ์ดˆ๊ธฐํ™” ํ™•์ธ + if (!isInitialized) { + const initSuccess = await initializeUniver(); + if (!initSuccess) { + throw new Error("Univer ์ดˆ๊ธฐํ™” ์‹คํŒจ"); + } + } + + // Step 2: ๊ฐ’๋งŒ ๋กœ๋“œ (์ˆ˜์‹ ์—†์ด) + console.log("๐Ÿ“Š Step 1: ์˜ˆ์ œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ค‘..."); + await tutorialExecutor.current.populateSampleData(tutorial.sampleData); + + // Step 3: ํ”„๋กฌํ”„ํŠธ ๋ฏธ๋ฆฌ ์„ค์ • (์‹คํ–‰ํ•˜์ง€ ์•Š์Œ) + console.log("๐Ÿ’ญ Step 2: ํ”„๋กฌํ”„ํŠธ ๋ฏธ๋ฆฌ ์„ค์ •"); + setPrompt(tutorial.prompt); + + // Step 4: ํ”Œ๋กœ์šฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + setCurrentStep("loaded"); + setShowPromptInput(true); + + console.log("โœ… ํŠœํ† ๋ฆฌ์–ผ ์ค€๋น„ ์™„๋ฃŒ - ์‚ฌ์šฉ์ž๊ฐ€ Send ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์•ผ ํ•จ"); + } catch (error) { + console.error("โŒ ํŠœํ† ๋ฆฌ์–ผ ์„ค์ • ์‹คํŒจ:", error); + const errorMessage = + error instanceof Error ? error.message : "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜"; + alert(`ํŠœํ† ๋ฆฌ์–ผ์„ ์ค€๋น„ํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: ${errorMessage}`); + setCurrentStep("select"); + } finally { + setIsProcessing(false); + } + }, + [isProcessing, isInitialized, initializeUniver], + ); + + // ํŠœํ† ๋ฆฌ์–ผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ•ธ๋“ค๋Ÿฌ (Send ๋ฒ„ํŠผ์šฉ) - AI ๋Œ€์‹  ์‚ฌ์ „ ์ •์˜๋œ ๊ณต์‹ ์‚ฌ์šฉ + const handlePromptExecute = useCallback(async () => { + if (!prompt.trim()) { + alert("ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); + return; + } + + if (!selectedTutorial) { + alert("๋จผ์ € ํŠœํ† ๋ฆฌ์–ผ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); + return; + } + + console.log("๐ŸŽฏ Step 3: ํŠœํ† ๋ฆฌ์–ผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹œ์ž‘"); + setIsProcessing(true); + setCurrentStep("prompted"); + + const currentRange = appStore.selectedRange + ? rangeToAddress(appStore.selectedRange.range) + : selectedTutorial.targetCell; + const currentSheetName = selectedTutorial.metadata.title; + + // ํžˆ์Šคํ† ๋ฆฌ์— pending ์ƒํƒœ๋กœ ์ถ”๊ฐ€ + const historyId = addHistoryEntry( + prompt.trim(), + currentRange, + currentSheetName, + [], + "pending", + ); + + try { + // AI ์ฒ˜๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (0.8์ดˆ ์ง€์—ฐ์œผ๋กœ ์ž์—ฐ์Šค๋Ÿฌ์šด ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์—ฐ์ถœ) + console.log("๐Ÿค– AI ์ฒ˜๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ค‘..."); + await new Promise((resolve) => setTimeout(resolve, 800)); + + // ์„ ํƒ๋œ ํŠœํ† ๋ฆฌ์–ผ์˜ ์‚ฌ์ „ ์ •์˜๋œ ๊ฒฐ๊ณผ ์ ์šฉ + const tutorialResult = await applyTutorialFormula(selectedTutorial); + + if (tutorialResult.success) { + updateHistoryEntry(historyId, { + status: "success", + actions: tutorialResult.appliedActions || [], + }); + + // Step 4: ๊ฒฐ๊ณผ ์™„๋ฃŒ ์ƒํƒœ + setCurrentStep("executed"); + console.log("โœ… Step 4: ํŠœํ† ๋ฆฌ์–ผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„๋ฃŒ"); + + // ์‹คํ–‰๋œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ €์žฅํ•˜๊ณ  ์ž…๋ ฅ์ฐฝ ๋น„์šฐ๊ธฐ + setExecutedTutorialPrompt(prompt.trim()); + setPrompt(""); + } else { + updateHistoryEntry(historyId, { + status: "error", + error: tutorialResult.message, + actions: [], + }); + alert(`โŒ ์‹คํ–‰ ์‹คํŒจ: ${tutorialResult.message}`); + } + } catch (error) { + const errorMessage = + error instanceof Error + ? error.message + : "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."; + + updateHistoryEntry(historyId, { + status: "error", + error: errorMessage, + actions: [], + }); + + alert(`โŒ ์‹คํ–‰ ์˜ค๋ฅ˜: ${errorMessage}`); + } finally { + setIsProcessing(false); + } + }, [prompt, selectedTutorial, appStore.selectedRange]); + + // ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ์ดˆ๊ธฐํ™” + useEffect(() => { + if (mountedRef.current) return; + mountedRef.current = true; + + const setupTutorial = async () => { + try { + // DOM๊ณผ ์ปจํ…Œ์ด๋„ˆ ์ค€๋น„ ์™„๋ฃŒ ๋Œ€๊ธฐ + await new Promise((resolve) => { + const checkContainer = () => { + if ( + containerRef.current && + containerRef.current.offsetParent !== null + ) { + resolve(); + } else { + requestAnimationFrame(checkContainer); + } + }; + checkContainer(); + }); + + // ์•ฑ ์Šคํ† ์–ด์—์„œ ์„ ํƒ๋œ ํŠœํ† ๋ฆฌ์–ผ ํ™•์ธ + const activeTutorial = appStore.tutorialSession.activeTutorial; + if (activeTutorial) { + console.log( + "๐Ÿ“š ์•ฑ ์Šคํ† ์–ด์—์„œ ํ™œ์„ฑ ํŠœํ† ๋ฆฌ์–ผ ๋ฐœ๊ฒฌ:", + activeTutorial.metadata.title, + ); + await handleTutorialSelect(activeTutorial); + } else { + // Univer๋งŒ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋Œ€๊ธฐ + await initializeUniver(); + } + } catch (error) { + console.error("โŒ ํŠœํ† ๋ฆฌ์–ผ ์„ค์ • ์‹คํŒจ:", error); + } + }; + + setupTutorial(); + }, [ + initializeUniver, + handleTutorialSelect, + appStore.tutorialSession.activeTutorial, + ]); + + // ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ + useEffect(() => { + return () => { + if (cellSelectionHandler.current.isActive()) { + cellSelectionHandler.current.dispose(); + } + }; + }, []); + + // ๋‹จ๊ณ„๋ณ„ UI ๋ Œ๋”๋ง ๋„์šฐ๋ฏธ + const renderStepIndicator = () => { + const steps = [ + { key: "select", label: "ํŠœํ† ๋ฆฌ์–ผ ์„ ํƒ", icon: "๐ŸŽฏ" }, + { key: "loaded", label: "๋ฐ์ดํ„ฐ ๋กœ๋“œ๋จ", icon: "๐Ÿ“Š" }, + { key: "prompted", label: "ํ”„๋กฌํ”„ํŠธ ์ค€๋น„", icon: "๐Ÿ’ญ" }, + { key: "executed", label: "์‹คํ–‰ ์™„๋ฃŒ", icon: "โœ…" }, + ]; + + const currentIndex = steps.findIndex((step) => step.key === currentStep); + + return ( +
+ {steps.map((step, index) => ( +
+ {step.icon} + {step.label} +
+ ))} +
+ ); + }; + + return ( +
+ {/* ํŠœํ† ๋ฆฌ์–ผ ํ—ค๋” */} +
+
+

+ ๐Ÿ“š Excel ํ•จ์ˆ˜ ํŠœํ† ๋ฆฌ์–ผ +

+ + {/* ๋‹จ๊ณ„ ํ‘œ์‹œ๊ธฐ */} + {renderStepIndicator()} + +

+ {currentStep === "select" && "ํŠœํ† ๋ฆฌ์–ผ์„ ์„ ํƒํ•˜์—ฌ ์‹œ์ž‘ํ•˜์„ธ์š”."} + {currentStep === "loaded" && + "๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ํ™•์ธํ•˜๊ณ  Send ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”."} + {currentStep === "prompted" && "ํ”„๋กฌํ”„ํŠธ๋ฅผ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค..."} + {currentStep === "executed" && + "์‹คํ–‰์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”."} +

+ + {/* ํ˜„์žฌ ์„ ํƒ๋œ ํŠœํ† ๋ฆฌ์–ผ ์ •๋ณด */} + {selectedTutorial && ( +
+

+ ํ˜„์žฌ ํŠœํ† ๋ฆฌ์–ผ: {selectedTutorial.metadata.title} +

+

+ {selectedTutorial.metadata.description} +

+
+ + ํ•จ์ˆ˜: {selectedTutorial.functionName} + + + ๋‚œ์ด๋„: {selectedTutorial.metadata.difficulty} + + + ์†Œ์š”์‹œ๊ฐ„: {selectedTutorial.metadata.estimatedTime} + +
+
+ )} + + {/* ํŠœํ† ๋ฆฌ์–ผ ์„ ํƒ ๊ทธ๋ฆฌ๋“œ */} + {currentStep === "select" && ( +
+ {tutorialList.map((tutorial) => ( + + ))} +
+ )} +
+
+ + {/* ์Šคํ”„๋ ˆ๋“œ์‹œํŠธ ์˜์—ญ */} +
+
+ + {/* ํžˆ์Šคํ† ๋ฆฌ ํŒจ๋„ */} + setIsHistoryOpen(false)} + history={history} + onReapply={handleHistoryReapply} + onClear={handleHistoryClear} + /> + + {/* ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ์ฐฝ - ๋‹จ๊ณ„๋ณ„ ํ‘œ์‹œ */} + {showPromptInput && + (currentStep === "loaded" || currentStep === "executed") && ( +
+
+ setPrompt(e.target.value)} + onExecute={handlePromptExecute} + onHistoryToggle={handleHistoryToggle} + historyCount={history.length} + disabled={isProcessing || currentStep !== "loaded"} + tutorialPrompt={executedTutorialPrompt} + /> +
+
+ )} + + {/* ๋กœ๋”ฉ ์˜ค๋ฒ„๋ ˆ์ด */} + {isProcessing && ( +
+
+
+

+ {currentStep === "select" && "ํŠœํ† ๋ฆฌ์–ผ ์ค€๋น„ ์ค‘..."} + {currentStep === "loaded" && "๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ค‘..."} + {currentStep === "prompted" && + "๐Ÿค– AI๊ฐ€ ์ˆ˜์‹์„ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค..."} +

+
+
+ )} +
+
+ ); +}; + +export default TutorialSheetViewer; diff --git a/src/components/sheet/EditSheetViewer.tsx b/src/components/sheet/EditSheetViewer.tsx index 08e61ff..ebe66d0 100644 --- a/src/components/sheet/EditSheetViewer.tsx +++ b/src/components/sheet/EditSheetViewer.tsx @@ -19,6 +19,7 @@ import { useAppStore } from "../../stores/useAppStore"; import { rangeToAddress } from "../../utils/cellUtils"; import { CellSelectionHandler } from "../../utils/cellSelectionHandler"; import { aiProcessor } from "../../utils/aiProcessor"; +import { TutorialExecutor } from "../../utils/tutorialExecutor"; import type { HistoryEntry } from "../../types/ai"; // ์ „์—ญ ๊ณ ์œ  ํ‚ค ์ƒ์„ฑ @@ -74,7 +75,7 @@ const getGlobalState = (): GlobalUniverState => { * Presets ๊ธฐ๋ฐ˜ ๊ฐ•ํ™”๋œ ์ „์—ญ Univer ๊ด€๋ฆฌ์ž * ๊ณต์‹ ๋ฌธ์„œ ๊ถŒ์žฅ์‚ฌํ•ญ์— ๋”ฐ๋ผ createUniver ์‚ฌ์šฉ */ -const UniverseManager = { +export const UniverseManager = { // ์ „์—ญ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ (์™„์ „ ๋‹จ์ผ ์ธ์Šคํ„ด์Šค ๋ณด์žฅ) async createInstance(container: HTMLElement): Promise { const state = getGlobalState(); @@ -339,6 +340,9 @@ const TestSheetViewer: React.FC = () => { // CellSelectionHandler ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ const cellSelectionHandler = useRef(new CellSelectionHandler()); + // TutorialExecutor ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ + const tutorialExecutor = useRef(new TutorialExecutor()); + // ํžˆ์Šคํ† ๋ฆฌ ๊ด€๋ จ ์ƒํƒœ ์ถ”๊ฐ€ const [isHistoryOpen, setIsHistoryOpen] = useState(false); const [history, setHistory] = useState([]); @@ -587,6 +591,9 @@ const TestSheetViewer: React.FC = () => { // ์…€ ์„ ํƒ ํ•ธ๋“ค๋Ÿฌ ์ดˆ๊ธฐํ™” - SRP์— ๋งž์ถฐ ๋ณ„๋„ ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌ cellSelectionHandler.current.initialize(univer); + // TutorialExecutor์— Univer API ์„ค์ • + tutorialExecutor.current.setUniverAPI(univerAPI); + setIsInitialized(true); } else { console.warn("โš ๏ธ univerAPI๊ฐ€ ์ œ๊ณต๋˜์ง€ ์•Š์Œ"); @@ -862,6 +869,92 @@ const TestSheetViewer: React.FC = () => { }; }, []); + // ํŠœํ† ๋ฆฌ์–ผ ์ž๋™ ์‹คํ–‰ ๋กœ์ง + useEffect(() => { + const { tutorialSession } = appStore; + + // ํŠœํ† ๋ฆฌ์–ผ์ด ์„ ํƒ๋˜์—ˆ๊ณ  ์•„์ง ์‹คํ–‰๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ + if ( + tutorialSession.activeTutorial && + tutorialSession.execution?.status === "์ค€๋น„์ค‘" && + tutorialSession.isAutoMode && + !tutorialExecutor.current.isCurrentlyExecuting() + ) { + console.log( + "๐ŸŽฏ ํŠœํ† ๋ฆฌ์–ผ ์ž๋™ ์‹คํ–‰ ์‹œ์ž‘:", + tutorialSession.activeTutorial.metadata.title, + ); + + const executeTutorial = async () => { + try { + // ์ƒํƒœ๋ฅผ ์‹คํ–‰์ค‘์œผ๋กœ ์—…๋ฐ์ดํŠธ + appStore.updateTutorialExecution("์‹คํ–‰์ค‘", 1); + + // ํŠœํ† ๋ฆฌ์–ผ ์‹คํ–‰ + const result = await tutorialExecutor.current.startTutorial( + tutorialSession.activeTutorial!, + { + autoExecute: true, + stepDelay: 1500, + highlightDuration: 2000, + showFormula: true, + enableAnimation: true, + }, + ); + + if (result.success) { + // ์„ฑ๊ณต ์‹œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + appStore.updateTutorialExecution("์™„๋ฃŒ", 3); + + // ํ”„๋กฌํ”„ํŠธ ์ž๋™ ์ž…๋ ฅ + if (tutorialSession.activeTutorial!.prompt) { + setPrompt(tutorialSession.activeTutorial!.prompt); + setShowPromptInput(true); + } + + console.log("โœ… ํŠœํ† ๋ฆฌ์–ผ ์‹คํ–‰ ์™„๋ฃŒ:", result); + } else { + appStore.updateTutorialExecution( + "์˜ค๋ฅ˜", + undefined, + "ํŠœํ† ๋ฆฌ์–ผ ์‹คํ–‰ ์‹คํŒจ", + ); + } + } catch (error) { + console.error("โŒ ํŠœํ† ๋ฆฌ์–ผ ์‹คํ–‰ ์˜ค๋ฅ˜:", error); + appStore.updateTutorialExecution( + "์˜ค๋ฅ˜", + undefined, + error instanceof Error ? error.message : "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜", + ); + } + }; + + // Univer๊ฐ€ ์ดˆ๊ธฐํ™”๋œ ํ›„ ์‹คํ–‰ + if (getGlobalState().univerAPI) { + executeTutorial(); + } else { + // Univer API๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ + const checkInterval = setInterval(() => { + if (getGlobalState().univerAPI) { + clearInterval(checkInterval); + executeTutorial(); + } + }, 500); + + // 10์ดˆ ํ›„ ํƒ€์ž„์•„์›ƒ + setTimeout(() => { + clearInterval(checkInterval); + appStore.updateTutorialExecution( + "์˜ค๋ฅ˜", + undefined, + "Univer API ์ดˆ๊ธฐํ™” ํƒ€์ž„์•„์›ƒ", + ); + }, 10000); + } + } + }, [appStore.tutorialSession, appStore]); + return (
{/* ํ—ค๋” - App.tsx์™€ ๋™์ผํ•œ ์Šคํƒ€์ผ ์ ์šฉ */} diff --git a/src/components/sheet/PromptInput.tsx b/src/components/sheet/PromptInput.tsx index 89066e0..b26446d 100644 --- a/src/components/sheet/PromptInput.tsx +++ b/src/components/sheet/PromptInput.tsx @@ -11,6 +11,7 @@ interface PromptInputProps { maxLength?: number; onHistoryToggle?: () => void; historyCount?: number; + tutorialPrompt?: string; } /** @@ -29,6 +30,7 @@ const PromptInput: React.FC = ({ maxLength = 500, onHistoryToggle, historyCount, + tutorialPrompt, }) => { const textareaRef = useRef(null); const [, setShowCellInsertFeedback] = useState(false); @@ -182,8 +184,11 @@ const PromptInput: React.FC = ({