mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 13:39:41 +02:00
Merge pull request #277 from Ark0N/feat/readmymind-phase3-part2
feat(readmymind): rethink steer note (phase 3 part 2)
This commit is contained in:
@@ -255,12 +255,15 @@
|
||||
'Read My Mind: predict your next prompt': '读心术:预测您的下一条提示',
|
||||
'Predict my next prompt': '预测我的下一条提示',
|
||||
'Reading your mind…': '正在读取您的想法…',
|
||||
'No suggestion this time. Rethink to try again.': '这次没有建议。点击「重想」再试一次。',
|
||||
'No suggestion this time. Add a steer note and Rethink to try again.':
|
||||
'这次没有建议。可添加引导备注后点击「重想」再试一次。',
|
||||
Rethink: '重想',
|
||||
Insert: '插入',
|
||||
"Put the text on the session's composer without submitting it": '将文本放入会话输入框但不提交',
|
||||
'Predicted prompt, editable': '预测的提示,可编辑',
|
||||
'Use this suggestion instead': '改用此建议',
|
||||
"Steer the rethink, e.g. 'no, I meant the mobile bug'": '引导重想,例如:"不,我是指移动端的问题"',
|
||||
'Steer note for Rethink': '重想的引导备注',
|
||||
'Select a session first': '请先选择一个会话',
|
||||
'Read My Mind works on Claude sessions only': '读心术仅适用于 Claude 会话',
|
||||
'Prompt sent': '提示已发送',
|
||||
|
||||
@@ -3107,13 +3107,23 @@
|
||||
<div class="readmymind-why" id="readMyMindWhy" data-i18n-skip></div>
|
||||
<div class="readmymind-alternates" id="readMyMindAlternates" data-i18n-skip style="display:none"></div>
|
||||
</div>
|
||||
<div class="readmymind-error" style="display:none">No suggestion this time. Rethink to try again.</div>
|
||||
<div class="readmymind-error" style="display:none">No suggestion this time. Add a steer note and Rethink to try again.</div>
|
||||
<!-- Rethink steer note: the user's own words about what they actually
|
||||
meant, sent as `steer` with the next Rethink. Shown whenever
|
||||
Rethink is live (ready AND empty-result phases), hidden while a
|
||||
prediction runs. -->
|
||||
<div class="readmymind-steer-row" id="readMyMindSteerRow" style="display:none">
|
||||
<input type="text" id="readMyMindSteer" class="readmymind-steer-input" maxlength="2000" placeholder="Steer the rethink, e.g. 'no, I meant the mobile bug'" aria-label="Steer note for Rethink" onkeydown="if(event.key==='Enter')app.rethinkReadMyMind()">
|
||||
</div>
|
||||
</div>
|
||||
<!-- btn-toolbar, not bare "btn btn-*": no .btn/.btn-secondary rule exists
|
||||
in this codebase, so the bare classes render as unstyled UA buttons.
|
||||
btn-toolbar is the convention every other modal footer uses. -->
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="app.closeReadMyMind()">Dismiss</button>
|
||||
<button class="btn btn-secondary" id="readMyMindRethink" onclick="app.rethinkReadMyMind()">Rethink</button>
|
||||
<button class="btn btn-secondary" onclick="app.sendReadMyMind(false)" title="Put the text on the session's composer without submitting it">Insert</button>
|
||||
<button class="btn btn-primary" onclick="app.sendReadMyMind(true)">Send</button>
|
||||
<button class="btn-toolbar" onclick="app.closeReadMyMind()">Dismiss</button>
|
||||
<button class="btn-toolbar" id="readMyMindRethink" onclick="app.rethinkReadMyMind()">Rethink</button>
|
||||
<button class="btn-toolbar" onclick="app.sendReadMyMind(false)" title="Put the text on the session's composer without submitting it">Insert</button>
|
||||
<button class="btn-toolbar btn-primary" onclick="app.sendReadMyMind(true)">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1318,20 +1318,22 @@ html.mobile-init .file-browser-panel {
|
||||
width: calc(100% - 2rem);
|
||||
}
|
||||
/* Four footer buttons on a narrow phone: let them wrap instead of clipping,
|
||||
and give buttons + alternate rows finger-sized targets. */
|
||||
and give buttons + alternate rows finger-sized targets. The flex row
|
||||
itself comes from the base rule in styles.css. */
|
||||
.readmymind-modal .modal-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.readmymind-modal .modal-footer .btn {
|
||||
.readmymind-modal .modal-footer .btn-toolbar {
|
||||
flex: 1 1 auto;
|
||||
justify-content: center;
|
||||
min-height: 38px;
|
||||
}
|
||||
.readmymind-alt {
|
||||
min-height: 38px;
|
||||
}
|
||||
.readmymind-steer-input {
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
/* Modal safe area padding - all sides for full-screen modals */
|
||||
.ios-device .modal-content {
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
* suggestions render as tappable alternate rows that swap into the field
|
||||
* without losing edits. Buttons are Send (with Enter), Insert (drop on the CLI
|
||||
* composer WITHOUT Enter, for editing), Rethink (re-run with the whole shown
|
||||
* set, main + alternates, recorded as rejected), Dismiss.
|
||||
* set, main + alternates, recorded as rejected, plus the optional free-text
|
||||
* steer note, e.g. "no, I meant the mobile bug", sent as `steer`), Dismiss.
|
||||
*
|
||||
* Suggestions are NEVER auto-sent: the explicit click here is the security
|
||||
* boundary for observed/injectable predictor inputs, so suggestion text is
|
||||
@@ -47,8 +48,11 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.showToast('Read My Mind works on Claude sessions only', 'warning');
|
||||
return;
|
||||
}
|
||||
// Rethink memory resets on each open (a fresh open is a fresh question).
|
||||
// Rethink memory resets on each open (a fresh open is a fresh question),
|
||||
// and the steer note resets with it.
|
||||
this._rmm = { sessionId, suggestions: [], selected: 0, rejected: [], busy: false };
|
||||
const steer = document.getElementById('readMyMindSteer');
|
||||
if (steer) steer.value = '';
|
||||
document.getElementById('readMyMindModal')?.classList.add('active');
|
||||
this._readMyMindPredict();
|
||||
},
|
||||
@@ -65,7 +69,13 @@ Object.assign(CodemanApp.prototype, {
|
||||
state.busy = true;
|
||||
this._rmmSetPhase('loading');
|
||||
|
||||
const body = state.rejected.length > 0 ? { rejected: state.rejected.slice(-10) } : {};
|
||||
const body = {};
|
||||
if (state.rejected.length > 0) body.rejected = state.rejected.slice(-10);
|
||||
// The steer note rides every re-run while it stays in the field: what the
|
||||
// user sees in the box is what the predictor gets. Empty on first open
|
||||
// (openReadMyMind clears it), so a plain predict sends neither key.
|
||||
const steer = document.getElementById('readMyMindSteer')?.value.trim() ?? '';
|
||||
if (steer) body.steer = steer.slice(0, 2000);
|
||||
const data = await this._apiJson(`/api/sessions/${state.sessionId}/readmymind`, { method: 'POST', body });
|
||||
|
||||
// The modal may have been dismissed (or reopened for another session) while
|
||||
@@ -176,7 +186,8 @@ Object.assign(CodemanApp.prototype, {
|
||||
},
|
||||
|
||||
/** Re-run with the whole shown set (main + alternates) recorded as rejected:
|
||||
* the user saw every row and asked for something else. */
|
||||
* the user saw every row and asked for something else. The steer note (if
|
||||
* any) is read from the field by _readMyMindPredict itself. */
|
||||
rethinkReadMyMind() {
|
||||
const state = this._rmm;
|
||||
if (!state || state.busy) return;
|
||||
@@ -193,6 +204,11 @@ Object.assign(CodemanApp.prototype, {
|
||||
modal.querySelector('.readmymind-loading').style.display = phase === 'loading' ? '' : 'none';
|
||||
modal.querySelector('.readmymind-result').style.display = phase === 'ready' ? '' : 'none';
|
||||
modal.querySelector('.readmymind-error').style.display = phase === 'error' ? '' : 'none';
|
||||
// The steer note belongs to Rethink, so it shows wherever Rethink is live:
|
||||
// the ready phase AND the empty-result phase (typed text survives the
|
||||
// loading round-trip, only the row's visibility toggles).
|
||||
const steerRow = document.getElementById('readMyMindSteerRow');
|
||||
if (steerRow) steerRow.style.display = phase === 'loading' ? 'none' : '';
|
||||
const rethinkBtn = document.getElementById('readMyMindRethink');
|
||||
if (rethinkBtn) rethinkBtn.disabled = phase === 'loading';
|
||||
},
|
||||
|
||||
@@ -10933,6 +10933,58 @@ kbd {
|
||||
white-space: nowrap;
|
||||
font-family: var(--mono-font, monospace);
|
||||
}
|
||||
/* Rethink steer note: the user's own words about what they actually meant,
|
||||
sent as `steer` on the next re-run (readmymind-ui.js _readMyMindPredict).
|
||||
Dashed border marks it as the optional side channel, distinct from the
|
||||
primary suggestion field above; it solidifies on focus. */
|
||||
.readmymind-steer-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.readmymind-steer-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
font-size: 12px;
|
||||
padding: 7px 10px;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px dashed var(--control-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.readmymind-steer-input:focus {
|
||||
outline: none;
|
||||
border-style: solid;
|
||||
border-color: var(--accent);
|
||||
background: var(--bg-dark);
|
||||
}
|
||||
.readmymind-steer-input::placeholder {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
/* Footer row: the buttons are btn-toolbar (display: flex, block-level), so
|
||||
without this rule the four of them stack vertically. Mirrors the
|
||||
runSummaryModal footer; the ≤430px block in mobile.css adds wrapping. */
|
||||
.readmymind-modal .modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
/* Send keeps the primary accent: the skin block's bare .btn-toolbar rule is
|
||||
(0,2,1) under html:not([data-skin="og"]) and outranks the base
|
||||
.btn-toolbar.btn-primary (0,2,0) — the specificity trap CLAUDE.md documents
|
||||
for mobile.css — so the accent is re-asserted here at (0,4,0). Scoped to
|
||||
this modal on purpose; un-greying every btn-primary on the new skins is a
|
||||
design call, not this feature's. */
|
||||
.readmymind-modal .modal-footer .btn-toolbar.btn-primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
.readmymind-modal .modal-footer .btn-toolbar.btn-primary:hover {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Keyboard-accessory 🧠 key: the phone surface for the same opt-in setting
|
||||
(the header 🧠 button stays phone-hidden in mobile.css). The key ships in
|
||||
|
||||
Reference in New Issue
Block a user