Spaces:
Running
Running
| import gradio as gr | |
| import time | |
| import random | |
| # --- Mock Data --- | |
| MOCK_STYLE = """风格:赛博朋克 / 黑色电影 | |
| 视角:第三人称限制视角(主角:凯) | |
| 基调:阴郁、压抑、霓虹闪烁的高科技低生活 | |
| 核心规则: | |
| 1. 强调感官描写,特别是光影和声音。 | |
| 2. 避免过多的心理独白,通过行动展现心理。 | |
| """ | |
| MOCK_KNOWLEDGE_BASE = [ | |
| ["凯 (Kai)", "主角,前黑客,现在是义体医生。左臂是老式的军用义体。"], | |
| ["夜之城 (Night City)", "故事发生的舞台,一座永夜的巨型都市,被企业掌控。"], | |
| ["荒坂塔 (Arasaka Tower)", "市中心的最高建筑,象征着绝对的权力。"], | |
| ["赛博精神病 (Cyberpsychosis)", "过度改装义体导致的解离性精神障碍。"], | |
| ["网络监察 (NetWatch)", "负责维护网络安全的组织,被黑客们视为走狗。"] | |
| ] | |
| MOCK_SHORT_TERM_OUTLINE = [ | |
| [True, "凯接到一个神秘电话,对方声称知道他失踪妹妹的下落。"], | |
| [False, "凯前往'来生'酒吧与接头人见面。"], | |
| [False, "在酒吧遇到旧识,引发一场关于过去的争执。"], | |
| [False, "接头人出现,但似乎被跟踪了。"] | |
| ] | |
| MOCK_LONG_TERM_OUTLINE = [ | |
| [False, "揭露夜之城背后的惊天阴谋。"], | |
| [False, "凯找回妹妹,或者接受她已经改变的事实。"], | |
| [False, "与荒坂公司的最终决战。"] | |
| ] | |
| MOCK_INSPIRATIONS = [ | |
| "霓虹灯光在雨后的路面上破碎成无数光斑,凯拉紧了风衣的领口,义体手臂在寒风中隐隐作痛。来生酒吧的招牌在雾气中若隐若现,像是一只在黑暗中窥视的电子眼。", | |
| "\"你来晚了。\"接头人的声音经过变声器处理,听起来像是指甲划过玻璃。他坐在阴影里,只有指尖的一点红光在闪烁——那是他正在抽的廉价合成烟。", | |
| "突如其来的爆炸声震碎了酒吧的玻璃,人群尖叫着四散奔逃。凯本能地拔出了腰间的动能手枪,他的视觉系统瞬间切换到了战斗模式,周围的一切都变成了数据流。" | |
| ] | |
| MOCK_FLOW_SUGGESTIONS = [ | |
| "他感觉到了...", | |
| "空气中弥漫着...", | |
| "那是他从未见过的...", | |
| "就在这一瞬间..." | |
| ] | |
| # --- Logic Functions --- | |
| def get_stats(text): | |
| """Mock word count and read time.""" | |
| if not text: | |
| return "0 Words | 0 mins" | |
| words = len(text) | |
| read_time = max(1, words // 500) | |
| return f"{words} Words | ~{read_time} mins" | |
| def fetch_inspiration(prompt): | |
| """Simulate fetching inspiration options based on user prompt.""" | |
| time.sleep(1) | |
| # Simple Mock Logic based on prompt keywords | |
| if prompt and "打斗" in prompt: | |
| opts = [ | |
| "凯侧身闪过那一记重拳,义体关节发出尖锐的摩擦声。他顺势抓住对方的手腕,电流顺着接触点瞬间爆发。", | |
| "激光刃切开空气,留下一道灼热的残影。凯没有退缩,他的视觉系统已经计算出了对方唯一的破绽。", | |
| "周围的空气仿佛凝固了,只剩下心跳声和能量枪充能的嗡嗡声。谁先动,谁就会死。" | |
| ] | |
| elif prompt and "风景" in prompt: | |
| opts = [ | |
| "酸雨冲刷着生锈的金属外墙,流下一道道黑色的泪痕。远处的全息广告牌在雨雾中显得格外刺眼。", | |
| "清晨的阳光穿透厚重的雾霾,无力地洒在贫民窟的屋顶上。这里没有希望,只有生存。", | |
| "夜之城的地下就像是一个巨大的迷宫,管道交错,蒸汽弥漫,老鼠和瘾君子在阴影中通过眼神交流。" | |
| ] | |
| else: | |
| opts = MOCK_INSPIRATIONS | |
| return gr.update(visible=True), opts[0], opts[1], opts[2] | |
| def apply_inspiration(current_text, inspiration_text): | |
| """Append selected inspiration to the editor.""" | |
| if not current_text: | |
| new_text = inspiration_text | |
| else: | |
| new_text = current_text + "\n\n" + inspiration_text | |
| return new_text, gr.update(visible=False), "" # Clear prompt | |
| def dismiss_inspiration(): | |
| return gr.update(visible=False) | |
| def fetch_flow_suggestion(current_text): | |
| """Simulate fetching a short continuation.""" | |
| # If text ends with newline, maybe don't suggest? Or suggest new paragraph start. | |
| time.sleep(0.5) | |
| return random.choice(MOCK_FLOW_SUGGESTIONS) | |
| def accept_flow_suggestion(current_text, suggestion): | |
| if not suggestion or "等待输入" in suggestion: | |
| return current_text | |
| return current_text + suggestion | |
| def refresh_context(current_outline): | |
| """Mock refreshing the outline context (auto-complete task or add new one).""" | |
| new_outline = [row[:] for row in current_outline] | |
| # Try to complete the first pending task | |
| task_completed = False | |
| for row in new_outline: | |
| if not row[0]: | |
| row[0] = True | |
| task_completed = True | |
| break | |
| # If all done, or randomly, add a new event | |
| if not task_completed or random.random() > 0.7: | |
| new_outline.append([False, f"新的动态事件: 突发情况 #{random.randint(100, 999)}"]) | |
| return new_outline | |
| # --- UI Construction --- | |
| def create_smart_writer_tab(): | |
| # Hidden Buttons for JS triggers | |
| btn_accept_flow_trigger = gr.Button(visible=False, elem_id="btn_accept_flow_trigger") | |
| btn_refresh_context_trigger = gr.Button(visible=False, elem_id="btn_refresh_context_trigger") | |
| with gr.Row(equal_height=False, elem_id="indicator-writing-tab"): | |
| # --- Left Column: Entity Console --- | |
| with gr.Column(scale=0, min_width=384) as left_panel: | |
| gr.Markdown("### 🧠 核心实体控制台") | |
| with gr.Accordion("整体章程 (Style)", open=True): | |
| style_input = gr.Textbox( | |
| label="整体章程", | |
| lines=8, | |
| value=MOCK_STYLE, | |
| interactive=True | |
| ) | |
| with gr.Accordion("知识库 (Knowledge Base)", open=True): | |
| kb_input = gr.Dataframe( | |
| headers=["Term", "Description"], | |
| datatype=["str", "str"], | |
| value=MOCK_KNOWLEDGE_BASE, | |
| interactive=True, | |
| label="知识库", | |
| wrap=True | |
| ) | |
| with gr.Accordion("当前章节大纲 (Short-Term)", open=True): | |
| short_outline_input = gr.Dataframe( | |
| headers=["Done", "Task"], | |
| datatype=["bool", "str"], | |
| value=MOCK_SHORT_TERM_OUTLINE, | |
| interactive=True, | |
| label="当前章节大纲", | |
| col_count=(2, "fixed"), | |
| ) | |
| with gr.Accordion("故事总纲 (Long-Term)", open=False): | |
| long_outline_input = gr.Dataframe( | |
| headers=["Done", "Task"], | |
| datatype=["bool", "str"], | |
| value=MOCK_LONG_TERM_OUTLINE, | |
| interactive=True, | |
| label="故事总纲", | |
| col_count=(2, "fixed"), | |
| ) | |
| # --- Right Column: Writing Canvas --- | |
| with gr.Column(scale=1) as right_panel: | |
| # Toolbar | |
| with gr.Row(elem_classes=["toolbar"]): | |
| stats_display = gr.Markdown("0 Words | 0 mins") | |
| inspiration_btn = gr.Button("✨ 灵感扩写 (Cmd+Enter)", size="sm", variant="primary") | |
| # 主要编辑器区域 | |
| editor = gr.Textbox( | |
| label="沉浸写作画布", | |
| placeholder="开始你的创作...", | |
| lines=30, | |
| elem_classes=["writing-editor"], | |
| elem_id="writing-editor", | |
| show_label=False, | |
| ) | |
| # Flow Suggestion | |
| with gr.Row(variant="panel"): | |
| flow_suggestion_display = gr.Textbox( | |
| label="AI 实时续写建议 (按 Tab 采纳)", | |
| value="(等待输入...)", | |
| interactive=False, | |
| scale=4, | |
| elem_classes=["flow-suggestion-box"] | |
| ) | |
| accept_flow_btn = gr.Button("采纳", scale=1, elem_id='btn-action-accept-flow') | |
| refresh_flow_btn = gr.Button("换一个", scale=1) | |
| # Inspiration Modal | |
| with gr.Group(visible=False) as inspiration_modal: | |
| gr.Markdown("### 💡 灵感选项 (由 Ling 模型生成)") | |
| inspiration_prompt_input = gr.Textbox( | |
| label="设定脉络 (可选)", | |
| placeholder="例如:写一段激烈的打斗 / 描写赛博朋克夜景...", | |
| lines=1 | |
| ) | |
| refresh_inspiration_btn = gr.Button("生成选项") | |
| with gr.Row(): | |
| opt1_btn = gr.Button(MOCK_INSPIRATIONS[0], elem_classes=["inspiration-card"]) | |
| opt2_btn = gr.Button(MOCK_INSPIRATIONS[1], elem_classes=["inspiration-card"]) | |
| opt3_btn = gr.Button(MOCK_INSPIRATIONS[2], elem_classes=["inspiration-card"]) | |
| cancel_insp_btn = gr.Button("取消") | |
| # --- Interactions --- | |
| # 1. Stats | |
| editor.change(fn=get_stats, inputs=editor, outputs=stats_display) | |
| # 2. Inspiration Workflow | |
| # Open Modal (reset prompt) | |
| inspiration_btn.click( | |
| fn=lambda: (gr.update(visible=True), ""), | |
| outputs=[inspiration_modal, inspiration_prompt_input] | |
| ) | |
| # Generate Options based on Prompt | |
| refresh_inspiration_btn.click( | |
| fn=fetch_inspiration, | |
| inputs=[inspiration_prompt_input], | |
| outputs=[inspiration_modal, opt1_btn, opt2_btn, opt3_btn] | |
| ) | |
| # Apply Option | |
| for btn in [opt1_btn, opt2_btn, opt3_btn]: | |
| btn.click( | |
| fn=apply_inspiration, | |
| inputs=[editor, btn], | |
| outputs=[editor, inspiration_modal, inspiration_prompt_input] | |
| ) | |
| cancel_insp_btn.click(fn=dismiss_inspiration, outputs=inspiration_modal) | |
| # 3. Flow Suggestion | |
| editor.change(fn=fetch_flow_suggestion, inputs=editor, outputs=flow_suggestion_display) | |
| refresh_flow_btn.click(fn=fetch_flow_suggestion, inputs=editor, outputs=flow_suggestion_display) | |
| # Accept Flow (Triggered by Button or Tab Key via JS) | |
| accept_flow_fn_inputs = [editor, flow_suggestion_display] | |
| accept_flow_fn_outputs = [editor] | |
| accept_flow_btn.click(fn=accept_flow_suggestion, inputs=accept_flow_fn_inputs, outputs=accept_flow_fn_outputs) | |
| btn_accept_flow_trigger.click(fn=accept_flow_suggestion, inputs=accept_flow_fn_inputs, outputs=accept_flow_fn_outputs) | |
| # 4. Context Refresh (Triggered by Enter Key via JS) | |
| btn_refresh_context_trigger.click( | |
| fn=refresh_context, | |
| inputs=[short_outline_input], | |
| outputs=[short_outline_input] | |
| ) | |