From 21edf27de69c58dc9667361275e2a86939ae500c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=A1=9C=EB=A1=9C?=
Date: Sun, 22 Feb 2026 11:04:36 +0900
Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EB=8C=80=EB=B3=B8=20=EC=9D=BD?=
=?UTF-8?q?=EA=B8=B0=20=EC=86=8D=EB=8F=84=20=EC=84=A4=EC=A0=95,=20?=
=?UTF-8?q?=EA=B3=84=EC=82=B0=20=EC=B6=94=EA=B0=80=20(#311)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/assets/icons/icon-setting.svg | 7 +
.../slide/script/ScriptBoxContent.tsx | 51 +++++--
.../slide/script/ScriptBulkEditModal.tsx | 17 +++
.../slide/script/ScriptReadingSpeedModal.tsx | 134 ++++++++++++++++++
src/hooks/index.ts | 1 +
src/hooks/useScriptReadingSpeed.ts | 36 +++++
src/stores/scriptReadingSpeedStore.ts | 35 +++++
src/utils/scriptDuration.test.ts | 84 +++++++++++
src/utils/scriptDuration.ts | 109 ++++++++++++++
9 files changed, 464 insertions(+), 10 deletions(-)
create mode 100644 src/assets/icons/icon-setting.svg
create mode 100644 src/components/slide/script/ScriptReadingSpeedModal.tsx
create mode 100644 src/hooks/useScriptReadingSpeed.ts
create mode 100644 src/stores/scriptReadingSpeedStore.ts
create mode 100644 src/utils/scriptDuration.test.ts
create mode 100644 src/utils/scriptDuration.ts
diff --git a/src/assets/icons/icon-setting.svg b/src/assets/icons/icon-setting.svg
new file mode 100644
index 00000000..7899ee02
--- /dev/null
+++ b/src/assets/icons/icon-setting.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/components/slide/script/ScriptBoxContent.tsx b/src/components/slide/script/ScriptBoxContent.tsx
index cbd9bb64..961ef4d3 100644
--- a/src/components/slide/script/ScriptBoxContent.tsx
+++ b/src/components/slide/script/ScriptBoxContent.tsx
@@ -6,12 +6,25 @@
* Zustand store를 통해 대본을 읽고 업데이트하며,
* debounce로 자동저장됩니다.
*/
-import { useAutoSaveScript, useSlideActions, useSlideScript } from '@/hooks';
+import { useMemo, useState } from 'react';
+
+import IconSetting from '@/assets/icons/icon-setting.svg?react';
+import { useAutoSaveScript, useScriptReadingSpeed, useSlideActions, useSlideScript } from '@/hooks';
+import { estimateScriptDurationSeconds, formatScriptDuration } from '@/utils/scriptDuration';
+
+import ScriptReadingSpeedModal from './ScriptReadingSpeedModal';
export default function ScriptBoxContent() {
+ const [isSpeedModalOpen, setIsSpeedModalOpen] = useState(false);
const script = useSlideScript();
const { updateScript } = useSlideActions();
const { autoSave, flushSave } = useAutoSaveScript();
+ const { selectedSpeed } = useScriptReadingSpeed();
+
+ const estimatedDuration = useMemo(() => {
+ const durationSeconds = estimateScriptDurationSeconds(script, selectedSpeed);
+ return formatScriptDuration(durationSeconds);
+ }, [script, selectedSpeed]);
const handleChange = (value: string) => {
updateScript(value);
@@ -19,15 +32,33 @@ export default function ScriptBoxContent() {
};
return (
-
+ >
);
}
diff --git a/src/components/slide/script/ScriptBulkEditModal.tsx b/src/components/slide/script/ScriptBulkEditModal.tsx
index 43c475ec..5516df64 100644
--- a/src/components/slide/script/ScriptBulkEditModal.tsx
+++ b/src/components/slide/script/ScriptBulkEditModal.tsx
@@ -1,8 +1,11 @@
+import { useMemo } from 'react';
import type { ChangeEvent, RefObject } from 'react';
import UploadIcon from '@/assets/icons/icon-upload.svg?react';
import { Modal, SlideImage } from '@/components/common';
import type { ScriptBulkEditPreviewItem } from '@/hooks/useScriptBulkEdit';
+import { useScriptReadingSpeed } from '@/hooks/useScriptReadingSpeed';
+import { estimateScriptsDurationSeconds, formatScriptDuration } from '@/utils/scriptDuration';
interface ScriptBulkEditModalProps {
isOpen: boolean;
@@ -31,6 +34,15 @@ function ScriptBulkEditModal({
onFileChange,
onScriptChange,
}: ScriptBulkEditModalProps) {
+ const { selectedSpeed } = useScriptReadingSpeed();
+ const totalDuration = useMemo(() => {
+ const durationSeconds = estimateScriptsDurationSeconds(
+ previewItems.map((item) => item.script),
+ selectedSpeed,
+ );
+ return formatScriptDuration(durationSeconds);
+ }, [previewItems, selectedSpeed]);
+
return (
{selectedFileName}
) : null}
+
+ 전체 예상 읽기 시간:{' '}
+ {totalDuration}{' '}
+ (분당 {selectedSpeed}자 기준)
+
From d44d9158d51831c8887826239520e82252d9e664 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=A1=9C=EB=A1=9C?=
Date: Sun, 22 Feb 2026 11:11:52 +0900
Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=EB=AA=A8=EB=8B=AC=20=EB=8B=AB?=
=?UTF-8?q?=ED=9E=98=20=EC=8B=9C=20=EC=A0=84=EC=B2=B4=20=EB=8C=80=EB=B3=B8?=
=?UTF-8?q?=20=EC=8B=9C=EA=B0=84=20=EC=9C=A0=EC=A7=80=20(#311)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/slide/script/ScriptReadingSpeedModal.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/components/slide/script/ScriptReadingSpeedModal.tsx b/src/components/slide/script/ScriptReadingSpeedModal.tsx
index 04cee0a0..35acdd75 100644
--- a/src/components/slide/script/ScriptReadingSpeedModal.tsx
+++ b/src/components/slide/script/ScriptReadingSpeedModal.tsx
@@ -18,8 +18,7 @@ interface ScriptReadingSpeedModalProps {
export default function ScriptReadingSpeedModal({ isOpen, onClose }: ScriptReadingSpeedModalProps) {
const { projectId } = useParams<{ projectId: string }>();
- const activeProjectId = isOpen ? (projectId ?? '') : '';
- const { data: projectScripts } = useProjectScripts(activeProjectId);
+ const { data: projectScripts } = useProjectScripts(projectId ?? '');
const slideId = useSlideId();
const script = useSlideScript();
const { speedOptions, selectedSpeed, selectedPreset, setSelectedSpeed } = useScriptReadingSpeed();
From 66ae9a799331a3950c1ae5f7543046864050df42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=A1=9C=EB=A1=9C?=
Date: Sun, 22 Feb 2026 11:13:51 +0900
Subject: [PATCH 4/5] =?UTF-8?q?fix:=20Gemini=20=EB=A6=AC=EB=B7=B0=20?=
=?UTF-8?q?=EC=BD=94=EB=A9=98=ED=8A=B8=20=EB=B0=98=EC=98=81=20(#311)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../slide/script/ScriptReadingSpeedModal.tsx | 12 ++++++++++--
src/hooks/useScriptReadingSpeed.ts | 4 ++--
2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/components/slide/script/ScriptReadingSpeedModal.tsx b/src/components/slide/script/ScriptReadingSpeedModal.tsx
index 35acdd75..b2102f11 100644
--- a/src/components/slide/script/ScriptReadingSpeedModal.tsx
+++ b/src/components/slide/script/ScriptReadingSpeedModal.tsx
@@ -1,6 +1,8 @@
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
+import clsx from 'clsx';
+
import { Dropdown, Modal } from '@/components/common';
import type { DropdownItem } from '@/components/common/Dropdown';
import { useProjectScripts, useScriptReadingSpeed, useSlideId, useSlideScript } from '@/hooks';
@@ -65,11 +67,17 @@ export default function ScriptReadingSpeedModal({ isOpen, onClose }: ScriptReadi
trigger={({ isOpen }) => (
{presetLabel}