Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions codec_chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ <h1><a href="/" style="color:inherit;text-decoration:none">CODEC</a></h1>
// (also stripping an UNCLOSED <thinking> tail — a cut/degenerate stream
// must never dump raw reasoning into the bubble) > honest glitch notice
// with the reasoning kept in the reveal panel.
var answer=(pf.answer&&pf.answer.trim())||raw.replace(/<thinking>[\s\S]*?<\/thinking>/i,'').replace(/<thinking>[\s\S]*$/i,'').replace(/###\s*FINAL ANSWER:?/i,'').trim();
var answer=(pf.answer&&pf.answer.trim())||raw.replace(/<thinking>[\s\S]*?<\/thinking>/i,'').replace(/<thinking>[\s\S]*$/i,'').replace(/###\s*FINAL ANSWER:?/i,'').replace(/<\/?final\s*answer>/gi,'').trim();
if(!answer&&pf.think&&pf.think.trim()){answer='*My reasoning ran on without reaching a final answer — a local-model glitch, not a real reply. The train of thought is below; please ask again.*'}
if(answer){
div.remove();var md=addMessage('assistant',answer);
Expand Down Expand Up @@ -1703,7 +1703,7 @@ <h1><a href="/" style="color:inherit;text-decoration:none">CODEC</a></h1>
var pf2=parseScaffold(raw);
// Same fallback order as the primary handler: never dump an unclosed
// <thinking> tail into the bubble; keep reasoning in the reveal panel.
var answer2=(pf2.answer&&pf2.answer.trim())||raw.replace(/<thinking>[\s\S]*?<\/thinking>/i,'').replace(/<thinking>[\s\S]*$/i,'').replace(/###\s*FINAL ANSWER:?/i,'').trim();
var answer2=(pf2.answer&&pf2.answer.trim())||raw.replace(/<thinking>[\s\S]*?<\/thinking>/i,'').replace(/<thinking>[\s\S]*$/i,'').replace(/###\s*FINAL ANSWER:?/i,'').replace(/<\/?final\s*answer>/gi,'').trim();
if(!answer2&&pf2.think&&pf2.think.trim()){answer2='*My reasoning ran on without reaching a final answer — a local-model glitch, not a real reply. The train of thought is below; please ask again.*'}
if(answer2){div.remove();var md2=addMessage('assistant',answer2);if(thinkingEnabled&&pf2.think&&pf2.think.trim()&&md2){var fp2=makeToT(md2,'Train of thought',false);totSet(fp2,pf2.think.trim())}chatHist.push({role:'assistant',content:answer2});saveMessages([{role:'assistant',content:answer2}])}
else{bubble.innerHTML='<em style="color:var(--text-dim)">No response</em>'}
Expand Down
25 changes: 23 additions & 2 deletions routes/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,16 +1110,37 @@ async def _skill_stream():
f"Treat it as the current working directory for this conversation."
)

# A persona model (Fréd, M Corpus, Mwen — config extra_models[].system_prompt*)
# OWNS the turn: it must speak only as itself. The frontend still ships its
# generic "You are CODEC Deep Chat — a J.A.R.V.I.S.-class AI…" system
# message; gluing that onto the persona gave the 4B two conflicting
# identities (it answered in the generic voice and confabulated an
# attachment it never read). So for a persona, REPLACE the client system
# message with the persona prompt instead of concatenating.
_persona_active = False
try:
import codec_models as _cmp
_persona_active = bool(_cmp.model_extras().get("system_prompt"))
except Exception as _e:
log.debug("persona check failed: %s", _e)

# Prepend system message (or replace existing one)
if messages and messages[0].get("role") == "system":
messages[0]["content"] = sys_prompt + "\n\n" + messages[0]["content"]
messages[0]["content"] = (
sys_prompt if _persona_active
else sys_prompt + "\n\n" + messages[0]["content"]
)
else:
messages.insert(0, {"role": "system", "content": sys_prompt})

# Think mode: append the reasoning scaffold LAST so it wins over the base
# prompt's "answer directly"/emoji rules. Frontend sends reason_scaffold
# = Think-toggle state; it parses the resulting <thinking>/### FINAL ANSWER.
if body.get("reason_scaffold") and messages and messages[0].get("role") == "system":
# Skipped for persona models: a small fine-tune renders the format as its
# own <Final Answer> variant the UI stripper misses, and it fights the
# persona's voice.
if (body.get("reason_scaffold") and not _persona_active
and messages and messages[0].get("role") == "system"):
messages[0]["content"] += _REASON_SCAFFOLD

stream_mode = body.get("stream", False)
Expand Down