mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat(chat): add "hide agent thinking" toggle (#3994)
This commit is contained in:
@@ -57,4 +57,16 @@ describe('<AppearancePanel /> font size', () => {
|
||||
|
||||
expect(store.getState().theme.agentMessageViewMode).toBe('text');
|
||||
});
|
||||
|
||||
it('toggles hide-agent-thinking on and off', () => {
|
||||
const { getByRole, store } = renderPanel('medium');
|
||||
const toggle = getByRole('switch', { name: /settings\.appearance\.hideAgentInsights/ });
|
||||
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'false');
|
||||
fireEvent.click(toggle);
|
||||
expect(store.getState().theme.hideAgentInsights).toBe(true);
|
||||
|
||||
fireEvent.click(toggle);
|
||||
expect(store.getState().theme.hideAgentInsights).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type FontSize,
|
||||
setAgentMessageViewMode,
|
||||
setFontSize,
|
||||
setHideAgentInsights,
|
||||
setTabBarLabels,
|
||||
setThemeMode,
|
||||
type TabBarLabels,
|
||||
@@ -76,6 +77,7 @@ const AppearancePanel = () => {
|
||||
const agentMessageViewMode = useAppSelector(
|
||||
state => state.theme.agentMessageViewMode ?? 'bubbles'
|
||||
);
|
||||
const hideAgentInsights = useAppSelector(state => state.theme.hideAgentInsights ?? false);
|
||||
const labelsAlwaysVisible = tabBarLabels === 'always';
|
||||
const assistantTextModeEnabled = agentMessageViewMode === 'text';
|
||||
const toggleTabBarLabels = () => {
|
||||
@@ -86,6 +88,9 @@ const AppearancePanel = () => {
|
||||
const next: AgentMessageViewMode = assistantTextModeEnabled ? 'bubbles' : 'text';
|
||||
dispatch(setAgentMessageViewMode(next));
|
||||
};
|
||||
const toggleHideAgentInsights = () => {
|
||||
dispatch(setHideAgentInsights(!hideAgentInsights));
|
||||
};
|
||||
|
||||
// Build at render time so the labels follow the active locale; `t()` itself
|
||||
// memoises on locale change, so this stays stable across re-renders within a
|
||||
@@ -309,6 +314,19 @@ const AppearancePanel = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsRow
|
||||
htmlFor="switch-hide-agent-insights"
|
||||
label={t('settings.appearance.hideAgentInsights')}
|
||||
description={t('settings.appearance.hideAgentInsightsDesc')}
|
||||
control={
|
||||
<SettingsSwitch
|
||||
id="switch-hide-agent-insights"
|
||||
checked={hideAgentInsights}
|
||||
onCheckedChange={toggleHideAgentInsights}
|
||||
aria-label={t('settings.appearance.hideAgentInsights')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
{/* ── Display language (moved from the old settings home list) ── */}
|
||||
|
||||
@@ -3003,6 +3003,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': 'المصادر',
|
||||
'conversations.agentTaskInsights.noSteps': 'لم يتم تسجيل أي خطوات',
|
||||
'conversations.agentTaskInsights.viewProcessSource': 'عرض مصدر عملية الوكيل الكامل',
|
||||
'conversations.agentTaskInsights.processing': 'قيد المعالجة',
|
||||
'daemon.serviceBlockingGate.body': 'المحتوى',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'تلميح التنزيل',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'تنزيل أحدث إصدار',
|
||||
@@ -4509,6 +4510,9 @@ const messages: TranslationMap = {
|
||||
'عند إيقاف التشغيل، تظهر التسميات فقط عند التمرير أو لعلامة التبويب النشطة.',
|
||||
'settings.appearance.chatHeading': 'الدردشة',
|
||||
'settings.appearance.assistantTextMode': 'ردود المساعد كنص',
|
||||
'settings.appearance.hideAgentInsights': 'إخفاء تفكير الوكيل',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'طيّ الجدول الزمني المباشر لخطوات الوكيل في المحادثة. سيظل رابط "قيد المعالجة" الوامض يتيح لك فتح العملية الكاملة.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'اعرض ردود المساعد كنص بلا إطار مع إبقاء رسائلك داخل فقاعات.',
|
||||
'settings.mascot.active': 'نشط',
|
||||
|
||||
@@ -3067,6 +3067,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': 'উৎসসমূহ',
|
||||
'conversations.agentTaskInsights.noSteps': 'কোনো ধাপ রেকর্ড করা হয়নি',
|
||||
'conversations.agentTaskInsights.viewProcessSource': 'সম্পূর্ণ এজেন্ট প্রক্রিয়ার উৎস দেখুন',
|
||||
'conversations.agentTaskInsights.processing': 'প্রসেসিং',
|
||||
'daemon.serviceBlockingGate.body': 'বডি',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'ডাউনলোড হিন্ট',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'সর্বশেষ সংস্করণ ডাউনলোড করুন',
|
||||
@@ -4602,6 +4603,9 @@ const messages: TranslationMap = {
|
||||
'বন্ধ থাকা অবস্থায়, লেবেলগুলি শুধুমাত্র হোভারে বা সক্রিয় ট্যাবের জন্য প্রদর্শিত হয়৷',
|
||||
'settings.appearance.chatHeading': 'চ্যাট',
|
||||
'settings.appearance.assistantTextMode': 'অ্যাসিস্ট্যান্টের উত্তর টেক্সট হিসেবে',
|
||||
'settings.appearance.hideAgentInsights': 'এজেন্টের চিন্তা লুকান',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'চ্যাটে এজেন্টের ধাপে ধাপে লাইভ টাইমলাইন সংকুচিত করুন। একটি ব্লিঙ্কিং "প্রসেসিং" লিঙ্ক এখনও আপনাকে সম্পূর্ণ প্রক্রিয়া খুলতে দেয়।',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'আপনার বার্তাগুলি বাবলে রেখে অ্যাসিস্ট্যান্টের উত্তর ফ্রেমহীন টেক্সট হিসেবে দেখান।',
|
||||
'settings.mascot.active': 'সক্রিয়',
|
||||
|
||||
@@ -3141,6 +3141,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.noSteps': 'Keine Schritte aufgezeichnet',
|
||||
'conversations.agentTaskInsights.viewProcessSource':
|
||||
'Vollständige Agentenprozess-Quelle anzeigen',
|
||||
'conversations.agentTaskInsights.processing': 'Wird verarbeitet',
|
||||
'daemon.serviceBlockingGate.body': 'Körper',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'Hinweis herunterladen',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'Lade die neueste Version herunter',
|
||||
@@ -4721,6 +4722,9 @@ const messages: TranslationMap = {
|
||||
'Wenn diese Option deaktiviert ist, werden Beschriftungen nur beim Hover oder für die aktive Registerkarte angezeigt.',
|
||||
'settings.appearance.chatHeading': 'Chat',
|
||||
'settings.appearance.assistantTextMode': 'Assistentenantworten als Text',
|
||||
'settings.appearance.hideAgentInsights': 'Agent-Denkprozess ausblenden',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Blendet die schrittweise Live-Zeitleiste des Agenten im Chat aus. Über einen blinkenden „Wird verarbeitet“-Link lässt sich der vollständige Ablauf weiterhin öffnen.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'Zeigt Assistentenantworten als ungerahmten Text an und lässt deine Nachrichten in Blasen.',
|
||||
'settings.mascot.active': 'Aktiv',
|
||||
|
||||
@@ -3614,6 +3614,7 @@ const en: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': 'Sources',
|
||||
'conversations.agentTaskInsights.noSteps': 'No steps recorded',
|
||||
'conversations.agentTaskInsights.viewProcessSource': 'View full agent process Source',
|
||||
'conversations.agentTaskInsights.processing': 'Processing',
|
||||
'daemon.serviceBlockingGate.body':
|
||||
'Retrying in the background. This usually resolves in a few seconds.',
|
||||
'daemon.serviceBlockingGate.downloadHint':
|
||||
@@ -5204,6 +5205,9 @@ const en: TranslationMap = {
|
||||
'settings.appearance.assistantTextMode': 'Plain assistant responses',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'Render assistant replies as unframed text while keeping your messages in bubbles.',
|
||||
'settings.appearance.hideAgentInsights': 'Hide agent thinking',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Collapse the live step-by-step agent timeline in chat. A blinking “Processing” link still lets you open the full run.',
|
||||
'settings.mascot.active': 'Active',
|
||||
'settings.mascot.characterDesc': 'Choose your OpenHuman character.',
|
||||
'settings.mascot.characterHeading': 'Character',
|
||||
|
||||
@@ -3119,6 +3119,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.noSteps': 'No hay pasos registrados',
|
||||
'conversations.agentTaskInsights.viewProcessSource':
|
||||
'Ver la fuente completa del proceso del agente',
|
||||
'conversations.agentTaskInsights.processing': 'Procesando',
|
||||
'daemon.serviceBlockingGate.body': 'Cuerpo',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'Sugerencia de descarga',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'Descargar la última versión',
|
||||
@@ -4690,6 +4691,9 @@ const messages: TranslationMap = {
|
||||
'Cuando está desactivado, las etiquetas solo aparecen al pasar el mouse o para la pestaña activa.',
|
||||
'settings.appearance.chatHeading': 'Chat',
|
||||
'settings.appearance.assistantTextMode': 'Respuestas del asistente en texto',
|
||||
'settings.appearance.hideAgentInsights': 'Ocultar el razonamiento del agente',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Contrae la cronología paso a paso del agente en el chat. Un enlace «Procesando» parpadeante te permite abrir el proceso completo.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'Muestra las respuestas del asistente como texto sin marco y mantiene tus mensajes en burbujas.',
|
||||
'settings.mascot.active': 'Activo',
|
||||
|
||||
@@ -3134,6 +3134,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.noSteps': 'Aucune étape enregistrée',
|
||||
'conversations.agentTaskInsights.viewProcessSource':
|
||||
"Voir la source complète du processus de l'agent",
|
||||
'conversations.agentTaskInsights.processing': 'Traitement en cours',
|
||||
'daemon.serviceBlockingGate.body': 'Corps',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'Indice de téléchargement',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'Télécharger la dernière version',
|
||||
@@ -4707,6 +4708,9 @@ const messages: TranslationMap = {
|
||||
"Lorsqu'elle est désactivée, les étiquettes n'apparaissent qu'au survol ou pour l'onglet actif.",
|
||||
'settings.appearance.chatHeading': 'Chat',
|
||||
'settings.appearance.assistantTextMode': "Réponses de l'assistant en texte",
|
||||
'settings.appearance.hideAgentInsights': 'Masquer le raisonnement de l’agent',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Réduit la chronologie en direct des étapes de l’agent dans le chat. Un lien clignotant « Traitement en cours » permet toujours d’ouvrir le déroulé complet.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
"Affiche les réponses de l'assistant en texte sans cadre tout en gardant vos messages en bulles.",
|
||||
'settings.mascot.active': 'Actif',
|
||||
|
||||
@@ -3068,6 +3068,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': 'स्रोत',
|
||||
'conversations.agentTaskInsights.noSteps': 'कोई चरण दर्ज नहीं किया गया',
|
||||
'conversations.agentTaskInsights.viewProcessSource': 'पूर्ण एजेंट प्रक्रिया स्रोत देखें',
|
||||
'conversations.agentTaskInsights.processing': 'प्रोसेसिंग',
|
||||
'daemon.serviceBlockingGate.body': 'विवरण',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'डाउनलोड संकेत',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'नवीनतम संस्करण डाउनलोड करें',
|
||||
@@ -4606,6 +4607,9 @@ const messages: TranslationMap = {
|
||||
'बंद होने पर, लेबल केवल होवर पर या सक्रिय टैब के लिए दिखाई देते हैं।',
|
||||
'settings.appearance.chatHeading': 'चैट',
|
||||
'settings.appearance.assistantTextMode': 'असिस्टेंट जवाब टेक्स्ट में',
|
||||
'settings.appearance.hideAgentInsights': 'एजेंट की सोच छिपाएँ',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'चैट में एजेंट की चरण-दर-चरण लाइव टाइमलाइन को छिपाएँ। एक ब्लिंक करता "प्रोसेसिंग" लिंक फिर भी आपको पूरी प्रक्रिया खोलने देता है।',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'असिस्टेंट के जवाबों को बिना फ्रेम वाले टेक्स्ट के रूप में दिखाएं और आपके संदेश बबल में रखें।',
|
||||
'settings.mascot.active': 'एक्टिव',
|
||||
|
||||
@@ -3071,6 +3071,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': 'Sumber',
|
||||
'conversations.agentTaskInsights.noSteps': 'Tidak ada langkah yang tercatat',
|
||||
'conversations.agentTaskInsights.viewProcessSource': 'Lihat sumber proses agen lengkap',
|
||||
'conversations.agentTaskInsights.processing': 'Memproses',
|
||||
'daemon.serviceBlockingGate.body': 'Isi',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'Petunjuk unduhan',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'Unduh Versi Terbaru',
|
||||
@@ -4615,6 +4616,9 @@ const messages: TranslationMap = {
|
||||
'Saat nonaktif, label hanya muncul saat diarahkan atau untuk tab yang aktif.',
|
||||
'settings.appearance.chatHeading': 'Chat',
|
||||
'settings.appearance.assistantTextMode': 'Respons asisten sebagai teks',
|
||||
'settings.appearance.hideAgentInsights': 'Sembunyikan proses berpikir agen',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Ciutkan lini masa langkah demi langkah agen secara langsung di obrolan. Tautan "Memproses" yang berkedip tetap memungkinkan Anda membuka proses lengkapnya.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'Tampilkan balasan asisten sebagai teks tanpa bingkai, sementara pesan Anda tetap dalam gelembung.',
|
||||
'settings.mascot.active': 'Aktif',
|
||||
|
||||
@@ -3113,6 +3113,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.noSteps': 'Nessun passaggio registrato',
|
||||
'conversations.agentTaskInsights.viewProcessSource':
|
||||
"Visualizza l'origine completa del processo dell'agente",
|
||||
'conversations.agentTaskInsights.processing': 'Elaborazione',
|
||||
'daemon.serviceBlockingGate.body': 'Corpo',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'Suggerimento di download',
|
||||
'daemon.serviceBlockingGate.downloadLatest': "Scarica l'ultima versione",
|
||||
@@ -4678,6 +4679,9 @@ const messages: TranslationMap = {
|
||||
'Quando disattivata, le etichette vengono visualizzate solo al passaggio del mouse o per la scheda attiva.',
|
||||
'settings.appearance.chatHeading': 'Chat',
|
||||
'settings.appearance.assistantTextMode': "Risposte dell'assistente in testo",
|
||||
'settings.appearance.hideAgentInsights': 'Nascondi il ragionamento dell’agente',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Comprime la cronologia in tempo reale dei passaggi dell’agente nella chat. Un link lampeggiante «Elaborazione» consente comunque di aprire l’intero processo.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
"Mostra le risposte dell'assistente come testo senza cornice mantenendo i tuoi messaggi nei fumetti.",
|
||||
'settings.mascot.active': 'Attivo',
|
||||
|
||||
@@ -3043,6 +3043,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': '소스',
|
||||
'conversations.agentTaskInsights.noSteps': '기록된 단계 없음',
|
||||
'conversations.agentTaskInsights.viewProcessSource': '전체 에이전트 프로세스 소스 보기',
|
||||
'conversations.agentTaskInsights.processing': '처리 중',
|
||||
'daemon.serviceBlockingGate.body': '본문',
|
||||
'daemon.serviceBlockingGate.downloadHint': '다운로드 안내',
|
||||
'daemon.serviceBlockingGate.downloadLatest': '최신 버전 다운로드',
|
||||
@@ -4558,6 +4559,9 @@ const messages: TranslationMap = {
|
||||
'끄면 레이블은 마우스를 가져가거나 활성 탭에 대해서만 표시됩니다.',
|
||||
'settings.appearance.chatHeading': '채팅',
|
||||
'settings.appearance.assistantTextMode': '어시스턴트 답변을 텍스트로 표시',
|
||||
'settings.appearance.hideAgentInsights': '에이전트 사고 숨기기',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'채팅에서 에이전트의 단계별 실시간 타임라인을 접습니다. 깜박이는 "처리 중" 링크로 전체 과정을 열 수 있습니다.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'사용자 메시지는 말풍선으로 유지하고 어시스턴트 답변은 프레임 없는 텍스트로 표시합니다.',
|
||||
'settings.mascot.active': '활성',
|
||||
|
||||
@@ -3099,6 +3099,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': 'Źródła',
|
||||
'conversations.agentTaskInsights.noSteps': 'Brak zarejestrowanych kroków',
|
||||
'conversations.agentTaskInsights.viewProcessSource': 'Zobacz pełne źródło procesu agenta',
|
||||
'conversations.agentTaskInsights.processing': 'Przetwarzanie',
|
||||
'daemon.serviceBlockingGate.body':
|
||||
'Rdzeń OpenHuman nie odpowiada. Spróbuj ponownie lub pobierz najnowszą wersję aplikacji.',
|
||||
'daemon.serviceBlockingGate.downloadHint':
|
||||
@@ -4672,6 +4673,9 @@ const messages: TranslationMap = {
|
||||
'Wyłączone — etykiety pojawiają się tylko po najechaniu lub dla aktywnej zakładki.',
|
||||
'settings.appearance.chatHeading': 'Czat',
|
||||
'settings.appearance.assistantTextMode': 'Odpowiedzi asystenta jako tekst',
|
||||
'settings.appearance.hideAgentInsights': 'Ukryj myślenie agenta',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Zwija oś czasu z krokami agenta na żywo w czacie. Migający link „Przetwarzanie” nadal pozwala otworzyć pełny przebieg.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'Wyświetla odpowiedzi asystenta jako tekst bez ramki, a Twoje wiadomości pozostawia w dymkach.',
|
||||
'settings.mascot.active': 'Aktywny',
|
||||
|
||||
@@ -3117,6 +3117,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': 'Fontes',
|
||||
'conversations.agentTaskInsights.noSteps': 'Nenhuma etapa registrada',
|
||||
'conversations.agentTaskInsights.viewProcessSource': 'Ver a fonte completa do processo do agente',
|
||||
'conversations.agentTaskInsights.processing': 'Processando',
|
||||
'daemon.serviceBlockingGate.body': 'Corpo',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'Dica de download',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'Baixar Versão Mais Recente',
|
||||
@@ -4682,6 +4683,9 @@ const messages: TranslationMap = {
|
||||
'Quando desativado, os rótulos só aparecem ao passar o mouse ou para a guia ativa.',
|
||||
'settings.appearance.chatHeading': 'Chat',
|
||||
'settings.appearance.assistantTextMode': 'Respostas do assistente em texto',
|
||||
'settings.appearance.hideAgentInsights': 'Ocultar o raciocínio do agente',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Recolhe a linha do tempo passo a passo do agente no chat. Um link “Processando” piscando ainda permite abrir o processo completo.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'Renderiza as respostas do assistente como texto sem moldura e mantém suas mensagens em balões.',
|
||||
'settings.mascot.active': 'Ativo',
|
||||
|
||||
@@ -3090,6 +3090,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': 'Источники',
|
||||
'conversations.agentTaskInsights.noSteps': 'Шаги не записаны',
|
||||
'conversations.agentTaskInsights.viewProcessSource': 'Показать полный источник процесса агента',
|
||||
'conversations.agentTaskInsights.processing': 'Обработка',
|
||||
'daemon.serviceBlockingGate.body': 'Текст',
|
||||
'daemon.serviceBlockingGate.downloadHint': 'Подсказка по загрузке',
|
||||
'daemon.serviceBlockingGate.downloadLatest': 'Скачать последнюю версию',
|
||||
@@ -4645,6 +4646,9 @@ const messages: TranslationMap = {
|
||||
'Если этот параметр отключен, метки отображаются только при наведении курсора мыши или на активной вкладке.',
|
||||
'settings.appearance.chatHeading': 'Чат',
|
||||
'settings.appearance.assistantTextMode': 'Ответы ассистента текстом',
|
||||
'settings.appearance.hideAgentInsights': 'Скрыть размышления агента',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'Сворачивает пошаговую ленту действий агента в чате. Мигающая ссылка «Обработка» по-прежнему позволяет открыть весь процесс.',
|
||||
'settings.appearance.assistantTextModeDesc':
|
||||
'Показывает ответы ассистента как текст без рамки, оставляя ваши сообщения в пузырьках.',
|
||||
'settings.mascot.active': 'Активно',
|
||||
|
||||
@@ -2920,6 +2920,7 @@ const messages: TranslationMap = {
|
||||
'conversations.agentTaskInsights.sourcesHeading': '来源',
|
||||
'conversations.agentTaskInsights.noSteps': '未记录任何步骤',
|
||||
'conversations.agentTaskInsights.viewProcessSource': '查看完整的智能体处理来源',
|
||||
'conversations.agentTaskInsights.processing': '处理中',
|
||||
'daemon.serviceBlockingGate.body': '核心服务不可用,请等待或下载最新版本。',
|
||||
'daemon.serviceBlockingGate.downloadHint': '下载最新版本',
|
||||
'daemon.serviceBlockingGate.downloadLatest': '下载最新版本',
|
||||
@@ -4379,6 +4380,9 @@ const messages: TranslationMap = {
|
||||
'settings.appearance.tabBarAlwaysShowLabelsDesc': '关闭时,标签仅出现在悬停时或活动选项卡上。',
|
||||
'settings.appearance.chatHeading': '聊天',
|
||||
'settings.appearance.assistantTextMode': '助手回复以文本显示',
|
||||
'settings.appearance.hideAgentInsights': '隐藏智能体思考过程',
|
||||
'settings.appearance.hideAgentInsightsDesc':
|
||||
'折叠聊天中智能体逐步执行的实时时间线。闪烁的“处理中”链接仍可让你打开完整过程。',
|
||||
'settings.appearance.assistantTextModeDesc': '将助手回复渲染为无边框文本,同时保留你的消息气泡。',
|
||||
'settings.mascot.active': '活跃',
|
||||
'settings.mascot.characterDesc': '选择你的 OpenHuman 角色',
|
||||
|
||||
@@ -304,6 +304,11 @@ const Conversations = ({
|
||||
const agentMessageViewMode = useAppSelector(
|
||||
state => state.theme?.agentMessageViewMode ?? 'bubbles'
|
||||
);
|
||||
// When ON, the verbose per-agent "Agentic task insights" timeline is hidden
|
||||
// from chat; a compact blinking "Processing" link (and the existing message
|
||||
// bubble loading) stand in for it, with the full run one click away in the
|
||||
// Agent Process Source side panel. See themeSlice.hideAgentInsights.
|
||||
const hideAgentInsights = useAppSelector(state => state.theme?.hideAgentInsights ?? false);
|
||||
const inferenceTurnLifecycleByThread = useAppSelector(
|
||||
state => state.chatRuntime.inferenceTurnLifecycleByThread
|
||||
);
|
||||
@@ -2233,12 +2238,43 @@ const Conversations = ({
|
||||
messages the turn produced — both for the settled/inline case
|
||||
(shouldRenderTimelineBeforeLatestAgentMessage) and the live
|
||||
in-flight fallback. */}
|
||||
{selectedThreadToolTimeline.length > 0 && (
|
||||
<ToolTimelineBlock
|
||||
entries={selectedThreadToolTimeline}
|
||||
onViewSubagent={sub => setOpenSubagentTaskId(sub.taskId)}
|
||||
/>
|
||||
)}
|
||||
{selectedThreadToolTimeline.length > 0 &&
|
||||
(hideAgentInsights ? (
|
||||
// "Hide agent thinking" is ON: suppress the verbose step rows.
|
||||
// While the turn is still in flight, surface a single compact
|
||||
// blinking "Processing" link that opens the full run in the
|
||||
// Agent Process Source side panel. Once settled, the
|
||||
// "View full agent process Source" button below takes over.
|
||||
isSending ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowProcessSource(true)}
|
||||
data-testid="agent-processing-link"
|
||||
className="flex items-center gap-1.5 px-1 py-1 text-[11px] font-medium text-primary-600 hover:underline dark:text-primary-300">
|
||||
<span className="inline-block w-1.5 h-1.5 rounded-full bg-primary-400 animate-pulse" />
|
||||
<span>{t('conversations.agentTaskInsights.processing')} →</span>
|
||||
</button>
|
||||
) : !shouldRenderTimelineBeforeLatestAgentMessage ? (
|
||||
// Settled, but the hoisted "View full agent process Source"
|
||||
// opener below won't render because no agent message exists
|
||||
// for this turn (e.g. a cancelled first turn — onError skips
|
||||
// the agent message for `error_type === 'cancelled'`). Without
|
||||
// this fallback the recorded steps would be unreachable while
|
||||
// hidden, so surface our own opener whenever entries remain.
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowProcessSource(true)}
|
||||
data-testid="agent-process-source-fallback"
|
||||
className="px-1 text-[11px] font-medium text-primary-600 hover:underline dark:text-primary-300">
|
||||
{t('conversations.agentTaskInsights.viewProcessSource')} →
|
||||
</button>
|
||||
) : null
|
||||
) : (
|
||||
<ToolTimelineBlock
|
||||
entries={selectedThreadToolTimeline}
|
||||
onViewSubagent={sub => setOpenSubagentTaskId(sub.taskId)}
|
||||
/>
|
||||
))}
|
||||
{/* "View full agent process" — only in the settled/inline state
|
||||
(turn finished, an agent message exists). Hoisted out of the
|
||||
per-message map alongside the panel above so it renders once
|
||||
|
||||
@@ -19,6 +19,7 @@ import { chatSend } from '../../services/chatService';
|
||||
import { CoreRpcError } from '../../services/coreRpcClient';
|
||||
import agentProfileReducer from '../../store/agentProfileSlice';
|
||||
import chatRuntimeReducer, {
|
||||
beginInferenceTurn,
|
||||
setInferenceStatusForThread,
|
||||
setTaskBoardForThread,
|
||||
setToolTimelineForThread,
|
||||
@@ -1778,6 +1779,183 @@ describe('Conversations — agent task insights panel anchoring (#3717 Bug 2)',
|
||||
fireEvent.click(screen.getByTestId('subagent-view-processing'));
|
||||
});
|
||||
});
|
||||
|
||||
it('hides the verbose timeline when "hide agent thinking" is on, but still opens the source panel', async () => {
|
||||
const thread = makeThread({ id: 'hide-insights-thread', title: 'Hide insights' });
|
||||
const messages: ThreadMessage[] = [
|
||||
{
|
||||
id: 'm-user',
|
||||
sender: 'user',
|
||||
type: 'text',
|
||||
content: 'How many posts?',
|
||||
extraMetadata: {},
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'm-agent',
|
||||
sender: 'agent',
|
||||
type: 'text',
|
||||
content: 'Zero posts went up.',
|
||||
extraMetadata: {},
|
||||
createdAt: '2026-01-01T00:01:00.000Z',
|
||||
},
|
||||
];
|
||||
mockGetThreads.mockResolvedValue({ threads: [thread], count: 1 });
|
||||
mockGetThreadMessages.mockResolvedValue({ messages, count: messages.length });
|
||||
|
||||
let store: ReturnType<typeof buildStore> | undefined;
|
||||
await act(async () => {
|
||||
store = await renderConversations({
|
||||
thread: {
|
||||
...selectedThreadState(thread),
|
||||
messagesByThreadId: { [thread.id]: messages },
|
||||
messages,
|
||||
},
|
||||
socket: socketState('connected'),
|
||||
theme: {
|
||||
mode: 'system',
|
||||
tabBarLabels: 'hover',
|
||||
fontSize: 'medium',
|
||||
hideAgentInsights: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await screen.findByText('Zero posts went up.');
|
||||
await act(async () => {
|
||||
store!.dispatch(
|
||||
setToolTimelineForThread({
|
||||
threadId: thread.id,
|
||||
entries: [{ id: 'tl-1', name: 'web_fetch', round: 1, status: 'success' }],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Settled turn + preference ON: the verbose inline timeline is suppressed…
|
||||
expect(screen.queryByTestId('agent-task-insights')).toBeNull();
|
||||
// …but the "View full agent process Source" affordance still works and the
|
||||
// full run is one click away in the side panel (which renders the timeline).
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByTestId('view-process-source'));
|
||||
});
|
||||
expect(await screen.findByTestId('agent-task-insights')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a blinking "Processing" link instead of the timeline while in flight', async () => {
|
||||
const thread = makeThread({ id: 'processing-thread', title: 'Processing' });
|
||||
const messages: ThreadMessage[] = [
|
||||
{
|
||||
id: 'm-user',
|
||||
sender: 'user',
|
||||
type: 'text',
|
||||
content: 'Go.',
|
||||
extraMetadata: {},
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
];
|
||||
mockGetThreads.mockResolvedValue({ threads: [thread], count: 1 });
|
||||
mockGetThreadMessages.mockResolvedValue({ messages, count: messages.length });
|
||||
|
||||
let store: ReturnType<typeof buildStore> | undefined;
|
||||
await act(async () => {
|
||||
store = await renderConversations({
|
||||
thread: {
|
||||
...selectedThreadState(thread),
|
||||
messagesByThreadId: { [thread.id]: messages },
|
||||
messages,
|
||||
},
|
||||
socket: socketState('connected'),
|
||||
theme: {
|
||||
mode: 'system',
|
||||
tabBarLabels: 'hover',
|
||||
fontSize: 'medium',
|
||||
hideAgentInsights: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await screen.findByText('Go.');
|
||||
// Drive the thread into an in-flight turn so `isSending` is true, then seed
|
||||
// a running timeline that the preference should keep hidden behind the link.
|
||||
await act(async () => {
|
||||
store!.dispatch(beginInferenceTurn({ threadId: thread.id }));
|
||||
store!.dispatch(
|
||||
setToolTimelineForThread({
|
||||
threadId: thread.id,
|
||||
entries: [{ id: 'tl-1', name: 'web_fetch', round: 1, status: 'running' }],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const link = await screen.findByTestId('agent-processing-link');
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('agent-task-insights')).toBeNull();
|
||||
|
||||
// Clicking the compact link opens the full run in the source panel.
|
||||
await act(async () => {
|
||||
fireEvent.click(link);
|
||||
});
|
||||
expect(await screen.findByTestId('agent-task-insights')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps a settled source opener when hidden and no agent message exists (cancelled first turn)', async () => {
|
||||
// A cancelled first turn records timeline steps but never persists an agent
|
||||
// message, so the hoisted "View full agent process Source" button does not
|
||||
// render. With the timeline hidden, the fallback opener must keep those
|
||||
// steps reachable.
|
||||
const thread = makeThread({ id: 'cancelled-first-turn', title: 'Cancelled' });
|
||||
const messages: ThreadMessage[] = [
|
||||
{
|
||||
id: 'm-user',
|
||||
sender: 'user',
|
||||
type: 'text',
|
||||
content: 'Start then stop.',
|
||||
extraMetadata: {},
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
},
|
||||
];
|
||||
mockGetThreads.mockResolvedValue({ threads: [thread], count: 1 });
|
||||
mockGetThreadMessages.mockResolvedValue({ messages, count: messages.length });
|
||||
|
||||
let store: ReturnType<typeof buildStore> | undefined;
|
||||
await act(async () => {
|
||||
store = await renderConversations({
|
||||
thread: {
|
||||
...selectedThreadState(thread),
|
||||
messagesByThreadId: { [thread.id]: messages },
|
||||
messages,
|
||||
},
|
||||
socket: socketState('connected'),
|
||||
theme: {
|
||||
mode: 'system',
|
||||
tabBarLabels: 'hover',
|
||||
fontSize: 'medium',
|
||||
hideAgentInsights: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await screen.findByText('Start then stop.');
|
||||
// Settled (no in-flight turn) timeline with steps but no agent message.
|
||||
await act(async () => {
|
||||
store!.dispatch(
|
||||
setToolTimelineForThread({
|
||||
threadId: thread.id,
|
||||
entries: [{ id: 'tl-1', name: 'web_fetch', round: 1, status: 'cancelled' }],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// No verbose timeline, and the hoisted opener is absent (no agent message)…
|
||||
expect(screen.queryByTestId('agent-task-insights')).toBeNull();
|
||||
expect(screen.queryByTestId('view-process-source')).toBeNull();
|
||||
// …so the fallback opener carries access to the recorded steps.
|
||||
const fallback = await screen.findByTestId('agent-process-source-fallback');
|
||||
await act(async () => {
|
||||
fireEvent.click(fallback);
|
||||
});
|
||||
expect(await screen.findByTestId('agent-task-insights')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Conversations — open-session resume (View work)', () => {
|
||||
|
||||
@@ -96,7 +96,14 @@ const persistedLocaleReducer = persistReducer(localePersistConfig, localeReducer
|
||||
const themePersistConfig = {
|
||||
key: 'theme',
|
||||
storage: localStorageAdapter,
|
||||
whitelist: ['mode', 'tabBarLabels', 'fontSize', 'agentMessageViewMode', 'developerMode'],
|
||||
whitelist: [
|
||||
'mode',
|
||||
'tabBarLabels',
|
||||
'fontSize',
|
||||
'agentMessageViewMode',
|
||||
'developerMode',
|
||||
'hideAgentInsights',
|
||||
],
|
||||
};
|
||||
const persistedThemeReducer = persistReducer(themePersistConfig, themeReducer);
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ import { describe, expect, it } from 'vitest';
|
||||
import themeReducer, {
|
||||
FONT_SIZE_PX,
|
||||
type FontSize,
|
||||
selectHideAgentInsights,
|
||||
setAgentMessageViewMode,
|
||||
setFontSize,
|
||||
setHideAgentInsights,
|
||||
setTabBarLabels,
|
||||
setThemeMode,
|
||||
} from './themeSlice';
|
||||
@@ -39,6 +41,7 @@ describe('themeSlice', () => {
|
||||
fontSize: 'xlarge',
|
||||
agentMessageViewMode: 'text',
|
||||
developerMode: false,
|
||||
hideAgentInsights: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,6 +51,20 @@ describe('themeSlice', () => {
|
||||
expect(state.agentMessageViewMode).toBe('text');
|
||||
});
|
||||
|
||||
it('defaults hideAgentInsights to false and toggles it', () => {
|
||||
let state = themeReducer(undefined, { type: '@@INIT' });
|
||||
expect(state.hideAgentInsights).toBe(false);
|
||||
expect(selectHideAgentInsights({ theme: state })).toBe(false);
|
||||
|
||||
state = themeReducer(state, setHideAgentInsights(true));
|
||||
expect(state.hideAgentInsights).toBe(true);
|
||||
expect(selectHideAgentInsights({ theme: state })).toBe(true);
|
||||
});
|
||||
|
||||
it('falls back to false when hideAgentInsights is absent from persisted state', () => {
|
||||
expect(selectHideAgentInsights({ theme: {} as never })).toBe(false);
|
||||
});
|
||||
|
||||
it('maps every font size to a concrete px value', () => {
|
||||
const sizes: FontSize[] = ['small', 'medium', 'large', 'xlarge'];
|
||||
expect(sizes.map(size => FONT_SIZE_PX[size])).toEqual(['14px', '16px', '18px', '20px']);
|
||||
|
||||
@@ -36,6 +36,15 @@ interface ThemeState {
|
||||
* is authoritative and is never relaxed by this toggle.
|
||||
*/
|
||||
developerMode: boolean;
|
||||
/**
|
||||
* Hide the live "Agentic task insights" step-by-step timeline in chat
|
||||
* (default OFF). When true, the verbose per-agent step rows are collapsed
|
||||
* away: the chat shows only the existing message-bubble loading plus a
|
||||
* compact blinking "Processing" link while a turn is in flight. The full
|
||||
* timeline is still one click away via that link / the "View full agent
|
||||
* process Source" affordance, which open the existing side panel.
|
||||
*/
|
||||
hideAgentInsights: boolean;
|
||||
}
|
||||
|
||||
const initialState: ThemeState = {
|
||||
@@ -44,6 +53,7 @@ const initialState: ThemeState = {
|
||||
fontSize: 'medium',
|
||||
agentMessageViewMode: 'text',
|
||||
developerMode: false,
|
||||
hideAgentInsights: false,
|
||||
};
|
||||
|
||||
const themeSlice = createSlice({
|
||||
@@ -65,6 +75,9 @@ const themeSlice = createSlice({
|
||||
setDeveloperMode(state, action: PayloadAction<boolean>) {
|
||||
state.developerMode = action.payload;
|
||||
},
|
||||
setHideAgentInsights(state, action: PayloadAction<boolean>) {
|
||||
state.hideAgentInsights = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -74,9 +87,18 @@ export const {
|
||||
setFontSize,
|
||||
setAgentMessageViewMode,
|
||||
setDeveloperMode,
|
||||
setHideAgentInsights,
|
||||
} = themeSlice.actions;
|
||||
export default themeSlice.reducer;
|
||||
|
||||
/**
|
||||
* Selector for the persisted `hideAgentInsights` preference. Falls back to
|
||||
* `false` so existing persisted state (written before this field existed)
|
||||
* keeps the verbose timeline visible until the user opts out.
|
||||
*/
|
||||
export const selectHideAgentInsights = (state: { theme: ThemeState }): boolean =>
|
||||
state.theme.hideAgentInsights ?? false;
|
||||
|
||||
/**
|
||||
* Selector for the persisted `developerMode` preference.
|
||||
* Use {@link useDeveloperMode} in components — it combines this with `IS_DEV`.
|
||||
|
||||
Reference in New Issue
Block a user