File size: 10,713 Bytes
b931367
 
74ebe5c
 
 
 
b931367
74ebe5c
b931367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74ebe5c
b931367
74ebe5c
b931367
 
74ebe5c
 
b931367
 
 
 
 
 
 
 
bb2bd12
74ebe5c
b931367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74ebe5c
 
 
 
 
 
 
 
 
 
 
 
b931367
 
 
 
 
 
 
 
 
 
74ebe5c
 
b931367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74ebe5c
b931367
 
 
 
 
 
 
 
 
 
 
 
 
 
74ebe5c
 
b931367
 
 
 
74ebe5c
 
 
 
 
b931367
 
 
 
 
 
 
 
 
 
74ebe5c
b931367
 
74ebe5c
 
 
b931367
 
 
 
 
 
 
 
74ebe5c
 
 
b931367
 
 
74ebe5c
 
b931367
 
 
 
 
 
74ebe5c
b931367
 
 
 
74ebe5c
 
 
bb2bd12
 
74ebe5c
 
 
 
 
 
bb2bd12
 
74ebe5c
 
 
 
 
 
bb2bd12
74ebe5c
bb2bd12
74ebe5c
 
b931367
 
74ebe5c
b931367
74ebe5c
 
 
 
b931367
 
74ebe5c
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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]
    )