Add previous messaging session controls
This commit is contained in:
parent
e6be01c4dd
commit
3a53592107
9 changed files with 118 additions and 6 deletions
|
|
@ -4128,6 +4128,7 @@ _SETTINGS_DEFAULTS = {
|
|||
"show_tps": False, # show tokens-per-second chip in assistant message headers
|
||||
"fade_text_effect": False, # animate newly streamed words with a lightweight fade-in effect
|
||||
"show_cli_sessions": False, # merge CLI sessions from state.db into the sidebar
|
||||
"show_previous_messaging_sessions": False, # show older Telegram/Discord/etc. reset segments
|
||||
"sync_to_insights": False, # mirror WebUI token usage to state.db for /insights
|
||||
"check_for_updates": True, # check if webui/agent repos are behind upstream
|
||||
"whats_new_summary_enabled": False, # show an LLM-written What's New summary before diff links
|
||||
|
|
@ -4263,6 +4264,7 @@ _SETTINGS_BOOL_KEYS = {
|
|||
"show_tps",
|
||||
"fade_text_effect",
|
||||
"show_cli_sessions",
|
||||
"show_previous_messaging_sessions",
|
||||
"sync_to_insights",
|
||||
"check_for_updates",
|
||||
"whats_new_summary_enabled",
|
||||
|
|
|
|||
|
|
@ -2158,8 +2158,15 @@ def _messaging_source_key(session: dict) -> str | None:
|
|||
return _messaging_session_identity(session, raw)
|
||||
|
||||
|
||||
def _keep_latest_messaging_session_per_source(sessions: list[dict]) -> list[dict]:
|
||||
def _keep_latest_messaging_session_per_source(
|
||||
sessions: list[dict],
|
||||
*,
|
||||
show_previous_messaging_sessions: bool = False,
|
||||
) -> list[dict]:
|
||||
"""Keep only the newest sidebar row per messaging session identity."""
|
||||
if show_previous_messaging_sessions:
|
||||
return sorted(sessions, key=_session_sort_timestamp, reverse=True)
|
||||
|
||||
gateway_metadata = _load_gateway_session_identity_map()
|
||||
active_gateway_session_ids = {str(sid) for sid in gateway_metadata.keys() if sid}
|
||||
active_gateway_sources = {
|
||||
|
|
@ -3943,7 +3950,12 @@ def handle_get(handler, parsed) -> bool:
|
|||
if _profiles_match(s.get("profile"), active_profile)]
|
||||
other_profile_count = len(merged) - len(scoped)
|
||||
diag.stage("messaging_dedupe")
|
||||
scoped = _keep_latest_messaging_session_per_source(scoped)
|
||||
scoped = _keep_latest_messaging_session_per_source(
|
||||
scoped,
|
||||
show_previous_messaging_sessions=bool(
|
||||
settings.get("show_previous_messaging_sessions")
|
||||
),
|
||||
)
|
||||
if show_cli_sessions:
|
||||
diag.stage("cli_cap")
|
||||
scoped = _cap_recent_cli_sessions(scoped, cli_cap=CLI_VISIBLE_SESSION_CAP)
|
||||
|
|
|
|||
|
|
@ -1412,6 +1412,7 @@ function applyBotName(){
|
|||
window._showTps=!!s.show_tps;
|
||||
window._fadeTextEffect=!!s.fade_text_effect;
|
||||
window._showCliSessions=!!s.show_cli_sessions;
|
||||
window._showPreviousMessagingSessions=!!s.show_previous_messaging_sessions;
|
||||
window._soundEnabled=!!s.sound_enabled;
|
||||
window._notificationsEnabled=!!s.notifications_enabled;
|
||||
// Persist default workspace so the blank new-chat page can show it
|
||||
|
|
|
|||
|
|
@ -430,12 +430,15 @@ const LOCALES = {
|
|||
session_move_project_desc_has: 'Change the project for this conversation',
|
||||
session_move_project_desc_none: 'Assign a project to this conversation',
|
||||
session_archive: 'Archive conversation',
|
||||
session_hide_external: 'Hide from list',
|
||||
session_restore: 'Restore conversation',
|
||||
session_archive_desc: 'Hide this conversation until archived is shown',
|
||||
session_archive_worktree_desc: 'Hide this conversation; keep its worktree on disk',
|
||||
session_hide_external_desc: 'Hide this imported session from WebUI without deleting the source history.',
|
||||
session_restore_desc: 'Bring this conversation back into the main list',
|
||||
session_archived: 'Session archived',
|
||||
session_archived_worktree: 'Session archived. Worktree remains on disk.',
|
||||
session_hidden: 'Session hidden from list',
|
||||
session_restored: 'Session restored',
|
||||
session_archive_failed: 'Archive failed: ',
|
||||
session_duplicate: 'Duplicate conversation',
|
||||
|
|
@ -546,6 +549,7 @@ const LOCALES = {
|
|||
settings_label_sidebar_density: 'Sidebar density',
|
||||
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
|
||||
settings_label_external_sessions: 'Show non-WebUI sessions',
|
||||
settings_label_previous_messaging_sessions: 'Show previous messaging sessions',
|
||||
settings_label_sync_insights: 'Sync to insights',
|
||||
settings_label_check_updates: 'Check for updates',
|
||||
settings_label_whats_new_summary: "Summarize What's New with AI",
|
||||
|
|
@ -784,6 +788,7 @@ const LOCALES = {
|
|||
settings_auto_title_refresh_20: 'Every 20 exchanges',
|
||||
settings_desc_auto_title_refresh: 'Automatically re-generates the session title based on the latest exchange, keeping it relevant as the conversation evolves. Requires an LLM title generation model to be configured.',
|
||||
settings_desc_external_sessions: 'Show conversations from CLI, Telegram, Discord, Slack, and other channels in the session list. Click to import and continue.',
|
||||
settings_desc_previous_messaging_sessions: 'Show older Discord, Telegram, Slack, and Weixin sessions that were replaced by reset or compression.',
|
||||
settings_desc_sync_insights: 'Mirrors WebUI token usage to state.db so hermes /insights includes browser session data. Off by default.',
|
||||
settings_desc_check_updates: 'Show a banner when newer versions of the WebUI or Agent are available. Runs a background git fetch periodically.',
|
||||
settings_desc_whats_new_summary: "Changes the What's New action from opening the raw diff first to generating a short, human-readable summary. The regular diff comparison stays available after the summary.",
|
||||
|
|
@ -10858,12 +10863,15 @@ const LOCALES = {
|
|||
session_move_project_desc_has: 'Change the project for this conversation',
|
||||
session_move_project_desc_none: 'Assign a project to this conversation',
|
||||
session_archive: 'Archive conversation',
|
||||
session_hide_external: '목록에서 숨기기',
|
||||
session_restore: 'Restore conversation',
|
||||
session_archive_desc: 'Hide this conversation until archived is shown',
|
||||
session_archive_worktree_desc: '이 대화를 숨기고 worktree는 디스크에 유지합니다',
|
||||
session_hide_external_desc: '원본 기록은 삭제하지 않고 WebUI 목록에서만 숨깁니다.',
|
||||
session_restore_desc: 'Bring this conversation back into the main list',
|
||||
session_archived: 'Session archived',
|
||||
session_archived_worktree: '세션이 보관되었습니다. Worktree는 디스크에 남아 있습니다.',
|
||||
session_hidden: '목록에서 숨겼습니다',
|
||||
session_restored: 'Session restored',
|
||||
session_archive_failed: 'Archive failed: ',
|
||||
session_duplicate: 'Duplicate conversation',
|
||||
|
|
@ -10974,6 +10982,7 @@ const LOCALES = {
|
|||
settings_label_sidebar_density: '사이드바 밀도',
|
||||
cmd_reasoning: 'Toggle thinking visibility (show/hide), set effort level, or check current status',
|
||||
settings_label_external_sessions: '외부 세션 표시',
|
||||
settings_label_previous_messaging_sessions: '이전 메시징 세션 표시',
|
||||
settings_label_sync_insights: 'Insights에 동기화',
|
||||
settings_label_check_updates: '업데이트 확인',
|
||||
settings_label_whats_new_summary: "Summarize What's New with AI",
|
||||
|
|
@ -11161,6 +11170,7 @@ const LOCALES = {
|
|||
settings_auto_title_refresh_20: 'Every 20 exchanges',
|
||||
settings_desc_auto_title_refresh: '최신 대화를 바탕으로 세션 제목을 자동으로 다시 생성해 대화가 진행되어도 제목을 관련 있게 유지합니다. LLM 제목 생성 모델 설정이 필요합니다.',
|
||||
settings_desc_external_sessions: 'CLI, Telegram, Discord, Slack 및 기타 채널의 대화를 세션 목록에 표시합니다. 클릭하여 가져오고 계속하세요.',
|
||||
settings_desc_previous_messaging_sessions: 'reset 또는 compression으로 교체된 이전 Discord, Telegram, Slack, Weixin 세션을 표시합니다.',
|
||||
settings_desc_sync_insights: 'WebUI 토큰 사용량을 state.db에 반영하여 hermes /insights에 브라우저 세션 데이터가 포함되도록 합니다. 기본값은 꺼짐입니다.',
|
||||
settings_desc_check_updates: 'WebUI 또는 Agent의 새 버전이 있으면 배너를 표시합니다. 백그라운드에서 주기적으로 git fetch를 실행합니다.',
|
||||
settings_desc_whats_new_summary: "Changes the What's New action from opening the raw diff first to generating a short, human-readable summary. The regular diff comparison stays available after the summary.",
|
||||
|
|
|
|||
|
|
@ -1129,6 +1129,13 @@
|
|||
</label>
|
||||
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="settings_desc_external_sessions">Show conversations from CLI, Telegram, Discord, Slack, and other channels in the session list. Click to import and continue.</div>
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||||
<input type="checkbox" id="settingsShowPreviousMessagingSessions" style="width:15px;height:15px;accent-color:var(--accent)">
|
||||
<span data-i18n="settings_label_previous_messaging_sessions">Show previous messaging sessions</span>
|
||||
</label>
|
||||
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="settings_desc_previous_messaging_sessions">Show older Discord, Telegram, Slack, and Weixin sessions that were replaced by reset or compression.</div>
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||||
<input type="checkbox" id="settingsSyncInsights" style="width:15px;height:15px;accent-color:var(--accent)">
|
||||
|
|
|
|||
|
|
@ -5272,6 +5272,8 @@ function _preferencesPayloadFromUi(){
|
|||
if(apiRedactCb) payload.api_redact_enabled=apiRedactCb.checked;
|
||||
const showCliCb=$('settingsShowCliSessions');
|
||||
if(showCliCb) payload.show_cli_sessions=showCliCb.checked;
|
||||
const showPreviousMessagingCb=$('settingsShowPreviousMessagingSessions');
|
||||
if(showPreviousMessagingCb) payload.show_previous_messaging_sessions=showPreviousMessagingCb.checked;
|
||||
const syncCb=$('settingsSyncInsights');
|
||||
if(syncCb) payload.sync_to_insights=syncCb.checked;
|
||||
const updateCb=$('settingsCheckUpdates');
|
||||
|
|
@ -5524,6 +5526,8 @@ async function loadSettingsPanel(){
|
|||
if(apiRedactCb){apiRedactCb.checked=settings.api_redact_enabled!==false;apiRedactCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});}
|
||||
const showCliCb=$('settingsShowCliSessions');
|
||||
if(showCliCb){showCliCb.checked=!!settings.show_cli_sessions;showCliCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});}
|
||||
const showPreviousMessagingCb=$('settingsShowPreviousMessagingSessions');
|
||||
if(showPreviousMessagingCb){showPreviousMessagingCb.checked=!!settings.show_previous_messaging_sessions;showPreviousMessagingCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});}
|
||||
const syncCb=$('settingsSyncInsights');
|
||||
if(syncCb){syncCb.checked=!!settings.sync_to_insights;syncCb.addEventListener('change',_schedulePreferencesAutosave,{once:false});}
|
||||
const updateCb=$('settingsCheckUpdates');
|
||||
|
|
@ -6287,6 +6291,7 @@ function _applySavedSettingsUi(saved, body, opts){
|
|||
window._showTps=showTps;
|
||||
window._fadeTextEffect=!!fadeTextEffect;
|
||||
window._showCliSessions=showCliSessions;
|
||||
window._showPreviousMessagingSessions=!!body.show_previous_messaging_sessions;
|
||||
window._soundEnabled=body.sound_enabled;
|
||||
window._notificationsEnabled=body.notifications_enabled;
|
||||
window._whatsNewSummaryEnabled=!!body.whats_new_summary_enabled;
|
||||
|
|
@ -6382,6 +6387,7 @@ async function saveSettings(andClose){
|
|||
const showTps=!!($('settingsShowTps')||{}).checked;
|
||||
const fadeTextEffect=!!($('settingsFadeTextEffect')||{}).checked;
|
||||
const showCliSessions=!!($('settingsShowCliSessions')||{}).checked;
|
||||
const showPreviousMessagingSessions=!!($('settingsShowPreviousMessagingSessions')||{}).checked;
|
||||
const pw=($('settingsPassword')||{}).value;
|
||||
const theme=($('settingsTheme')||{}).value||'dark';
|
||||
const skin=($('settingsSkin')||{}).value||'default';
|
||||
|
|
@ -6405,6 +6411,7 @@ async function saveSettings(andClose){
|
|||
body.simplified_tool_calling=!!($('settingsSimplifiedToolCalling')||{}).checked;
|
||||
body.api_redact_enabled=!!($('settingsApiRedact')||{}).checked;
|
||||
body.show_cli_sessions=showCliSessions;
|
||||
body.show_previous_messaging_sessions=showPreviousMessagingSessions;
|
||||
body.sync_to_insights=!!($('settingsSyncInsights')||{}).checked;
|
||||
body.check_for_updates=!!($('settingsCheckUpdates')||{}).checked;
|
||||
body.whats_new_summary_enabled=!!($('settingsWhatsNewSummary')||{}).checked;
|
||||
|
|
|
|||
|
|
@ -1773,6 +1773,23 @@ function _openSessionActionMenu(session, anchorEl){
|
|||
}catch(err){showToast(t('session_archive_failed')+err.message);}
|
||||
}
|
||||
));
|
||||
if(isExternalSession && !session.archived){
|
||||
menu.appendChild(_buildSessionAction(
|
||||
t('session_hide_external'),
|
||||
t('session_hide_external_desc'),
|
||||
ICONS.archive,
|
||||
async()=>{
|
||||
closeSessionActionMenu();
|
||||
try{
|
||||
await api('/api/session/archive',{method:'POST',body:JSON.stringify({session_id:session.session_id,archived:true})});
|
||||
session.archived=true;
|
||||
if(S.session&&S.session.session_id===session.session_id) S.session.archived=true;
|
||||
await renderSessionList();
|
||||
showToast(t('session_hidden'));
|
||||
}catch(err){showToast(t('session_archive_failed')+err.message);}
|
||||
}
|
||||
));
|
||||
}
|
||||
if(!isExternalSession){
|
||||
_appendSessionDuplicateAction(menu, session);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Mirrors the structure of test_1003_appearance_autosave.py to verify the
|
||||
preferences-panel autosave pattern is wired correctly:
|
||||
|
||||
- All 14 preference fields use _schedulePreferencesAutosave (not _markSettingsDirty)
|
||||
- All 15 preference fields use _schedulePreferencesAutosave (not _markSettingsDirty)
|
||||
- Password field MUST still call _markSettingsDirty (security: never autosave)
|
||||
- _preferencesPayloadFromUi covers all 14 fields
|
||||
- _setPreferencesAutosaveStatus uses the shared i18n keys
|
||||
|
|
@ -41,6 +41,7 @@ PREFERENCE_FIELDS_AUTOSAVE = [
|
|||
("settingsShowTps", "show_tps"),
|
||||
("settingsSimplifiedToolCalling", "simplified_tool_calling"),
|
||||
("settingsShowCliSessions", "show_cli_sessions"),
|
||||
("settingsShowPreviousMessagingSessions", "show_previous_messaging_sessions"),
|
||||
("settingsSyncInsights", "sync_to_insights"),
|
||||
("settingsCheckUpdates", "check_for_updates"),
|
||||
("settingsSoundEnabled", "sound_enabled"),
|
||||
|
|
@ -52,8 +53,8 @@ PREFERENCE_FIELDS_AUTOSAVE = [
|
|||
]
|
||||
|
||||
|
||||
def test_all_14_preference_fields_have_autosave_payload_entries():
|
||||
"""_preferencesPayloadFromUi must include all 14 preference fields."""
|
||||
def test_all_15_preference_fields_have_autosave_payload_entries():
|
||||
"""_preferencesPayloadFromUi must include all 15 preference fields."""
|
||||
block = _function_block(PANELS_JS, "_preferencesPayloadFromUi")
|
||||
for dom_id, field in PREFERENCE_FIELDS_AUTOSAVE:
|
||||
assert f"$('{dom_id}')" in block, \
|
||||
|
|
@ -63,7 +64,7 @@ def test_all_14_preference_fields_have_autosave_payload_entries():
|
|||
|
||||
|
||||
def test_preference_fields_use_schedule_autosave_not_mark_dirty():
|
||||
"""All 13 listener attachments (excluding bot_name's debounce wrapper) must
|
||||
"""All 14 listener attachments (excluding bot_name's debounce wrapper) must
|
||||
use _schedulePreferencesAutosave. bot_name uses a wrapper but still
|
||||
eventually calls _schedulePreferencesAutosave."""
|
||||
panel = _load_settings_panel_block()
|
||||
|
|
|
|||
|
|
@ -827,6 +827,61 @@ def test_sessions_js_treats_email_as_messaging_source():
|
|||
assert "email: 'Email'" in src[src.find("_MESSAGING_SOURCE_LABELS"):src.find("function _isMessagingSession")]
|
||||
|
||||
|
||||
def test_previous_messaging_setting_keeps_reset_history(monkeypatch):
|
||||
"""The previous-messaging toggle exposes older reset segments."""
|
||||
import api.routes as routes
|
||||
|
||||
monkeypatch.setattr(
|
||||
routes,
|
||||
"_load_gateway_session_identity_map",
|
||||
lambda: {
|
||||
"discord_active": {
|
||||
"raw_source": "discord",
|
||||
"platform": "discord",
|
||||
"user_id": "user-1",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
rows = [
|
||||
{
|
||||
"session_id": "discord_active",
|
||||
"title": "Current Discord chat",
|
||||
"source_tag": "discord",
|
||||
"raw_source": "discord",
|
||||
"session_source": "messaging",
|
||||
"source_label": "Discord",
|
||||
"user_id": "user-1",
|
||||
"message_count": 3,
|
||||
"updated_at": 200.0,
|
||||
},
|
||||
{
|
||||
"session_id": "discord_previous_history",
|
||||
"title": "Previous Discord chat",
|
||||
"source_tag": "discord",
|
||||
"raw_source": "discord",
|
||||
"session_source": "messaging",
|
||||
"source_label": "Discord",
|
||||
"user_id": "user-1",
|
||||
"message_count": 7,
|
||||
"updated_at": 100.0,
|
||||
"end_reason": "session_reset",
|
||||
},
|
||||
]
|
||||
|
||||
hidden = routes._keep_latest_messaging_session_per_source(rows)
|
||||
visible = routes._keep_latest_messaging_session_per_source(
|
||||
rows,
|
||||
show_previous_messaging_sessions=True,
|
||||
)
|
||||
|
||||
assert [row["session_id"] for row in hidden] == ["discord_active"]
|
||||
assert [row["session_id"] for row in visible] == [
|
||||
"discord_active",
|
||||
"discord_previous_history",
|
||||
]
|
||||
|
||||
|
||||
def test_cross_source_parent_child_is_not_collapsed_into_root_metadata(cleanup_test_sessions):
|
||||
"""A WebUI continuation from a messaging parent must keep WebUI metadata.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue