Spaces:
Running
Running
| import gradio as gr | |
| import time | |
| from smart_writer_kit.agent_for_streaming_completion import fetch_flow_suggestion_agent, accept_flow_suggestion_agent | |
| from smart_writer_kit.agent_for_inspiration_expansion import fetch_inspiration_agent, apply_inspiration_agent | |
| from smart_writer_kit.agent_for_outline_update import update_outline_status_agent | |
| from smart_writer_kit.agent_for_kb_update import suggest_new_kb_terms_agent | |
| # --- Mock Data (for UI population only) --- | |
| 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, "与荒坂公司的最终决战。"] | |
| ] | |
| # --- UI Helper Functions --- | |
| def get_stats(text): | |
| """Calculate word count and read time.""" | |
| if not text: | |
| return "0 Words | 0 mins" | |
| words = len(text.split()) | |
| read_time = max(1, words // 200) # Average reading speed | |
| return f"{words} Words | ~{read_time} mins" | |
| def dismiss_inspiration(): | |
| return gr.update(visible=False) | |
| # --- UI Construction --- | |
| def create_smart_writer_tab(): | |
| debounce_state = gr.State({"last_change": 0, "active": False, "style": "", "kb": [], "short_outline": [], "long_outline": []}) | |
| debounce_timer = gr.Timer(0.5, active=False) | |
| 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.Row(): | |
| btn_suggest_kb = gr.Button("🔍 提取新词条", size="sm") | |
| md_suggested_terms_header = gr.Markdown("#### 推荐词条", visible=False) # Placeholder for suggested terms | |
| suggested_kb_dataframe = gr.Dataframe( | |
| headers=["Term", "Description"], | |
| datatype=["str", "str"], | |
| visible=False, # Initially hidden | |
| interactive=False, | |
| label="推荐词条" | |
| ) | |
| 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.Row(): | |
| btn_sync_outline = gr.Button("🔄 同步状态", size="sm") | |
| 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/Ctrl+Enter)", size="sm", variant="primary", elem_id="btn-action-create-paragraph") | |
| # 主要编辑器区域 | |
| 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("采纳(Tab)", scale=1, elem_id='btn-action-accept-flow') | |
| refresh_flow_btn = gr.Button("换一个(Shift+Tab)", scale=1, elem_id='btn-action-change-flow') | |
| # Debounce Progress | |
| debounce_progress = gr.HTML(value="", visible=False) | |
| # 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("生成选项(Shift+Enter)") | |
| with gr.Row(): | |
| opt1_btn = gr.Button("...", elem_classes=["inspiration-card"]) | |
| opt2_btn = gr.Button("...", elem_classes=["inspiration-card"]) | |
| opt3_btn = gr.Button("...", 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 (triggered by visible button or hidden trigger button for Cmd+Enter) | |
| open_inspiration_modal_fn = lambda: (gr.update(visible=True), "") | |
| inspiration_btn.click(fn=open_inspiration_modal_fn, outputs=[inspiration_modal, inspiration_prompt_input]) | |
| # Generate Options based on Prompt | |
| refresh_inspiration_btn.click( | |
| fn=fetch_inspiration_agent, | |
| inputs=[inspiration_prompt_input, editor, style_input, kb_input, short_outline_input, long_outline_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_agent, | |
| inputs=[editor, btn], | |
| outputs=[editor, inspiration_modal, inspiration_prompt_input] | |
| ) | |
| cancel_insp_btn.click(fn=dismiss_inspiration, outputs=inspiration_modal, show_progress="hidden") | |
| # 3. Flow Suggestion with Debounce | |
| def start_debounce(editor_content, style, kb, short_outline, long_outline): | |
| return {"last_change": time.time(), "active": True, "style": style, "kb": kb, "short_outline": short_outline, "long_outline": long_outline}, gr.update(active=True), gr.update(visible=True, value="<progress value='0' max='100'></progress> 补全中... 3.0s") | |
| def update_debounce(debounce_state, editor_content): | |
| if not debounce_state["active"]: | |
| return gr.update(), gr.update(), debounce_state, gr.update() | |
| elapsed = time.time() - debounce_state["last_change"] | |
| if elapsed >= 3: | |
| suggestion = fetch_flow_suggestion_agent(editor_content, debounce_state["style"], debounce_state["kb"], debounce_state["short_outline"], debounce_state["long_outline"]) | |
| return gr.update(visible=False), suggestion, {"last_change": 0, "active": False, "style": "", "kb": [], "short_outline": [], "long_outline": []}, gr.update(active=False) | |
| else: | |
| progress = int((elapsed / 3) * 100) | |
| remaining = 3 - elapsed | |
| progress_html = f"<progress value='{progress}' max='100'></progress> 补全中... {remaining:.1f}s" | |
| return gr.update(value=progress_html), gr.update(), debounce_state, gr.update() | |
| editor.change(fn=start_debounce, inputs=[editor, style_input, kb_input, short_outline_input, long_outline_input], outputs=[debounce_state, debounce_timer, debounce_progress]) | |
| debounce_timer.tick(fn=update_debounce, inputs=[debounce_state, editor], outputs=[debounce_progress, flow_suggestion_display, debounce_state, debounce_timer]) | |
| refresh_flow_btn.click(fn=fetch_flow_suggestion_agent, inputs=[editor, style_input, kb_input, short_outline_input, long_outline_input], outputs=flow_suggestion_display) | |
| # Accept Flow (Triggered by visible Button or hidden Tab Key trigger) | |
| accept_flow_fn_inputs = [editor, flow_suggestion_display] | |
| accept_flow_fn_outputs = [editor] | |
| accept_flow_btn.click(fn=accept_flow_suggestion_agent, inputs=accept_flow_fn_inputs, outputs=accept_flow_fn_outputs, show_progress="hidden") | |
| # 4. Agent-based Context Updates | |
| btn_sync_outline.click( | |
| fn=update_outline_status_agent, | |
| inputs=[short_outline_input, editor], | |
| outputs=[short_outline_input] | |
| ) | |
| btn_suggest_kb.click( | |
| fn=suggest_new_kb_terms_agent, | |
| inputs=[kb_input, editor], | |
| outputs=[suggested_kb_dataframe, md_suggested_terms_header] | |
| ) | |