Spaces:
Running on Zero
Running on Zero
Fix multi-turn chat: lock composer during generation, always stream into a fresh end-of-history bubble
Browse files
app.py
CHANGED
|
@@ -724,6 +724,11 @@ def _parse_think(raw: str) -> tuple[str, str, bool]:
|
|
| 724 |
close_idx = rest.find(THINK_CLOSE)
|
| 725 |
before = raw[:open_idx]
|
| 726 |
if close_idx == -1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
return rest.strip(), before.strip(), True
|
| 728 |
think = rest[:close_idx].strip()
|
| 729 |
answer = (before + rest[close_idx + len(THINK_CLOSE) :]).strip()
|
|
@@ -853,20 +858,27 @@ def generate_reply(history, max_new_tokens, temperature, top_p, enable_thinking)
|
|
| 853 |
return
|
| 854 |
conversation = _history_for_model(history)
|
| 855 |
|
|
|
|
|
|
|
| 856 |
think_msg = None
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
|
|
|
|
|
|
| 867 |
history.append(think_msg)
|
| 868 |
else:
|
| 869 |
-
|
|
|
|
|
|
|
|
|
|
| 870 |
yield history
|
| 871 |
|
| 872 |
answer_msg = {"role": "assistant", "content": ""}
|
|
@@ -958,39 +970,21 @@ with gr.Blocks(fill_height=True, fill_width=True, elem_id="app-root") as demo:
|
|
| 958 |
gr.Button("Analyze it", size="sm", elem_classes=["twil-card-btn"])
|
| 959 |
)
|
| 960 |
|
| 961 |
-
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
[prompt, chatbot, chips_row],
|
| 965 |
-
show_progress="hidden",
|
| 966 |
-
).then(
|
| 967 |
-
generate_reply,
|
| 968 |
-
[chatbot, max_new_tokens, temperature, top_p, enable_thinking],
|
| 969 |
-
chatbot,
|
| 970 |
-
concurrency_limit=1,
|
| 971 |
-
show_progress="hidden",
|
| 972 |
-
)
|
| 973 |
-
prompt.submit(
|
| 974 |
-
queue_message,
|
| 975 |
-
[prompt, chatbot],
|
| 976 |
-
[prompt, chatbot, chips_row],
|
| 977 |
-
show_progress="hidden",
|
| 978 |
-
).then(
|
| 979 |
-
generate_reply,
|
| 980 |
-
[chatbot, max_new_tokens, temperature, top_p, enable_thinking],
|
| 981 |
-
chatbot,
|
| 982 |
-
concurrency_limit=1,
|
| 983 |
-
show_progress="hidden",
|
| 984 |
-
)
|
| 985 |
-
for btn, (_, template) in zip(chip_btns, CHIPS):
|
| 986 |
-
btn.click(lambda t=template: t, outputs=prompt)
|
| 987 |
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 994 |
queue_message,
|
| 995 |
[prompt, chatbot],
|
| 996 |
[prompt, chatbot, chips_row],
|
|
@@ -1002,6 +996,20 @@ with gr.Blocks(fill_height=True, fill_width=True, elem_id="app-root") as demo:
|
|
| 1002 |
concurrency_limit=1,
|
| 1003 |
show_progress="hidden",
|
| 1004 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1005 |
|
| 1006 |
if __name__ == "__main__":
|
| 1007 |
demo.launch(
|
|
|
|
| 724 |
close_idx = rest.find(THINK_CLOSE)
|
| 725 |
before = raw[:open_idx]
|
| 726 |
if close_idx == -1:
|
| 727 |
+
# Hide a partially streamed closing tag at the end of the think block.
|
| 728 |
+
for i in range(len(THINK_CLOSE) - 1, 0, -1):
|
| 729 |
+
if rest.endswith(THINK_CLOSE[:i]):
|
| 730 |
+
rest = rest[:-i]
|
| 731 |
+
break
|
| 732 |
return rest.strip(), before.strip(), True
|
| 733 |
think = rest[:close_idx].strip()
|
| 734 |
answer = (before + rest[close_idx + len(THINK_CLOSE) :]).strip()
|
|
|
|
| 858 |
return
|
| 859 |
conversation = _history_for_model(history)
|
| 860 |
|
| 861 |
+
# Stream only into a bubble at the END of the history. Reaching for any
|
| 862 |
+
# older assistant message would overwrite a previous turn in place.
|
| 863 |
think_msg = None
|
| 864 |
+
last = history[-1] if history else None
|
| 865 |
+
if (
|
| 866 |
+
last is not None
|
| 867 |
+
and last.get("role") == "assistant"
|
| 868 |
+
and isinstance(last.get("metadata"), dict)
|
| 869 |
+
and last["metadata"].get("status") == "pending"
|
| 870 |
+
):
|
| 871 |
+
think_msg = last
|
| 872 |
+
think_msg["content"] = ""
|
| 873 |
+
elif last is not None and last.get("role") == "user":
|
| 874 |
+
# Gradio strips the empty pending bubble in preprocessing; recreate it.
|
| 875 |
+
think_msg = {"role": "assistant", "content": ""}
|
| 876 |
history.append(think_msg)
|
| 877 |
else:
|
| 878 |
+
# No new user turn (e.g. an empty submit); don't regenerate old answers.
|
| 879 |
+
yield history
|
| 880 |
+
return
|
| 881 |
+
think_msg["metadata"] = {"title": "Logicizing…", "status": "pending"}
|
| 882 |
yield history
|
| 883 |
|
| 884 |
answer_msg = {"role": "assistant", "content": ""}
|
|
|
|
| 970 |
gr.Button("Analyze it", size="sm", elem_classes=["twil-card-btn"])
|
| 971 |
)
|
| 972 |
|
| 973 |
+
# Lock the composer while a turn is running: overlapping events make the
|
| 974 |
+
# finishing stream overwrite the chat with its own stale history.
|
| 975 |
+
lockable = [prompt, send, *card_btns]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 976 |
|
| 977 |
+
def _lock():
|
| 978 |
+
return [gr.update(interactive=False)] * len(lockable)
|
| 979 |
+
|
| 980 |
+
def _unlock():
|
| 981 |
+
return [gr.update(interactive=True)] * len(lockable)
|
| 982 |
+
|
| 983 |
+
def _wire(event, prep_fn=None, prep_outputs=None):
|
| 984 |
+
chain = event(_lock, None, lockable, show_progress="hidden")
|
| 985 |
+
if prep_fn is not None:
|
| 986 |
+
chain = chain.then(prep_fn, outputs=prep_outputs, show_progress="hidden")
|
| 987 |
+
chain = chain.then(
|
| 988 |
queue_message,
|
| 989 |
[prompt, chatbot],
|
| 990 |
[prompt, chatbot, chips_row],
|
|
|
|
| 996 |
concurrency_limit=1,
|
| 997 |
show_progress="hidden",
|
| 998 |
)
|
| 999 |
+
chain.then(_unlock, None, lockable, show_progress="hidden")
|
| 1000 |
+
|
| 1001 |
+
_wire(send.click)
|
| 1002 |
+
_wire(prompt.submit)
|
| 1003 |
+
for btn, (_, template) in zip(chip_btns, CHIPS):
|
| 1004 |
+
btn.click(lambda t=template: t, outputs=prompt)
|
| 1005 |
+
|
| 1006 |
+
for btn, case in zip(card_btns, SHOWCASE):
|
| 1007 |
+
case_prompt = SHOWCASE_TEMPLATE.format(argument=case["quote"])
|
| 1008 |
+
_wire(
|
| 1009 |
+
btn.click,
|
| 1010 |
+
prep_fn=lambda p=case_prompt: (p, gr.Tabs(selected="chat")),
|
| 1011 |
+
prep_outputs=[prompt, tabs],
|
| 1012 |
+
)
|
| 1013 |
|
| 1014 |
if __name__ == "__main__":
|
| 1015 |
demo.launch(
|