polats Claude Opus 4.8 (1M context) commited on
Commit
b862211
·
1 Parent(s): 62070d0

Cap persona + diary at 100 tokens; shorten prompts; stream the diary

Browse files

- maxTokens 100 for both persona and diary (was 220/768 via thinkMaxTokens) so models
can't ramble past the answer. Prompts tightened to match: persona about → "1-2 short
sentences (~25 words)"; diary → "1-2 vivid sentences (~60 words), be brief".
- War diary now streams visibly: it used the aggressive stripThink live, which an
unclosed <think> emptied — so it showed blank until the end. Use stripThinkFinal in
the live onToken so the prose (and narration) appear token-by-token.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

web/diaryPanel.js CHANGED
@@ -5,7 +5,9 @@
5
  import { streamChat, ensureModel, currentModel, currentModelId, getEngineId, backendLabel } from '/web/runtime.js'
6
  import { mountTtsBar } from '/web/ttsBar.js'
7
  import { makeNarrator, ensureTts } from '/web/tts.js'
8
- import { DIARY_SYSTEM, diaryUserPrompt, stripThink, stripThinkFinal, noThink, thinkMaxTokens } from '/web/personaPrompts.js'
 
 
9
 
10
  function el(tag, props = {}, kids = []) {
11
  const n = document.createElement(tag)
@@ -54,7 +56,7 @@ export function mountDiaryPanel(host) {
54
  '=== TINY ARMY · DIARY DEBUG ===',
55
  `engine: ${getEngineId()} · ${backendLabel()}`,
56
  `model: ${currentModelId()} (${currentModel().label})`,
57
- `input: unit=${unit.value} traits=${traits.value} maxTokens=${thinkMaxTokens(currentModelId(), 220)}`,
58
  `outcome: ${outcome}`,
59
  `--- raw output (${(raw || '').length} chars) ---`,
60
  raw || '(empty)',
@@ -139,10 +141,10 @@ export function mountDiaryPanel(host) {
139
  await ensureModel((frac, label) => { status.textContent = label || `downloading ${currentModel().label}… ${Math.round(frac * 100)}% (one-time)` })
140
  status.textContent = `writing on your device with ${currentModel().label}…`
141
  await streamChat(DIARY_SYSTEM, diaryUserPrompt(unit.value, traits.value) + noThink(currentModelId()), {
142
- maxTokens: thinkMaxTokens(currentModelId(), 220), temperature: 0.9,
143
  onToken: (piece) => {
144
  raw += piece
145
- lastBody = stripThink(raw)
146
  out.textContent = header + lastBody
147
  if (live) { const delta = lastBody.slice(spokenLen); if (delta) { live.push(delta); spokenLen = lastBody.length } }
148
  },
 
5
  import { streamChat, ensureModel, currentModel, currentModelId, getEngineId, backendLabel } from '/web/runtime.js'
6
  import { mountTtsBar } from '/web/ttsBar.js'
7
  import { makeNarrator, ensureTts } from '/web/tts.js'
8
+ import { DIARY_SYSTEM, diaryUserPrompt, stripThinkFinal, noThink } from '/web/personaPrompts.js'
9
+
10
+ const MAX_TOKENS = 100 // short diary entries — cap matches the "~60 words" prompt
11
 
12
  function el(tag, props = {}, kids = []) {
13
  const n = document.createElement(tag)
 
56
  '=== TINY ARMY · DIARY DEBUG ===',
57
  `engine: ${getEngineId()} · ${backendLabel()}`,
58
  `model: ${currentModelId()} (${currentModel().label})`,
59
+ `input: unit=${unit.value} traits=${traits.value} maxTokens=${MAX_TOKENS}`,
60
  `outcome: ${outcome}`,
61
  `--- raw output (${(raw || '').length} chars) ---`,
62
  raw || '(empty)',
 
141
  await ensureModel((frac, label) => { status.textContent = label || `downloading ${currentModel().label}… ${Math.round(frac * 100)}% (one-time)` })
142
  status.textContent = `writing on your device with ${currentModel().label}…`
143
  await streamChat(DIARY_SYSTEM, diaryUserPrompt(unit.value, traits.value) + noThink(currentModelId()), {
144
+ maxTokens: MAX_TOKENS, temperature: 0.9,
145
  onToken: (piece) => {
146
  raw += piece
147
+ lastBody = stripThinkFinal(raw)
148
  out.textContent = header + lastBody
149
  if (live) { const delta = lastBody.slice(spokenLen); if (delta) { live.push(delta); spokenLen = lastBody.length } }
150
  },
web/personaPanel.js CHANGED
@@ -5,9 +5,10 @@
5
  import { streamChat, ensureModel, currentModel, currentModelId, getEngineId, backendLabel } from '/web/runtime.js'
6
  import { extractLivePersona } from '/web/personaStream.js'
7
  import { parsePersonaJson } from '/web/personaParse.js'
8
- import { PERSONA_SYSTEM, personaUserPrompt, stripThink, stripThinkFinal, noThink, thinkMaxTokens } from '/web/personaPrompts.js'
9
 
10
  const CLASSES = ['Warrior', 'Ranger', 'Monk', 'Assassin', 'Mage', 'Paladin', 'Cleric', 'Knight']
 
11
 
12
  function el(tag, props = {}, kids = []) {
13
  const n = document.createElement(tag)
@@ -59,7 +60,7 @@ export function mountPersonaPanel(host) {
59
  '=== TINY ARMY · PERSONA DEBUG ===',
60
  `engine: ${getEngineId()} · ${backendLabel()}`,
61
  `model: ${currentModelId()} (${currentModel().label})`,
62
- `input: class=${sel.value} seed=${seed.value || '(none)'} maxTokens=${thinkMaxTokens(currentModelId(), 220)}`,
63
  `outcome: ${outcome}`,
64
  `--- raw output (${(acc || '').length} chars) ---`,
65
  acc || '(empty)',
@@ -95,7 +96,7 @@ export function mountPersonaPanel(host) {
95
  await ensureModel((frac, label) => { status.textContent = label || `downloading ${currentModel().label}… ${Math.round(frac * 100)}% (one-time)` })
96
  status.textContent = `writing on your device with ${currentModel().label}…`
97
  await streamChat(PERSONA_SYSTEM, personaUserPrompt(sel.value, seed.value) + noThink(currentModelId()), {
98
- maxTokens: thinkMaxTokens(currentModelId(), 220),
99
  onToken: (piece) => {
100
  acc += piece
101
  thinkEl.textContent = acc // raw view shows the model's <think> reasoning too
 
5
  import { streamChat, ensureModel, currentModel, currentModelId, getEngineId, backendLabel } from '/web/runtime.js'
6
  import { extractLivePersona } from '/web/personaStream.js'
7
  import { parsePersonaJson } from '/web/personaParse.js'
8
+ import { PERSONA_SYSTEM, personaUserPrompt, stripThink, stripThinkFinal, noThink } from '/web/personaPrompts.js'
9
 
10
  const CLASSES = ['Warrior', 'Ranger', 'Monk', 'Assassin', 'Mage', 'Paladin', 'Cleric', 'Knight']
11
+ const MAX_TOKENS = 100 // short personas — keep models from rambling past the JSON
12
 
13
  function el(tag, props = {}, kids = []) {
14
  const n = document.createElement(tag)
 
60
  '=== TINY ARMY · PERSONA DEBUG ===',
61
  `engine: ${getEngineId()} · ${backendLabel()}`,
62
  `model: ${currentModelId()} (${currentModel().label})`,
63
+ `input: class=${sel.value} seed=${seed.value || '(none)'} maxTokens=${MAX_TOKENS}`,
64
  `outcome: ${outcome}`,
65
  `--- raw output (${(acc || '').length} chars) ---`,
66
  acc || '(empty)',
 
96
  await ensureModel((frac, label) => { status.textContent = label || `downloading ${currentModel().label}… ${Math.round(frac * 100)}% (one-time)` })
97
  status.textContent = `writing on your device with ${currentModel().label}…`
98
  await streamChat(PERSONA_SYSTEM, personaUserPrompt(sel.value, seed.value) + noThink(currentModelId()), {
99
+ maxTokens: MAX_TOKENS,
100
  onToken: (piece) => {
101
  acc += piece
102
  thinkEl.textContent = acc // raw view shows the model's <think> reasoning too
web/personaPrompts.js CHANGED
@@ -6,7 +6,7 @@ export const PERSONA_SYSTEM =
6
  'fighter writes its own legend. Given a class and an optional seed, return ONE JSON ' +
7
  'object and NOTHING else, with exactly these keys:\n' +
8
  ' "name": a short evocative soldier name (2-4 words),\n' +
9
- ' "about": 1-3 sentences of backstory in a heroic, slightly wry war-legend tone,\n' +
10
  ' "specialty": a 1-3 word combat specialty,\n' +
11
  ' "personality": a 1-3 word personality tag,\n' +
12
  ' "vibe": a 1-3 word vibe.\n' +
@@ -14,9 +14,9 @@ export const PERSONA_SYSTEM =
14
 
15
  export const DIARY_SYSTEM =
16
  'You are a tiny soldier in the auto-battler Tiny Army, writing a short first-person ' +
17
- 'war-diary entry. Given your name and traits, write 2-4 vivid sentences in first ' +
18
- 'person about a day on the battlefield — heroic, grounded, a touch of dark humor. ' +
19
- 'Prose only: no headings, no lists, no preamble.'
20
 
21
  export function personaUserPrompt(unitClass = '', seed = '') {
22
  const s = seed && seed.trim() ? ` Seed inspiration: "${seed.trim()}".` : ''
 
6
  'fighter writes its own legend. Given a class and an optional seed, return ONE JSON ' +
7
  'object and NOTHING else, with exactly these keys:\n' +
8
  ' "name": a short evocative soldier name (2-4 words),\n' +
9
+ ' "about": 1-2 short sentences of backstory (about 25 words) in a heroic, wry war-legend tone,\n' +
10
  ' "specialty": a 1-3 word combat specialty,\n' +
11
  ' "personality": a 1-3 word personality tag,\n' +
12
  ' "vibe": a 1-3 word vibe.\n' +
 
14
 
15
  export const DIARY_SYSTEM =
16
  'You are a tiny soldier in the auto-battler Tiny Army, writing a short first-person ' +
17
+ 'war-diary entry. Given your name and traits, write just 1-2 vivid sentences (about ' +
18
+ '60 words, no more) in first person about a day on the battlefield — heroic, grounded, ' +
19
+ 'a touch of dark humor. Prose only: no headings, no lists, no preamble. Be brief.'
20
 
21
  export function personaUserPrompt(unitClass = '', seed = '') {
22
  const s = seed && seed.trim() ? ` Seed inspiration: "${seed.trim()}".` : ''