import gradio as gr 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_paragraph_continuation import fetch_paragraph_continuation_agent from smart_writer_kit.agent_for_prompt_suggestion import fetch_prompt_suggestions_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 from ui_components.debounce_manager import DebounceManager from i18n import get_text # --- Mock Data (for UI population only) --- MOCK_STYLE = """故事:人类逐渐走向消亡时,人形机器人的休闲生活。 风格:自然平淡,文字细腻,描绘未来的荒凉与宁静交织的景象。 主题:探索人类与机器的界限,记忆与身份的意义。 """ MOCK_KNOWLEDGE_BASE = [ ["Alpha", "故事的主角,女性人形机器人,外表与人类无异。性格悠闲"], ["横滨", "故事发生的主要城市。由于海平面上升,城市部分地区被淹没,形成独特的水上景观。"] ] MOCK_SHORT_TERM_OUTLINE = [ [False, "故事的场景设定:海平面上升后的城市景观。"], [False, "介绍主角 Alpha 的日常生活和她与其他机器人的互动。"], [False, "Alpha 发现了一张旧照片,勾起了她对过去人类生活的好奇心。"], [False, "奶油蛋糕的制作方法。"] ] ## 按日常向动画剧情走向写的长纲要。具体。 MOCK_LONG_TERM_OUTLINE = [ [False, "介绍故事背景。人类逐渐减少,机器人和人的互动。"], [False, "Alpha 决定离开居住地,到东京寻找失散的朋友。"], [False, "月亮变成了一个巨大的 Disco 灯球。机器人不受控制地开始跳舞,导致全球范围内的混乱。"], ] # --- UI Helper Functions --- def get_stats(text, lang): """Calculate word count and read time.""" if not text: return get_text("writer_stats_default", lang) words = len(text.split()) read_time = max(1, words // 200) # Average reading speed return get_text("writer_stats_format", lang).format( words=words, read_time=read_time ) # --- UI Construction --- def create_smart_writer_tab(lang_state: gr.State): lang = lang_state.value debounce_manager = DebounceManager(debounce_time=2.0, tick_time=1.0, loading_text=get_text("writer_debounce_loading_text", lang)) with gr.Row(equal_height=False, elem_id="indicator-writing-tab"): # --- Left Column: Entity Console --- with gr.Column(scale=1) as left_panel: style_input = gr.Textbox( label=get_text("writer_style_input_label", lang), lines=8, value=MOCK_STYLE, interactive=True ) with gr.Accordion( get_text("writer_kb_accordion_label", lang), open=True ) as kb_accordion: kb_input = gr.Dataframe( headers=[get_text("writer_kb_dataframe_headers", lang), '?'], datatype=["str", "str"], value=MOCK_KNOWLEDGE_BASE, interactive=True, wrap=True ) with gr.Row(): btn_suggest_kb = gr.Button(get_text("writer_suggest_kb_button", lang), size="sm") suggested_kb_dataframe = gr.Dataframe( headers=["Term", "Description"], datatype=["str", "str"], visible=False, interactive=False, label=get_text("writer_suggested_kb_label", lang), ) with gr.Accordion(get_text("writer_short_outline_accordion_label", lang), open=True) as short_outline_accordion: short_outline_input = gr.Dataframe( headers=[get_text("writer_short_outline_dataframe_headers", lang), '?'], datatype=["bool", "str"], value=MOCK_SHORT_TERM_OUTLINE, interactive=True, label="???", col_count=(2, "fixed"), ) with gr.Row(): btn_sync_outline = gr.Button(get_text("writer_sync_outline_button", lang), size="sm") with gr.Accordion(get_text("writer_long_outline_accordion_label", lang), open=False) as long_outline_accordion: long_outline_input = gr.Dataframe( headers=[get_text("writer_short_outline_dataframe_headers", lang), '?'], datatype=["bool", "str"], value=MOCK_LONG_TERM_OUTLINE, interactive=True, label="???", col_count=(2, "fixed"), ) # --- Right Column: Writing Canvas --- with gr.Column(scale=5): # --- RIBBON AREA (Top of Editor) --- with gr.Row(variant="panel", elem_classes=["ribbon-container"]): # Area 1: Real-time Continuation (Flow) with gr.Column(scale=1, min_width=200): flow_suggestion_display = gr.Textbox( show_label=True, label=get_text("writer_flow_suggestion_label", lang), placeholder=get_text("writer_flow_suggestion_placeholder", lang), lines=3, interactive=False, elem_classes=["flow-suggestion-box"], ) btn_accept_flow = gr.Button(get_text("writer_accept_flow_button", lang),size="sm",variant="primary",elem_id="btn-action-accept-flow") btn_change_flow = gr.Button(get_text("writer_change_flow_button", lang),size="sm",elem_id="btn-action-change-flow") # Debounce Progress Indicator (Using Manager) debounce_state, debounce_timer, debounce_progress = debounce_manager.create_ui() debounce_progress.visible = True # Area 2: Paragraph Continuation (Inspiration) with gr.Column(scale=1, min_width=200): inspiration_prompt_input = gr.Textbox( label=get_text("writer_inspiration_prompt_label", lang), placeholder=get_text("writer_inspiration_prompt_placeholder", lang), lines=2 ) prompt_suggestions_dataset = gr.Dataset( label=get_text("writer_prompt_suggestions_label", lang), components=[gr.Textbox(visible=False)], samples=[["生成建议..."], ["生成建议..."], ["生成建议..."]], type="values" ) refresh_suggestions_btn = gr.Button( get_text("writer_refresh_suggestions_button", lang), size="sm", variant="secondary", ) with gr.Row(): btn_generate_para = gr.Button(get_text("writer_generate_para_button", lang),size="sm",variant="primary",elem_id="btn-action-create-paragraph") btn_change_para = gr.Button(get_text("writer_change_para_button", lang),size="sm") btn_accept_para = gr.Button(get_text("writer_accept_para_button", lang),size="sm") para_suggestion_display = gr.Textbox( show_label=False, placeholder=get_text("writer_para_suggestion_placeholder", lang), lines=3, interactive=False ) # Area 3: Adjust/Polish (Placeholder) with gr.Column(scale=1, min_width=200): polish_title = gr.Markdown(get_text("writer_polish_title", lang)) polish_soon = gr.Markdown(get_text("writer_coming_soon", lang)) # --- TOOLBAR --- with gr.Row(elem_classes=["toolbar"]): # --- EDITOR --- stats_display = gr.Markdown(get_text("writer_stats_default", lang)) editor = gr.Textbox( label=get_text("writer_editor_label", lang), placeholder=get_text("writer_editor_placeholder", lang), lines=25, # Reduced lines slightly to accommodate ribbon elem_classes=["writing-editor"], elem_id="writing-editor", show_label=False, ) # --- Interactions --- # 1. Stats editor.change(fn=get_stats, inputs=[editor, lang_state], outputs=stats_display) # 2. Flow Suggestion Logic (Using DebounceManager) # Bind reset logic to editor change editor.change( fn=debounce_manager.reset, inputs=[editor, style_input, kb_input, short_outline_input, long_outline_input], # Capture all context as payload outputs=[debounce_state, debounce_timer, debounce_progress] ) # Bind tick logic def flow_suggestion_trigger(editor_content, style, kb, short_outline, long_outline): return fetch_flow_suggestion_agent(editor_content, style, kb, short_outline, long_outline) # Note: debounce_manager.tick calls the trigger function. # The lambda is used to pass the specific trigger function for this tab. debounce_timer.tick( fn=lambda s: debounce_manager.tick(s, flow_suggestion_trigger), inputs=[debounce_state], outputs=[debounce_progress, debounce_state, debounce_timer, flow_suggestion_display] ) btn_change_flow.click(fn=fetch_flow_suggestion_agent, inputs=[editor, style_input, kb_input, short_outline_input, long_outline_input], outputs=flow_suggestion_display) accept_flow_fn_inputs = [editor, flow_suggestion_display] # accept_flow_suggestion_agent returns modified editor text btn_accept_flow.click( fn=lambda e, s: (accept_flow_suggestion_agent(e, s), ""), # Accept and clear suggestion inputs=accept_flow_fn_inputs, outputs=[editor, flow_suggestion_display] ) # 3. Paragraph Continuation Logic (Updated with prompt input) def generate_paragraph_wrapper(prompt_val, editor_val, style, kb, short, long_): return fetch_paragraph_continuation_agent(prompt_val, editor_val, style, kb, short, long_) for btn in [btn_generate_para, btn_change_para]: btn.click( fn=generate_paragraph_wrapper, inputs=[inspiration_prompt_input, editor, style_input, kb_input, short_outline_input, long_outline_input], outputs=[para_suggestion_display] ) def accept_para_wrapper(curr, new): # Reuse apply_inspiration_agent but extract text. # It returns (new_text, modal_update, empty_string) res = apply_inspiration_agent(curr, new) return res[0], "" btn_accept_para.click( fn=accept_para_wrapper, inputs=[editor, para_suggestion_display], outputs=[editor, para_suggestion_display] ) # Suggestions Logic # Trigger for suggestion generation def refresh_suggestions_wrapper(editor_content, style, kb, short_outline, long_outline): s1, s2, s3 = fetch_prompt_suggestions_agent(editor_content, style, kb, short_outline, long_outline) # Return a gr.update object to properly update the Dataset component return gr.update(samples=[[s1], [s2], [s3]]) refresh_suggestions_btn.click( fn=refresh_suggestions_wrapper, inputs=[editor, style_input, kb_input, short_outline_input, long_outline_input], outputs=[prompt_suggestions_dataset] ) # Dataset click -> fill prompt input def fill_prompt_from_dataset(val): return val[0] prompt_suggestions_dataset.click( fn=fill_prompt_from_dataset, inputs=prompt_suggestions_dataset, outputs=inspiration_prompt_input ) # 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] ) return { "style_input": style_input, "kb_accordion": kb_accordion, "kb_input": kb_input, "btn_suggest_kb": btn_suggest_kb, "suggested_kb_dataframe": suggested_kb_dataframe, "short_outline_accordion": short_outline_accordion, "short_outline_input": short_outline_input, "btn_sync_outline": btn_sync_outline, "long_outline_accordion": long_outline_accordion, "long_outline_input": long_outline_input, "flow_suggestion_display": flow_suggestion_display, "btn_accept_flow": btn_accept_flow, "btn_change_flow": btn_change_flow, "inspiration_prompt_input": inspiration_prompt_input, "prompt_suggestions_dataset": prompt_suggestions_dataset, "refresh_suggestions_btn": refresh_suggestions_btn, "btn_generate_para": btn_generate_para, "btn_change_para": btn_change_para, "btn_accept_para": btn_accept_para, "para_suggestion_display": para_suggestion_display, "polish_title": polish_title, "polish_soon": polish_soon, "stats_display": stats_display, "editor": editor, } def update_language(lang: str, components: dict): return { components["style_input"]: gr.update( label=get_text("writer_style_input_label", lang) ), components["kb_accordion"]: gr.update( label=get_text("writer_kb_accordion_label", lang) ), components["kb_input"]: gr.update( headers=get_text("writer_kb_dataframe_headers", lang) ), components["btn_suggest_kb"]: gr.update( value=get_text("writer_suggest_kb_button", lang) ), components["suggested_kb_dataframe"]: gr.update( label=get_text("writer_suggested_kb_label", lang) ), components["short_outline_accordion"]: gr.update( label=get_text("writer_short_outline_accordion_label", lang) ), components["short_outline_input"]: gr.update( headers=get_text("writer_short_outline_dataframe_headers", lang) ), components["btn_sync_outline"]: gr.update( value=get_text("writer_sync_outline_button", lang) ), components["long_outline_accordion"]: gr.update( label=get_text("writer_long_outline_accordion_label", lang) ), components["long_outline_input"]: gr.update( headers=get_text("writer_short_outline_dataframe_headers", lang) ), # Assuming same headers components["flow_suggestion_display"]: gr.update( label=get_text("writer_flow_suggestion_label", lang), placeholder=get_text("writer_flow_suggestion_placeholder", lang), ), components["btn_accept_flow"]: gr.update( value=get_text("writer_accept_flow_button", lang) ), components["btn_change_flow"]: gr.update( value=get_text("writer_change_flow_button", lang) ), components["inspiration_prompt_input"]: gr.update( label=get_text("writer_inspiration_prompt_label", lang), placeholder=get_text("writer_inspiration_prompt_placeholder", lang), ), components["prompt_suggestions_dataset"]: gr.update( label=get_text("writer_prompt_suggestions_label", lang) ), components["refresh_suggestions_btn"]: gr.update( value=get_text("writer_refresh_suggestions_button", lang) ), components["btn_generate_para"]: gr.update( value=get_text("writer_generate_para_button", lang) ), components["btn_change_para"]: gr.update( value=get_text("writer_change_para_button", lang) ), components["btn_accept_para"]: gr.update( value=get_text("writer_accept_para_button", lang) ), components["para_suggestion_display"]: gr.update( placeholder=get_text("writer_para_suggestion_placeholder", lang) ), components["polish_title"]: gr.update( value=get_text("writer_polish_title", lang) ), components["polish_soon"]: gr.update( value=get_text("writer_coming_soon", lang) ), components["editor"]: gr.update( label=get_text("writer_editor_label", lang), placeholder=get_text("writer_editor_placeholder", lang), ), components["stats_display"]: gr.update( value=get_text("writer_stats_default", lang) ), # Reset stats on lang change }