diff --git a/app/service/Interaction/ScriptService.ts b/app/service/Interaction/ScriptService.ts index 7c988be..a9d8519 100644 --- a/app/service/Interaction/ScriptService.ts +++ b/app/service/Interaction/ScriptService.ts @@ -99,7 +99,6 @@ export interface UseScriptService { * 包括剧本生成、项目创建、剧本保存等功能 */ export const useScriptService = (): UseScriptService => { - console.log('useScriptService----9((@@@@@@@@@@@@@@@@@@'); // 响应式状态 const [loading, setLoading] = useState(false); const [synopsis, setSynopsis] = useState(""); @@ -215,7 +214,6 @@ export const useScriptService = (): UseScriptService => { // 获取解析后的故事详情 const storyDetails = newScriptEditUseCase.getStoryDetails(); - setSynopsis(storyDetails.synopsis || ""); setCategories(storyDetails.categories || []); setProtagonist(storyDetails.protagonist || ""); @@ -231,7 +229,7 @@ export const useScriptService = (): UseScriptService => { setLoading(false); } }, - [projectId, scriptEditUseCase] + [generateScriptFromIdea] ); /** @@ -392,7 +390,6 @@ export const useScriptService = (): UseScriptService => { const setAnyAttributeWrapper = useCallback( (type: string, value: SetStateAction, tags?: string[]) => { - console.log('setAnyAttributeWrapper', type); if (type === 'synopsis') { setFieldOld(synopsis) scriptEditUseCase.replaceScript(fieldOld,synopsis) @@ -492,8 +489,7 @@ export const useScriptService = (): UseScriptService => { ]; // 筛选出有内容的block const filteredArr = arr.filter(item => (item.content.length > 0 && item.content[0].text !== '')); - console.log('scriptBlocksMemo 所有关联数据', synopsis, categories, protagonist, incitingIncident, problem, conflict, stakes, characterArc); - console.log('scriptBlocksMemo', filteredArr); + console.log('scriptBlocksMemo', JSON.parse(JSON.stringify(filteredArr))); return filteredArr; }, [ synopsis, diff --git a/app/service/Interaction/ShotService.ts b/app/service/Interaction/ShotService.ts index 0d88cbb..653bbe0 100644 --- a/app/service/Interaction/ShotService.ts +++ b/app/service/Interaction/ShotService.ts @@ -1,7 +1,7 @@ import { useState, useCallback } from "react"; import { VideoSegmentEditUseCase } from "../usecase/ShotEditUsecase"; import { VideoSegmentEntity } from "../domain/Entities"; -import { ContentItem, LensType } from "../domain/valueObject"; +import { LensType } from "../domain/valueObject"; /** * 视频片段服务Hook接口 @@ -15,8 +15,6 @@ export interface UseShotService { videoSegments: VideoSegmentEntity[]; /** 当前选中的视频片段 */ selectedSegment: VideoSegmentEntity | null; - /** 错误信息 */ - error: string | null; // 操作方法 /** 获取视频片段列表 */ @@ -38,8 +36,10 @@ export interface UseShotService { abortOperation: () => void; /** 设置选中的视频片段 */ setSelectedSegment: (segment: VideoSegmentEntity | null) => void; - /** 清除错误信息 */ - clearError: () => void; + /** 添加新镜头到选中的视频片段 */ + addNewLens: () => void; + /** 删除指定镜头 */ + deleteLens: (lensName: string) => void; } /** @@ -52,10 +52,9 @@ export const useShotService = (): UseShotService => { const [loading, setLoading] = useState(false); const [videoSegments, setVideoSegments] = useState([]); const [selectedSegment, setSelectedSegment] = useState(null); - const [error, setError] = useState(null); // UseCase实例 - const [shotEditUseCase] = useState( + const [vidoEditUseCase] = useState( new VideoSegmentEditUseCase() ); @@ -67,19 +66,16 @@ export const useShotService = (): UseShotService => { async (projectId: string): Promise => { try { setLoading(true); - setError(null); - const segments = await shotEditUseCase.getVideoSegmentList(projectId); + const segments = await vidoEditUseCase.getVideoSegmentList(projectId); setVideoSegments(segments); } catch (error) { - const errorMessage = error instanceof Error ? error.message : "获取视频片段列表失败"; - setError(errorMessage); console.error("获取视频片段列表失败:", error); } finally { setLoading(false); } }, - [shotEditUseCase] + [vidoEditUseCase] ); /** @@ -99,9 +95,8 @@ export const useShotService = (): UseShotService => { ): Promise => { try { setLoading(true); - setError(null); - const regeneratedSegment = await shotEditUseCase.regenerateVideoSegment( + const regeneratedSegment = await vidoEditUseCase.regenerateVideoSegment( shotPrompt, shotId, roleReplaceParams, @@ -122,15 +117,13 @@ export const useShotService = (): UseShotService => { return regeneratedSegment; } catch (error) { - const errorMessage = error instanceof Error ? error.message : "重新生成视频片段失败"; - setError(errorMessage); console.error("重新生成视频片段失败:", error); throw error; } finally { setLoading(false); } }, - [shotEditUseCase] + [vidoEditUseCase] ); /** @@ -148,9 +141,8 @@ export const useShotService = (): UseShotService => { ): Promise => { try { setLoading(true); - setError(null); - const optimizedLensData = await shotEditUseCase.optimizeVideoContent( + const optimizedLensData = await vidoEditUseCase.optimizeVideoContent( shotId, userRequirement, lensData @@ -161,23 +153,21 @@ export const useShotService = (): UseShotService => { return optimizedLensData; } catch (error) { - const errorMessage = error instanceof Error ? error.message : "AI优化视频内容失败"; - setError(errorMessage); console.error("AI优化视频内容失败:", error); throw error; } finally { setLoading(false); } }, - [shotEditUseCase] + [vidoEditUseCase] ); /** * 中断当前操作 */ const abortOperation = useCallback((): void => { - shotEditUseCase.abortOperation(); + vidoEditUseCase.abortOperation(); setLoading(false); - }, [shotEditUseCase]); + }, [vidoEditUseCase]); /** * 设置选中的视频片段 @@ -187,24 +177,85 @@ export const useShotService = (): UseShotService => { }, []); /** - * 清除错误信息 + * 添加新镜头到选中的视频片段 + * @description 在selectedSegment的lens数组中添加一个新的空镜头,镜头名称按顺序命名 */ - const clearError = useCallback((): void => { - setError(null); - }, []); + const addNewLens = useCallback((): void => { + if (!selectedSegment) { + console.warn("没有选中的视频片段,无法添加镜头"); + return; + } + + // 计算下一个镜头编号 + const currentLensCount = selectedSegment.lens.length; + const newLensName = `镜头${currentLensCount + 1}`; + + // 创建新的空镜头 + const newLens = new LensType(newLensName, "", []); + + // 创建更新后的片段 + const updatedSegment: VideoSegmentEntity = { + ...selectedSegment, + lens: [...selectedSegment.lens, newLens] + }; + + // 批量更新状态,避免多次重渲染 + setSelectedSegment(updatedSegment); + setVideoSegments(prev => { + const segmentIndex = prev.findIndex(segment => segment.id === selectedSegment.id); + if (segmentIndex === -1) return prev; + + const newSegments = [...prev]; + newSegments[segmentIndex] = updatedSegment; + return newSegments; + }); + }, [selectedSegment]); + + /** + * 删除指定镜头 + * @param lensName 要删除的镜头名称 + */ + const deleteLens = useCallback((lensName: string): void => { + if (!selectedSegment) { + console.warn("没有选中的视频片段,无法删除镜头"); + return; + } + + // 过滤掉指定名称的镜头并重新排序 + const updatedLens = selectedSegment.lens + .filter(lens => lens.name !== lensName) + .map((lens, index) => new LensType(`镜头${index + 1}`, lens.script, lens.content)); + + // 创建更新后的片段 + const updatedSegment: VideoSegmentEntity = { + ...selectedSegment, + lens: updatedLens + }; + + // 批量更新状态,避免多次重渲染 + setSelectedSegment(updatedSegment); + setVideoSegments(prev => { + const segmentIndex = prev.findIndex(segment => segment.id === selectedSegment.id); + if (segmentIndex === -1) return prev; + + const newSegments = [...prev]; + newSegments[segmentIndex] = updatedSegment; + return newSegments; + }); + }, [selectedSegment]); return { // 响应式状态 loading, videoSegments, selectedSegment, - error, // 操作方法 getVideoSegmentList, regenerateVideoSegment, optimizeVideoContent, abortOperation, setSelectedSegment: setSelectedSegmentHandler, - clearError, + addNewLens, + deleteLens, }; }; diff --git a/app/service/domain/valueObject.ts b/app/service/domain/valueObject.ts index 4452a10..36d8b06 100644 --- a/app/service/domain/valueObject.ts +++ b/app/service/domain/valueObject.ts @@ -115,7 +115,9 @@ export class StoryDetails { * @param text 剧本文本 */ updateScript(text: string) { + console.log('text.length', text.length) const scriptObject = this.createFromText(text); + console.log('scriptObject', scriptObject) this.setProperties(scriptObject); return scriptObject; } diff --git a/app/service/test/Script.test.ts b/app/service/test/Script.test.ts index 2bea7a0..7bf3e22 100644 --- a/app/service/test/Script.test.ts +++ b/app/service/test/Script.test.ts @@ -14,9 +14,6 @@ jest.mock("../../../api/constants", () => ({ })); import { getProjectScript, - abortVideoTask, - pausePlanFlow, - resumePlanFlow, } from "../../../api/video_flow"; import {StoryDetails } from "../domain/valueObject"; import { ScriptEditUseCase } from "../usecase/ScriptEditUseCase"; @@ -59,7 +56,7 @@ describe("ScriptService 业务逻辑测试", () => { }); describe("解析测试", () => { - const scriptText = `"**Logline**\nAn anxious young man, facing what he believes is a crushing public rejection on a first date, must overcome his deepest fear of inadequacy to take a chance on a real connection.\n\n**Core Elements**\n\n1. **Protagonist:**\n * **Core Identity:** LI WEI (李伟), 25, male, an introverted and socially anxious programmer in a modern metropolis. He is neat but unremarkable, hiding his insecurity behind a pair of glasses and a quiet demeanor.\n * **Initial State & Flaw:** He is at a coffee shop for a first date, fraught with nervous anticipation. His core flaw is a profound **fear of inadequacy and rejection**, a deep-seated belief that he is fundamentally uninteresting and will inevitably disappoint others.\n\n2. **The Inciting Incident:**\n * (State Clearly) A beautiful, confident woman, XIAOQIAN (小倩), whom he believes is his date, enters the coffee shop. However, she sits at a different table and is soon joined by a handsome, charismatic man. Their immediate, easy chemistry confirms Li Wei's worst fears. He has been stood up for someone better, or so he believes. This event is a passive, public humiliation that directly preys on his core flaw.\n\n3. **The Problem & New Goal:**\n * (State Clearly) The problem is no longer about impressing a date. It is a crushing psychological crisis: \"How do I escape this situation without confirming my own deeply-held belief that I am a failure?\" His new, urgent goal is to simply survive the moment and leave with his self-worth intact.\n\n4. **Conflict & Obstacles:**\n * **Primary Conflict:** An intense internal battle between Li Wei's crippling self-doubt and the flicker of a desire to not be defined by it.\n * **Key Obstacles:** 1) The happy couple's laughter, which serves as an auditory torment. 2) The well-meaning WAITRESS, whose attention amplifies his isolation.\n\n5. **The Stakes:**\n * **What is at stake?:** If he succeeds in leaving with composure, he salvages a fragile piece of his dignity. If he fails—fleeing awkwardly or breaking down—he will permanently cement his narrative of failure, making it exponentially harder to ever attempt a social connection again. He will lose the courage to try.\n\n6. **Character Arc Accomplished:**\n * (State the Result Clearly) He confronts his fear of inadequacy head-on. After receiving a text from his *actual* date, who is at the wrong location, he makes a conscious choice. Instead of succumbing to his anxiety and cancelling, he takes a small but monumental step. He deletes a self-deprecating draft, sends a simple, confident reply, and walks out, ready to face the unknown. He has transformed from someone paralyzed by fear into someone willing to act despite it.\n\n**GENRE:** Drama\n\n---\n\n**SCENE 1**\n\nINT. \"CORNER PERCH\" COFFEE SHOP - DAY\n\n**Scene Transition:** An establishing shot of a sun-drenched, stylishly minimalist coffee shop. The camera finds our protagonist, LI WEI, already seated, having arrived early to stake out a \"safe\" spot. The scene is set to capture the quiet, hopeful tension before an anticipated event.\n\nSOUND of distant traffic, the gentle hiss of an espresso machine, soft indie music\n\nThe afternoon sun streams through a large window, illuminating dust motes dancing in the air. The coffee shop is a haven of warm wood and the rich aroma of roasted beans.\n\nLI WEI (25), neat in a plain grey t-shirt, sits alone at a small two-person table. He polishes his glasses with a napkin, an unnecessary, repetitive motion.\n\nAn untouched glass of water sweats onto the table.\n\nCLOSE-UP - LI WEI'S PHONE\nHis thumb hovers over a messaging app. The profile picture of his date, \"XIAOQIAN,\" is a bright, smiling face. He types a message: \"I'm here. At the table by the window.\" He hesitates. Deletes it.\n\nHe forces a casual posture, trying to look absorbed in his phone, but his eyes dart towards the door every time it chimes.\n\n**LI WEI (V.O.)**\nJust be normal. Don't look desperate.\nJust... a guy enjoying coffee. Alone.\n\nHe picks up his water glass, but his hand trembles slightly, rattling the ice. He puts it down too quickly. A little water sloshes over the rim. He dabs at it furiously with a napkin.\n\n---\n\n**SCENE 2**\n\nINT. \"CORNER PERCH\" COFFEE SHOP - DAY\n\n**Scene Transition:** The chiming of the door from the previous scene directly leads to the entrance of a new character. Li Wei's focus, and therefore the camera's, immediately shifts from his internal world to this external arrival, escalating the scene's tension.\n\nThe bell on the door CHIMES.\n\nLi Wei's head snaps up.\n\nXIAOQIAN (24) enters. She is exactly like her profile picture—radiant, dressed in a stylish floral dress. She scans the room.\n\nLi Wei's heart hammers. He makes to raise a hand, a small, aborted wave.\n\nBut her eyes glide right past him. She smiles, a flash of genuine warmth, at a table across the room. She walks over to an empty table for two, not his.\n\nLi Wei's half-raised hand drops to the table with a soft THUD. His face freezes.\n\nHe watches, paralyzed, as she sits down, places her bag on the empty chair, and pulls out her phone. She is waiting.\n\nThe WAITRESS (40s, kindly) approaches Li Wei's table.\n\n**WAITRESS**\nReady to order something else, dear?\nOr still waiting for your friend?\n\n**LI WEI**\n(A whisper)\nStill waiting. Thank you.\n\nThe waitress nods sympathetically and moves away. The words hang in the air, a public declaration of his solitude.\n\nA moment later, the door CHIMES again. A HANDSOME MAN (26), confident and laughing, enters and walks directly to Xiaoqian's table.\n\n**HANDSOME MAN**\nSorry! The traffic was a nightmare.\nYou look amazing.\n\n**XIAOQIAN**\n(Laughing)\nYou're lucky you're so handsome.\nI almost left.\n\nThey lean in, their conversation a low, happy murmur. Their chemistry is instant, effortless.\n\nEXTREME CLOSE-UP - LI WEI'S FACE\nA universe of humiliation plays out in his eyes. He stares down at his phone, the screen now a black mirror reflecting his own strained face.\n\n---\n\n**SCENE 3**\n\nINT. \"CORNER PERCH\" COFFEE SHOP - DAY\n\n**Scene Transition:** Li Wei remains frozen, trapped by the previous scene's devastating turn. The camera stays tight on him, emphasizing his psychological confinement. The passage of time is marked only by the shifting light and the sounds from the other table, creating a slow, agonizing development of his internal state.\n\nSOUND of the couple's intermittent laughter, a sharp, painful punctuation mark.\n\nMinutes crawl by. Li Wei doesn't move. He scrolls aimlessly through his phone, not seeing anything. The screen's glow is the only light on his face, which has gone pale.\n\nHe risks a glance. The couple is now sharing a piece of cake, laughing as the man wipes a smudge of cream from her nose. It is a moment of perfect, casual intimacy.\n\nLi Wei flinches as if struck. He needs to escape.\n\nHe starts drafting a text to a friend.\n\"Worst day ever. You won't believe...\" He stops. Deletes it. The shame is too much to even articulate.\n\n**LI WEI (V.O.)**\nJust go. Get up and walk out.\nWhy can't you just go?\n\nHis body won't obey. He is pinned to his chair by the weight of his own failure. He imagines walking past them, feeling their eyes on his back. The thought is unbearable.\n\nSuddenly, his phone VIBRATES violently on the table. A sharp, loud BUZZ that cuts through the cafe's murmur.\n\nHe startles. The couple looks over for a second. Li Wei's face burns with shame.\n\nHe looks at the screen. A new message. From a number he doesn't recognize, but the profile picture is different. More candid, less polished, but still her. The name reads: \"Qianqian.\"\n\nTHE MESSAGE: \"Hey! So sorry, I'm an idiot! I'm at the 'Corner Cup' not the 'Corner Perch'! They're across the street from each other! Running over now, so so sorry! :( \"\n\nLi Wei stares at the text. His brain reboots.\nThe 'Corner Cup'. Not the 'Corner Perch'.\nThis wasn't rejection. It was... logistics.\n\nA wave of dizzying relief washes over him, followed immediately by a new surge of anxiety. He has to meet her now. After all this.\n\nHe opens a reply, his thumbs shaking.\n\"It's okay but honestly I was about to leave, this has been a really awkward...\"\n\nHe stops. He looks at the words on his screen. A story of defeat.\nHe looks up, past his phone, at the couple across the room. They are just two people. Happy, yes. But just people.\nHe looks at the new message from \"Qianqian.\" An apology. An emoji. A human mistake.\n\nHe slowly, deliberately, deletes his entire draft.\n\n---\n\n**SCENE 4**\n\nINT. \"CORNER PERCH\" COFFEE SHOP - DAY\n\n**Scene Transition:** The decision made at the end of Scene 3 provides the direct motivation for action. The camera pulls back, giving Li Wei space for the first time. His movement from the chair to the door is the scene's entire narrative arc—a physical journey mirroring his internal one.\n\nLi Wei takes a deep breath. It's the first real breath he's taken all day.\n\nCLOSE-UP - HIS THUMBS\nHe types a new message. Short. Simple.\n\nREPLY: \"No worries. I'm at the window. See you soon.\"\n\nHe hits send without hesitating.\n\nThen, he does the impossible. He stands up. He pushes his chair in neatly. He places a few bills on the table for the water.\n\nHe walks towards the door.\nHis path takes him directly past the other couple's table.\nFor a fleeting second, he almost looks at them. But he doesn't. He looks straight ahead, at the door, at the world outside. They are no longer the center of his universe. They are just background noise.\n\nHe pushes the door open. The bell CHIMES, this time a sound of release.\n\nEXT. STREET - DAY\n\n**Scene Transition:** An \"empty shot\" transition. The camera holds on the 'Corner Perch' door as it closes, then PANS across the busy street to the bright, welcoming sign of the 'Corner Cup' cafe on the other side. This visually connects the two locations and bridges the emotional journey.\n\nLi Wei steps out into the bright, noisy street. The sun hits his face. He blinks, adjusting to the light. He looks across the street at the other cafe.\n\nHe takes a step off the curb.\n\n**LI WEI (V.O.)**\nJust a guy.\nGoing to get a coffee.\n\nHe walks across the street, his stride not confident, not perfect, but steady. He is moving forward.\n\nFADE TO BLACK.\n\n---\n\n### Part 2: Addendum for Director & Performance\n\n**【Decoding the Directing Style】**\n\n* **Core Visual Tone:** Employ a style of urban realism with high subjectivity. The color palette inside the 'Perch' should be warm, almost oppressively so, to contrast with Li Wei's cold anxiety. Use a shallow depth of field to isolate Li Wei, blurring the rest of the cafe into an indistinct background until the other couple becomes his sharp, painful focus.\n* **Key Scene Treatment Suggestion:** In Scene 3, during Li Wei's paralysis, use a long, static take. The only movement should be his thumbs on the phone and his darting eyes. The sound design is crucial here: amplify the couple's laughter and the clinking of their silverware until it feels like a psychological attack. The sudden, loud vibration of his phone should be a jump-scare, shattering the agonizing stillness and jolting both him and the audience.\n\n**【The Core Performance Key】**\n\n* **The Character's Physicality:** Li Wei's body language should be a portrait of containment. His shoulders are slightly hunched, his movements are small and economical as if trying to take up less space. In Scene 1, his fidgeting with the napkin and glass isn't just nervousness; it's a desperate attempt to control an environment he feels has no place for him. His transformation in Scene 4 is marked by the simple act of standing up straight and walking with a directed purpose, not looking down.\n* **Subtextual Drive:** The subtext of Li Wei's internal monologue and actions until the final text is: \"Confirm my inadequacy so I can go home.\" He is subconsciously looking for proof that he should give up. The performance must show this self-sabotage. The final text message, \"See you soon,\" has the subtext: \"I am willing to be proven wrong about myself.\"\n\n**【Connection to the Zeitgeist】**\n\n* This story of perceived public failure and digital miscommunication resonates deeply in an age of curated online identities, where the gap between our idealized digital selves and our anxious, real-world selves creates constant, quiet friction."`; + const scriptText = `"** Core Elements** \n\n1. Protagonist:\n\nCore Identity: LEO (24), male, a quiet and introverted app developer. He has a kind face but carries a perpetual tension in his shoulders. He lives in a modern, impersonal city. His personality is anxious, thoughtful, and overly self-critical.\n\nInitial State & Flaw: Leo is sitting alone, waiting for a first date he's genuinely excited about. His core flaw is a crippling fear of inadequacy and social rejection. He believes he is fundamentally uninteresting and that people will inevitably find him wanting, causing him to hide behind a curated digital persona.\n\n2. The Inciting Incident:\n\n(State Clearly) His date, CHLOE (23), arrives, but she is not alone. She is accompanied by MARK (25), a confident, handsome man whom she introduces as just a \"friend.\" This event shatters Leo's hopeful anticipation and directly preys on his fear that he is not enough on his own.\n\n3. The Problem & New Goal:\n\n(State Clearly) The problem is no longer about winning a date; it's about surviving a socially excruciating and humiliating situation. Leo's new, urgent goal is to navigate this tense encounter, understand what is truly happening, and find a way to exit with his dignity intact.\n\n4. Conflict & Obstacles:\n\nPrimary Conflict: The conflict is both interpersonal and internal. Leo vs. Mark (a battle for presence and respect). Leo vs. Chloe (her perceived cruelty or obliviousness). And most importantly, Leo vs. Himself (his deep-seated social anxiety versus a nascent desire for self-respect).\n\nKey Obstacles: 1) The public setting of the coffee shop prevents any honest emotional outburst. 2) Mark's charismatic dominance of the conversation constantly sidelines Leo.\n\n5. The Stakes:\n\nWhat is at stake?: If Leo succeeds in asserting himself, he gains a monumental victory over his internal demons, proving to himself that his worth isn't determined by others' approval. If he fails—by crumbling or fleeing without a word—he will permanently reinforce his self-loathing belief that he is inadequate, losing not just a potential relationship, but his own chance at self-respect.\n\n6. Character Arc Accomplished:\n\n(State the Result Clearly) Leo achieves his arc by making a choice rooted in self-worth, not fear. Instead of enduring the humiliation or running away, he calmly and directly addresses the absurdity of the situation, pays for his drink, and leaves. This quiet act of defiance is his triumph. He has transformed from someone fearing rejection to someone who rejects an unacceptable situation.\n\n**GENRE:** Drama\n\n---\n\n**SCENE 1**\n\nINT. \"THE DAILY GRIND\" COFFEE SHOP - DAY\n\n**Scene Transition:** An establishing shot of the coffee shop, a trendy place with exposed brick and the low HUM of conversation. The camera finds our character already in place, his presence setting the scene's initial, anxious tone.\n\nSunlight, thick with dust motes, streams through a large window. The air smells of burnt coffee and steamed milk.\n\nLEO (24), neat but nervous, sits at a small table for two. His posture is closed-off, shoulders hunched.\n\nAn untouched cappuccino sweats before him.\n\nHis phone is in his hands. An oasis.\n\nEXTREME CLOSE-UP - PHONE SCREEN\nA messaging app is open. A conversation with \"Chloe.\" Her profile picture is bright, confident. His last message, \"Here! Got a table by the window :)\" was sent 15 minutes ago. It is marked \"Read.\"\n\nLeo's thumb hovers, composing a new message, then deleting it. He locks the screen. Unlocks it. A nervous tic.\n\nHe looks toward the door. Each time it opens with a JINGLE of a small bell, his shoulders tense. A family enters. He slumps back down.\n\nCLOSE-UP - LEO\nHis eyes are full of a painful, familiar hope.\n\n**LEO (V.O.)**\nJust be normal.\nDon't say anything weird.\nPlease just like me.\n\nHe forces a sip of his now-lukewarm cappuccino. He grimaces slightly. The bell JINGLES again. He doesn't look up this time, protecting himself from another false alarm.\n\n**CHLOE (O.S.)**\nLeo?\n\nLeo's head snaps up.\n\nCHLOE (23) stands there. She matches her photo. Bright, energetic. A wave of relief washes over Leo's face. He starts to stand, a smile forming.\n\nThen he sees him.\n\nMARK (25), handsome and effortlessly cool, stands beside her. He scans the cafe with an owner's ease. Chloe's hand is briefly on his arm.\n\nLeo freezes mid-motion, half-standing. The smile dies on his lips.\n\n---\n\n**SCENE 2**\n\nINT. \"THE DAILY GRIND\" COFFEE SHOP - MOMENTS LATER\n\n**Scene Transition:** The characters are now locked in their initial, awkward arrangement. The previous scene's hope has curdled into the raw tension that will drive this new scene.\n\nLeo slowly sinks back into his chair. Chloe and Mark approach the small table. The space immediately feels crowded, invasive.\n\n**CHLOE**\n(A bit too cheerful)\nSorry we're late.\nRan into my friend, Mark.\nThis is Mark.\n\nMark offers a lazy, confident smile. He doesn't offer to shake hands.\n\n**MARK**\nHey, man. What's up?\n\n**LEO**\n(Muted)\nHey. Leo.\n\nMark pulls a chair from a nearby table. He sits beside Chloe, not across from her. They form a unit. Leo is the satellite.\n\n**CHLOE**\n(To Mark)\nI told you he was cute.\n(To Leo)\nI hope you don't mind.\nHe was just heading this way.\n\nThe question isn't a question. It's a statement. Leo just nods, a knot forming in his stomach. The barista calls an order. The sound is jarringly loud.\n\n**MARK**\n(Leaning back)\nSo. Chloe tells me you make apps.\nLike, games and stuff?\n\n**LEO**\nNo. Mostly productivity tools.\nOrganizational software.\n\nMark smirks, sharing a private look with Chloe.\n\n**MARK**\nRight. Fun stuff.\n\nThe subtext is a hammer blow. Leo's hands retreat from the table, hiding in his lap. He feels his face flush with heat.\n\n---\n\n**SCENE 3**\n\nINT. \"THE DAILY GRIND\" COFFEE SHOP - LATER\n\n**Scene Transition:** Five minutes have passed. The forced conversation has created a palpable sense of dread. The camera pushes in slightly, tightening the frame to emphasize Leo's growing claustrophobia.\n\nEmpty sugar packets are shredded into a small pile by Leo's cup. A nervous habit made manifest.\n\nMark is telling a story. It's loud, animated. He and Chloe are laughing. Leo is a spectator at his own date.\n\n**MARK**\n...so I tell the guy, that's not a bug.\nThat's a feature!\n\nChloe laughs, a genuine, loud laugh. It's a sound Leo thought was reserved for him.\n\n**CHLOE**\n(Wiping a tear)\nYou're terrible.\nHe actually believed you?\n\n**MARK**\nFor a second.\nHe was a coder type. Like Leo.\nYou know. A bit literal.\n\nMark winks at Leo. It's not friendly. It's a display of dominance.\n\nThis is the turning point.\n\nCLOSE-UP - LEO\nThe anxiousness in his eyes doesn't vanish. It crystallizes. It hardens into something new. A quiet, cold clarity. The fog of his fear has burned away, revealing the stark, humiliating landscape.\n\nHe stops shredding the packet. His hands become perfectly still on the table.\n\n**LEO (V.O.)**\nOh.\nI see now.\n\nHe looks from Mark's smug face to Chloe's smiling one. She doesn't meet his gaze. She is completely absorbed by Mark.\n\nLeo slowly, deliberately, reaches for his wallet.\n\n---\n\n**SCENE 4**\n\nINT. \"THE DAILY GRIND\" COFFEE SHOP - MOMENTS LATER\n\n**Scene Transition:** Building directly from Leo's internal decision, this scene is the physical execution of that choice. The stillness of the previous shot is broken by his decisive action.\n\nThe sound of a chair scraping against the floor cuts through their laughter.\n\nLeo is standing. His posture is different. Shoulders back. Chin up. He is no longer hunched.\n\nChloe and Mark look up, surprised by the interruption.\n\n**CHLOE**\nYou okay?\nGoing to the bathroom?\n\nLeo places a ten-dollar bill on the table. Enough for his coffee and a generous tip. The gesture is precise. Final.\n\n**LEO**\nI don't think I am.\n\nHe looks directly at Chloe. His gaze is not angry. It is clear.\n\n**LEO**\nThis was a mistake.\nEnjoy your coffee.\n\nHe doesn't look at Mark. He doesn't need to. Mark is irrelevant to this decision.\n\nLeo turns and walks toward the door. His steps are even, measured. Not a retreat. An exit.\n\nThe bell on the door JINGLES softly as he leaves.\n\nThe camera holds on the table. On the shocked, confused faces of Chloe and Mark. For the first time, they are silent. The ten-dollar bill sits between them, an indictment.\n\nFADE TO BLACK.\n\n---\n\n### Part 2: Addendum for Director & Performance\n\n**【Decoding the Directing Style】**\n\n* **Core Visual Tone:** A warm, inviting coffee shop palette (ambers, browns) that feels increasingly suffocating. Use a shallow depth of field to keep Leo emotionally isolated, with the background and even other characters often soft-focused, reflecting his internal, anxious world. The camera should be mostly static and observational, making his final, deliberate movements feel seismic.\n\n* **Key Scene Treatment Suggestion:** In Scene 4, when Leo stands, the camera should be at a low angle, making him appear taller and more resolute than before. The action of him placing the money on the table should be captured in an EXTREME CLOSE-UP, the sound of the bill hitting the wood amplified. After he leaves, hold a WIDE SHOT on Chloe and Mark at the table, emphasizing the new, awkward space he has left behind. The blocking is a full reversal: he begins trapped *by* the table and ends by using the table as the stage for his departure.\n\n**【The Core Performance Key】**\n\n* **The Character's Physicality:** Leo's performance is a journey from contraction to expansion. He begins hunched, making himself small, hands hidden or fidgeting (shredding packets). His core action is avoidance. His transformation is marked by stillness (Scene 3) followed by deliberate, open movement (Scene 4). His final walk should be straight-backed, a stark contrast to his initial posture.\n\n* **Subtextual Drive:** For the first three scenes, Leo's subtext is a desperate plea: \"Please see me. Please choose me. Am I good enough?\" In Scene 3, after Mark's \"coder type\" comment, the subtext shifts instantly to a declarative statement: \"I see you. And I am better than this.\" His final lines are not an attack, but the calm, verbal expression of that new internal truth.\n\n**【Connection to the Zeitgeist】**\n\n* This story captures the profound anxiety and disposability inherent in modern app-based dating, where individuals are often treated as interchangeable options rather than human beings deserving of basic respect."`; let protagonistText = "", incitingIncidentText = "", @@ -81,8 +78,8 @@ describe("解析测试", () => { categories = s .extractContentByHeader(scriptText, "GENRE") .split(",") - // .map((s) => s.trim()) - // .filter((s) => s.length > 0); + .map((s) => s.trim()) + .filter((s) => s.length > 0); console.log("categories", categories); protagonist = s.extractContentByHeader(scriptText, "Core Identity:"); console.log("protagonist", protagonist); diff --git a/app/service/test/Shot.test.ts b/app/service/test/Shot.test.ts index 7dd91ef..e69de29 100644 --- a/app/service/test/Shot.test.ts +++ b/app/service/test/Shot.test.ts @@ -1,13 +0,0 @@ -import { useVideoSegmentService } from '../Interaction/ShotService'; - -describe('VideoSegmentService 测试', () => { - it('初始化服务实例', () => { - const videoSegmentService = useVideoSegmentService(); - expect(videoSegmentService).toBeDefined(); - }); - - it('获取视频片段列表', () => { - const videoSegmentService = useVideoSegmentService(); - expect(typeof videoSegmentService.fetchVideoSegmentList).toBe('function'); - }); -}); diff --git a/components/script-renderer/ScriptRenderer.tsx b/components/script-renderer/ScriptRenderer.tsx index 416357b..bfbb329 100644 --- a/components/script-renderer/ScriptRenderer.tsx +++ b/components/script-renderer/ScriptRenderer.tsx @@ -150,7 +150,7 @@ export const ScriptRenderer: React.FC = ({ data, setIsPause onChange={handleBlockTextChange(block)} onBlur={handleBlockTextBlur(block)} autoFocus={true} - className="block w-full min-h-[120px] bg-white/5 backdrop-blur-md p-4 text-white/90 + className="block w-full min-h-[120px] bg-white/5 backdrop-blur-md p-4 text-white/90 rounded-lg border-unset outline-none pb-12 whitespace-pre-wrap break-words" placeholder="" @@ -166,7 +166,7 @@ export const ScriptRenderer: React.FC = ({ data, setIsPause {addThemeTag.map((item, index) => (
{item} - + handleThemeTagChange(addThemeTag.filter(v => v !== item)) } />
@@ -234,11 +234,11 @@ export const ScriptRenderer: React.FC = ({ data, setIsPause const isHovered = hoveredBlockId === block.id; const isActive = activeBlockId === block.id; const isEditing = editBlockId === block.id; - + console.log('block', block) return ( (contentRefs.current[block.id] = el)} onMouseEnter={() => setHoveredBlockId(block.id)} @@ -303,4 +303,4 @@ export const ScriptRenderer: React.FC = ({ data, setIsPause ); -}; \ No newline at end of file +};