Spaces:
Running
Running
File size: 11,058 Bytes
b931367 |
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 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
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]
)
|