diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index a540752b33..96af6e541d 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -190,6 +190,12 @@ # Chain of Thought (CoT) display mode. Can be "hidden", "tool_call" or "full". cot = "full" +# CoT display layout. "list" shows each step individually, "compact" collapses into one summary line. +# cot_display = "list" + +# Whether steps are expandable to show input/output details. +# show_step_details = true + # Specify a CSS file that can be used to customize the user interface. # The CSS file can be served from the public directory or via an external link. # custom_css = "/public/test.css" @@ -351,6 +357,8 @@ class UISettings(BaseModel): name: str description: str = "" cot: Literal["hidden", "tool_call", "full"] = "full" + cot_display: Literal["list", "compact"] = "list" + show_step_details: bool = True default_theme: Optional[Literal["light", "dark"]] = "dark" language: Optional[str] = None layout: Optional[Literal["default", "wide"]] = "default" diff --git a/backend/chainlit/translations/ar-SA.json b/backend/chainlit/translations/ar-SA.json index b98b427d23..5708e5f138 100644 --- a/backend/chainlit/translations/ar-SA.json +++ b/backend/chainlit/translations/ar-SA.json @@ -99,7 +99,9 @@ "messages": { "status": { "using": "يستخدم", - "used": "مستخدم" + "used": "مستخدم", + "usedSteps": "استُخدمت {{count}} خطوات", + "usedTools": "استُخدمت {{count}} أدوات" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/bn.json b/backend/chainlit/translations/bn.json index 432d53bea2..7147b68365 100644 --- a/backend/chainlit/translations/bn.json +++ b/backend/chainlit/translations/bn.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "ব্যবহার করছে", - "used": "ব্যবহৃত" + "used": "ব্যবহৃত", + "usedSteps": "{{count}}টি ধাপ ব্যবহার করেছে", + "usedTools": "{{count}}টি টুল ব্যবহার করেছে" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/da-DK.json b/backend/chainlit/translations/da-DK.json index aba214e617..8ee1520f84 100644 --- a/backend/chainlit/translations/da-DK.json +++ b/backend/chainlit/translations/da-DK.json @@ -99,7 +99,9 @@ "messages": { "status": { "using": "Bruger", - "used": "Brugte" + "used": "Brugte", + "usedSteps": "Brugte {{count}} trin", + "usedTools": "Brugte {{count}} værktøjer" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/de-DE.json b/backend/chainlit/translations/de-DE.json index db5934b6f1..5ec10d2a7d 100644 --- a/backend/chainlit/translations/de-DE.json +++ b/backend/chainlit/translations/de-DE.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "Verwendet", - "used": "Verwendete" + "used": "Verwendete", + "usedSteps": "{{count}} Schritte verwendet", + "usedTools": "{{count}} Tools verwendet" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/el-GR.json b/backend/chainlit/translations/el-GR.json index 54e12265f3..ca2b74f8b2 100644 --- a/backend/chainlit/translations/el-GR.json +++ b/backend/chainlit/translations/el-GR.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "Με τη χρήση", - "used": "Χρησιμοποιήθηκε" + "used": "Χρησιμοποιήθηκε", + "usedSteps": "Χρησιμοποίησε {{count}} βήματα", + "usedTools": "Χρησιμοποίησε {{count}} εργαλεία" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/en-US.json b/backend/chainlit/translations/en-US.json index 5a62b3c254..b7286942e1 100644 --- a/backend/chainlit/translations/en-US.json +++ b/backend/chainlit/translations/en-US.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "Using", - "used": "Used" + "used": "Used", + "usedSteps": "Used {{count}} steps", + "usedTools": "Used {{count}} tools" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/es.json b/backend/chainlit/translations/es.json index f47a53f7c6..b7d624a56d 100644 --- a/backend/chainlit/translations/es.json +++ b/backend/chainlit/translations/es.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "Usando", - "used": "Usado" + "used": "Usado", + "usedSteps": "Usó {{count}} pasos", + "usedTools": "Usó {{count}} herramientas" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/fr-FR.json b/backend/chainlit/translations/fr-FR.json index 163be03afd..677bb13695 100644 --- a/backend/chainlit/translations/fr-FR.json +++ b/backend/chainlit/translations/fr-FR.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "Utilise", - "used": "Utilisé" + "used": "Utilisé", + "usedSteps": "{{count}} étapes utilisées", + "usedTools": "{{count}} outils utilisés" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/gu.json b/backend/chainlit/translations/gu.json index 2abb735f99..5f2a909bf3 100644 --- a/backend/chainlit/translations/gu.json +++ b/backend/chainlit/translations/gu.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "વાપરી રહ્યા છે", - "used": "વપરાયેલ" + "used": "વપરાયેલ", + "usedSteps": "{{count}} પગલાં વાપર્યાં", + "usedTools": "{{count}} ટૂલ્સ વાપર્યાં" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/he-IL.json b/backend/chainlit/translations/he-IL.json index b184890121..319f0b54e1 100644 --- a/backend/chainlit/translations/he-IL.json +++ b/backend/chainlit/translations/he-IL.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "משתמש ב", - "used": "השתמש ב" + "used": "השתמש ב", + "usedSteps": "השתמש ב-{{count}} שלבים", + "usedTools": "השתמש ב-{{count}} כלים" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/hi.json b/backend/chainlit/translations/hi.json index 457d70eea6..15db6ad956 100644 --- a/backend/chainlit/translations/hi.json +++ b/backend/chainlit/translations/hi.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "उपयोग कर रहे हैं", - "used": "उपयोग किया" + "used": "उपयोग किया", + "usedSteps": "{{count}} चरणों का उपयोग किया", + "usedTools": "{{count}} टूल्स का उपयोग किया" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/it.json b/backend/chainlit/translations/it.json index 69021fe630..9479ecb47e 100644 --- a/backend/chainlit/translations/it.json +++ b/backend/chainlit/translations/it.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "In uso", - "used": "Utilizzato" + "used": "Utilizzato", + "usedSteps": "Utilizzati {{count}} passaggi", + "usedTools": "Utilizzati {{count}} strumenti" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/ja.json b/backend/chainlit/translations/ja.json index b1ea49659b..9220addeb5 100644 --- a/backend/chainlit/translations/ja.json +++ b/backend/chainlit/translations/ja.json @@ -99,7 +99,9 @@ "messages": { "status": { "using": "使用中", - "used": "使用済み" + "used": "使用済み", + "usedSteps": "{{count}}個のステップを使用", + "usedTools": "{{count}}個のツールを使用" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/kn.json b/backend/chainlit/translations/kn.json index ef59021765..e3e68d738f 100644 --- a/backend/chainlit/translations/kn.json +++ b/backend/chainlit/translations/kn.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "ಬಳಸುತ್ತಿರುವುದು", - "used": "ಬಳಸಲಾಗಿದೆ" + "used": "ಬಳಸಲಾಗಿದೆ", + "usedSteps": "{{count}} ಹಂತಗಳನ್ನು ಬಳಸಲಾಗಿದೆ", + "usedTools": "{{count}} ಸಾಧನಗಳನ್ನು ಬಳಸಲಾಗಿದೆ" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/ko.json b/backend/chainlit/translations/ko.json index f05622faf1..b26f4a0896 100644 --- a/backend/chainlit/translations/ko.json +++ b/backend/chainlit/translations/ko.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "사용 중", - "used": "사용됨" + "used": "사용됨", + "usedSteps": "{{count}}개의 단계 사용됨", + "usedTools": "{{count}}개의 도구 사용됨" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/ml.json b/backend/chainlit/translations/ml.json index 5e01cd402e..d0aeda2755 100644 --- a/backend/chainlit/translations/ml.json +++ b/backend/chainlit/translations/ml.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "ഉപയോഗിക്കുന്നു", - "used": "ഉപയോഗിച്ചു" + "used": "ഉപയോഗിച്ചു", + "usedSteps": "{{count}} ഘട്ടങ്ങൾ ഉപയോഗിച്ചു", + "usedTools": "{{count}} ടൂളുകൾ ഉപയോഗിച്ചു" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/mr.json b/backend/chainlit/translations/mr.json index c559f83cfc..3aebc2c917 100644 --- a/backend/chainlit/translations/mr.json +++ b/backend/chainlit/translations/mr.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "वापरत आहे", - "used": "वापरले" + "used": "वापरले", + "usedSteps": "{{count}} पायऱ्या वापरल्या", + "usedTools": "{{count}} साधने वापरली" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/nl.json b/backend/chainlit/translations/nl.json index b4760191a2..424f2b4434 100644 --- a/backend/chainlit/translations/nl.json +++ b/backend/chainlit/translations/nl.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "In gebruik", - "used": "Gebruikt" + "used": "Gebruikt", + "usedSteps": "{{count}} stappen gebruikt", + "usedTools": "{{count}} hulpmiddelen gebruikt" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/pt-PT.json b/backend/chainlit/translations/pt-PT.json index a1f1497059..f0b691444d 100644 --- a/backend/chainlit/translations/pt-PT.json +++ b/backend/chainlit/translations/pt-PT.json @@ -12,44 +12,44 @@ "loading": "A carregar...", "error": { "default": "Ocorreu um erro", - "serverConnection": "N\u00e3o foi poss\u00edvel estabelecer liga\u00e7\u00e3o ao servidor" + "serverConnection": "Não foi possível estabelecer ligação ao servidor" } } }, "auth": { "login": { - "title": "Inicie sess\u00e3o para aceder \u00e0 aplica\u00e7\u00e3o", + "title": "Inicie sessão para aceder à aplicação", "form": { "email": { "label": "E-mail", - "required": "o e-mail \u00e9 obrigat\u00f3rio", + "required": "o e-mail é obrigatório", "placeholder": "me@example.com" }, "password": { "label": "Palavra-passe", - "required": "a palavra-passe \u00e9 obrigat\u00f3ria" + "required": "a palavra-passe é obrigatória" }, "actions": { - "signin": "Iniciar sess\u00e3o" + "signin": "Iniciar sessão" }, "alternativeText": { "or": "Ou" } }, "errors": { - "default": "N\u00e3o foi poss\u00edvel iniciar sess\u00e3o", - "signin": "Tente iniciar sess\u00e3o com outra conta", - "oauthSignin": "Tente iniciar sess\u00e3o com outra conta", - "redirectUriMismatch": "O URI de redirecionamento n\u00e3o corresponde \u00e0 configura\u00e7\u00e3o da aplica\u00e7\u00e3o OAuth", - "oauthCallback": "Tente iniciar sess\u00e3o com outra conta", - "oauthCreateAccount": "Tente iniciar sess\u00e3o com outra conta", - "emailCreateAccount": "Tente iniciar sess\u00e3o com outra conta", - "callback": "Tente iniciar sess\u00e3o com outra conta", - "oauthAccountNotLinked": "Para confirmar a sua identidade, inicie sess\u00e3o com a mesma conta utilizada anteriormente", - "emailSignin": "N\u00e3o foi poss\u00edvel enviar o e-mail", + "default": "Não foi possível iniciar sessão", + "signin": "Tente iniciar sessão com outra conta", + "oauthSignin": "Tente iniciar sessão com outra conta", + "redirectUriMismatch": "O URI de redirecionamento não corresponde à configuração da aplicação OAuth", + "oauthCallback": "Tente iniciar sessão com outra conta", + "oauthCreateAccount": "Tente iniciar sessão com outra conta", + "emailCreateAccount": "Tente iniciar sessão com outra conta", + "callback": "Tente iniciar sessão com outra conta", + "oauthAccountNotLinked": "Para confirmar a sua identidade, inicie sessão com a mesma conta utilizada anteriormente", + "emailSignin": "Não foi possível enviar o e-mail", "emailVerify": "Por favor, verifique o seu e-mail. Foi enviada uma nova mensagem", - "credentialsSignin": "Erro ao iniciar sess\u00e3o. Verifique se os dados fornecidos est\u00e3o corretos", - "sessionRequired": "Por favor, inicie sess\u00e3o para aceder a esta p\u00e1gina" + "credentialsSignin": "Erro ao iniciar sessão. Verifique se os dados fornecidos estão corretos", + "sessionRequired": "Por favor, inicie sessão para aceder a esta página" } }, "provider": { @@ -70,18 +70,18 @@ "headline": "Mensagens favoritas", "remove": "Remover favorito", "empty": { - "title": "Ainda n\u00e3o h\u00e1 prompts guardados", - "description": "Comece por enviar um prompt e marc\u00e1-lo com estrela, ou marque com estrela um prompt de conversas anteriores" + "title": "Ainda não há prompts guardados", + "description": "Comece por enviar um prompt e marcá-lo com estrela, ou marque com estrela um prompt de conversas anteriores" } }, "commands": { "button": "Ferramentas", "changeTool": "Alterar ferramenta", - "availableTools": "Ferramentas dispon\u00edveis" + "availableTools": "Ferramentas disponíveis" }, "speech": { - "start": "Iniciar grava\u00e7\u00e3o", - "stop": "Parar grava\u00e7\u00e3o", + "start": "Iniciar gravação", + "stop": "Parar gravação", "connecting": "A ligar" }, "fileUpload": { @@ -100,39 +100,41 @@ "messages": { "status": { "using": "A utilizar", - "used": "Utilizado" + "used": "Utilizado", + "usedSteps": "{{count}} passos utilizados", + "usedTools": "{{count}} ferramentas utilizadas" }, "actions": { "copy": { - "button": "Copiar para a \u00e1rea de transfer\u00eancia", + "button": "Copiar para a área de transferência", "success": "Copiado!" } }, "feedback": { - "positive": "\u00datil", - "negative": "N\u00e3o \u00fatil", - "edit": "Editar coment\u00e1rio", + "positive": "Útil", + "negative": "Não útil", + "edit": "Editar comentário", "dialog": { - "title": "Adicionar um coment\u00e1rio", - "submit": "Enviar coment\u00e1rio", - "yourFeedback": "O seu coment\u00e1rio..." + "title": "Adicionar um comentário", + "submit": "Enviar comentário", + "yourFeedback": "O seu comentário..." }, "status": { "updating": "A atualizar", - "updated": "Coment\u00e1rio atualizado" + "updated": "Comentário atualizado" } } }, "history": { - "title": "\u00daltimas entradas", - "empty": "Est\u00e1 vazio...", - "show": "Mostrar hist\u00f3rico" + "title": "Últimas entradas", + "empty": "Está vazio...", + "show": "Mostrar histórico" }, "settings": { - "title": "Painel de configura\u00e7\u00f5es", - "customize": "Personalize aqui as configura\u00e7\u00f5es do seu chat" + "title": "Painel de configurações", + "customize": "Personalize aqui as configurações do seu chat" }, - "watermark": "Os modelos de linguagem podem cometer erros. Verifique sempre informa\u00e7\u00f5es importantes." + "watermark": "Os modelos de linguagem podem cometer erros. Verifique sempre informações importantes." }, "threadHistory": { "sidebar": { @@ -144,8 +146,8 @@ "timeframes": { "today": "Hoje", "yesterday": "Ontem", - "previous7days": "\u00daltimos 7 dias", - "previous30days": "\u00daltimos 30 dias" + "previous7days": "Últimos 7 dias", + "previous30days": "Últimos 30 dias" }, "empty": "Nenhuma conversa encontrada", "actions": { @@ -154,7 +156,7 @@ } }, "thread": { - "untitled": "Conversa sem t\u00edtulo", + "untitled": "Conversa sem título", "menu": { "rename": "Renomear", "share": "Partilhar", @@ -162,21 +164,21 @@ }, "actions": { "share": { - "title": "Partilhar liga\u00e7\u00e3o do chat", + "title": "Partilhar ligação do chat", "button": "Partilhar", "status": { - "copied": "Liga\u00e7\u00e3o copiada", - "created": "Liga\u00e7\u00e3o de partilha criada!", + "copied": "Ligação copiada", + "created": "Ligação de partilha criada!", "unshared": "Partilha desativada para esta conversa" }, "error": { - "create": "Erro ao criar liga\u00e7\u00e3o de partilha", + "create": "Erro ao criar ligação de partilha", "unshare": "Erro ao desativar a partilha" } }, "delete": { - "title": "Confirmar elimina\u00e7\u00e3o", - "description": "Ir\u00e1 eliminar a conversa e todos os seus conte\u00fados. Esta a\u00e7\u00e3o n\u00e3o pode ser anulada.", + "title": "Confirmar eliminação", + "description": "Irá eliminar a conversa e todos os seus conteúdos. Esta ação não pode ser anulada.", "success": "Chat eliminado", "inProgress": "A eliminar chat" }, @@ -209,42 +211,42 @@ "button": "Novo chat", "dialog": { "title": "Criar novo chat", - "description": "Isto ir\u00e1 apagar o hist\u00f3rico de chat atual. Tem a certeza de que pretende continuar?", + "description": "Isto irá apagar o histórico de chat atual. Tem a certeza de que pretende continuar?", "tooltip": "Novo chat" } }, "user": { "menu": { - "settings": "Configura\u00e7\u00f5es", + "settings": "Configurações", "settingsKey": "S", "apiKeys": "Chaves API", - "logout": "Terminar sess\u00e3o" + "logout": "Terminar sessão" } } }, "apiKeys": { - "title": "Chaves API necess\u00e1rias", - "description": "Para utilizar esta aplica\u00e7\u00e3o, s\u00e3o necess\u00e1rias as seguintes chaves API. As chaves s\u00e3o guardadas localmente no seu dispositivo.", + "title": "Chaves API necessárias", + "description": "Para utilizar esta aplicação, são necessárias as seguintes chaves API. As chaves são guardadas localmente no seu dispositivo.", "success": { "saved": "Guardado com sucesso" } }, "alerts": { - "info": "Informa\u00e7\u00e3o", + "info": "Informação", "note": "Nota", "tip": "Dica", "important": "Importante", "warning": "Aviso", "caution": "Cuidado", - "debug": "Depura\u00e7\u00e3o", + "debug": "Depuração", "example": "Exemplo", "success": "Sucesso", "help": "Ajuda", "idea": "Ideia", "pending": "Pendente", - "security": "Seguran\u00e7a", + "security": "Segurança", "beta": "Beta", - "best-practice": "Boa pr\u00e1tica" + "best-practice": "Boa prática" }, "components": { "MultiSelectInput": { diff --git a/backend/chainlit/translations/ta.json b/backend/chainlit/translations/ta.json index be051916f7..abc2ee38d7 100644 --- a/backend/chainlit/translations/ta.json +++ b/backend/chainlit/translations/ta.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "பயன்படுத்துகிறது", - "used": "பயன்படுத்தப்பட்டது" + "used": "பயன்படுத்தப்பட்டது", + "usedSteps": "{{count}} படிகள் பயன்படுத்தப்பட்டன", + "usedTools": "{{count}} கருவிகள் பயன்படுத்தப்பட்டன" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/te.json b/backend/chainlit/translations/te.json index 5849a3a1a4..d00c3a617e 100644 --- a/backend/chainlit/translations/te.json +++ b/backend/chainlit/translations/te.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "ఉపయోగిస్తోంది", - "used": "ఉపయోగించబడింది" + "used": "ఉపయోగించబడింది", + "usedSteps": "{{count}} దశలు ఉపయోగించబడ్డాయి", + "usedTools": "{{count}} సాధనాలు ఉపయోగించబడ్డాయి" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/zh-CN.json b/backend/chainlit/translations/zh-CN.json index bce16c1149..5e4f9adeb1 100644 --- a/backend/chainlit/translations/zh-CN.json +++ b/backend/chainlit/translations/zh-CN.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "使用中", - "used": "已使用" + "used": "已使用", + "usedSteps": "使用了 {{count}} 个步骤", + "usedTools": "使用了 {{count}} 个工具" }, "actions": { "copy": { diff --git a/backend/chainlit/translations/zh-TW.json b/backend/chainlit/translations/zh-TW.json index 4c475b61d1..e437a546a5 100644 --- a/backend/chainlit/translations/zh-TW.json +++ b/backend/chainlit/translations/zh-TW.json @@ -100,7 +100,9 @@ "messages": { "status": { "using": "正在使用", - "used": "已使用" + "used": "已使用", + "usedSteps": "使用了 {{count}} 個步驟", + "usedTools": "使用了 {{count}} 個工具" }, "actions": { "copy": { diff --git a/frontend/src/components/ReadOnlyThread.tsx b/frontend/src/components/ReadOnlyThread.tsx index 4036b4ed8c..b8b70b70f4 100644 --- a/frontend/src/components/ReadOnlyThread.tsx +++ b/frontend/src/components/ReadOnlyThread.tsx @@ -163,6 +163,8 @@ const ReadOnlyThread = ({ id }: Props) => { showFeedbackButtons: !!config?.dataPersistence, uiName: config?.ui?.name || '', cot: config?.ui?.cot || 'hidden', + cotDisplay: config?.ui?.cot_display || 'list', + showStepDetails: config?.ui?.show_step_details ?? true, onElementRefClick, onError, onFeedbackUpdated, @@ -171,8 +173,12 @@ const ReadOnlyThread = ({ id }: Props) => { }, [ config?.ui?.name, config?.ui?.cot, + config?.ui?.cot_display, + config?.ui?.show_step_details, config?.features?.unsafe_allow_html, + config?.features?.latex, config?.features?.user_message_markdown, + config?.dataPersistence, onElementRefClick, onError, onFeedbackUpdated, diff --git a/frontend/src/components/chat/Messages/CompactSteps.tsx b/frontend/src/components/chat/Messages/CompactSteps.tsx new file mode 100644 index 0000000000..4a5621b22f --- /dev/null +++ b/frontend/src/components/chat/Messages/CompactSteps.tsx @@ -0,0 +1,138 @@ +import { cn } from '@/lib/utils'; +import { MessageContext } from 'contexts/MessageContext'; +import { memo, useContext, useMemo, useState } from 'react'; + +import type { IAction, IMessageElement, IStep } from '@chainlit/react-client'; + +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger +} from '@/components/ui/accordion'; +import { Translator } from 'components/i18n'; + +import { Messages } from '.'; +import { MessageAvatar } from './Message/Avatar'; + +interface Props { + steps: IStep[]; + elements: IMessageElement[]; + actions: IAction[]; + indent: number; + isRunning?: boolean; + scorableRun?: IStep; +} + +// Recursively collect tool/step-type nodes for counting and naming +const collectVisible = (steps: IStep[], cot: string): IStep[] => { + const result: IStep[] = []; + for (const s of steps) { + if (!s.type.includes('message')) { + if (cot !== 'tool_call' || s.type === 'tool') result.push(s); + } + if (s.steps) result.push(...collectVisible(s.steps, cot)); + } + return result; +}; + +const CompactSteps = memo( + ({ steps, elements, actions, indent, isRunning, scorableRun }: Props) => { + const { cot } = useContext(MessageContext); + const [openValue, setOpenValue] = useState(''); + + // All non-message step children for rendering (Message skip logic drills through intermediates) + const stepChildren = useMemo(() => { + return steps.filter((s) => !s.type.includes('message')); + }, [steps]); + + // Recursively collected visible steps for count and label + const visibleSteps = useMemo(() => { + return collectVisible(steps, cot); + }, [steps, cot]); + + // Show "Using" until an assistant_message appears (meaning tools are done + // and the model is streaming). This avoids flashing between sequential tools + // (no assistant_message yet) while still switching once the answer begins. + const hasAnswer = useMemo(() => { + const check = (items: IStep[]): boolean => + items.some( + (s) => + s.type === 'assistant_message' || (s.steps ? check(s.steps) : false) + ); + return check(steps); + }, [steps]); + + const showUsing = !!isRunning && !hasAnswer; + + // Get the last visible step name for the "Using X" label + const lastStep = visibleSteps[visibleSteps.length - 1]; + const lastStepName = lastStep?.name || ''; + + // Determine the label translation key for the finished state + const usedLabelPath = + cot === 'tool_call' + ? 'chat.messages.status.usedTools' + : 'chat.messages.status.usedSteps'; + + const accordionId = 'compact-steps'; + + return ( +
+
+
+ +
+ setOpenValue(val)} + className="w-full" + > + + + {showUsing ? ( + <> + {' '} + {lastStepName} + + ) : ( + + )} + + +
+ +
+
+
+
+
+
+
+
+ ); + } +); + +CompactSteps.displayName = 'CompactSteps'; + +export { CompactSteps }; diff --git a/frontend/src/components/chat/Messages/Message/Step.tsx b/frontend/src/components/chat/Messages/Message/Step.tsx index 96432437bc..7504a0feea 100644 --- a/frontend/src/components/chat/Messages/Message/Step.tsx +++ b/frontend/src/components/chat/Messages/Message/Step.tsx @@ -1,5 +1,12 @@ import { cn } from '@/lib/utils'; -import { PropsWithChildren, useEffect, useMemo, useState } from 'react'; +import { MessageContext } from 'contexts/MessageContext'; +import { + PropsWithChildren, + useContext, + useEffect, + useMemo, + useState +} from 'react'; import type { IStep } from '@chainlit/react-client'; @@ -21,6 +28,8 @@ export default function Step({ children, isRunning }: PropsWithChildren) { + const { showStepDetails } = useContext(MessageContext); + const using = useMemo(() => { return isRunning && step.start && !step.end && !step.isError; }, [step, isRunning]); @@ -40,8 +49,8 @@ export default function Step({ } }, [using, step.autoCollapse]); - // If there's no content, just render the status without accordion - if (!hasContent) { + // If there's no content or step details are disabled, just render the status without accordion + if (!hasContent || !showStepDetails) { return (

{ ); }; +const countVisibleSteps = (steps: IStep[], cot: string): number => { + let count = 0; + for (const s of steps) { + if (!s.type.includes('message')) { + if (cot !== 'tool_call' || s.type === 'tool') count++; + } + if (s.steps) count += countVisibleSteps(s.steps, cot); + } + return count; +}; + const Messages = memo( ({ messages, elements, actions, indent, isRunning, scorableRun }: Props) => { const messageContext = useContext(MessageContext); @@ -76,17 +88,54 @@ const Messages = memo( // Ignore on_chat_start for scorable run const scorableRun = !isRunning && m.name !== 'on_chat_start' ? m : undefined; + + // Determine if compact mode should be used + const useCompact = + messageContext.cotDisplay === 'compact' && + messageContext.cot !== 'hidden'; + + // Only use compact when there are 2+ steps worth grouping + const visibleStepCount = useCompact + ? countVisibleSteps(m.steps || [], messageContext.cot) + : 0; + + const showCompact = useCompact && visibleStepCount > 1; + return ( {m.steps?.length ? ( - + showCompact ? ( + <> + + {/* Render message-type children at root level */} + + s.type.includes('message') + )} + elements={elements} + actions={actions} + indent={indent} + isRunning={isRunning} + scorableRun={scorableRun} + /> + + ) : ( + + ) ) : null} {(showToolCoTLoader || showHiddenCoTLoader) && m.name !== 'on_chat_start' ? ( diff --git a/frontend/src/components/chat/MessagesContainer/index.tsx b/frontend/src/components/chat/MessagesContainer/index.tsx index 18899fec92..f89835560e 100644 --- a/frontend/src/components/chat/MessagesContainer/index.tsx +++ b/frontend/src/components/chat/MessagesContainer/index.tsx @@ -164,6 +164,8 @@ const MessagesContainer = ({ navigate }: Props) => { showFeedbackButtons: enableFeedback, uiName: config?.ui?.name || '', cot: config?.ui?.cot || 'hidden', + cotDisplay: config?.ui?.cot_display || 'list', + showStepDetails: config?.ui?.show_step_details ?? true, onElementRefClick, onError, onFeedbackUpdated, @@ -175,6 +177,8 @@ const MessagesContainer = ({ navigate }: Props) => { loading, config?.ui?.name, config?.ui?.cot, + config?.ui?.cot_display, + config?.ui?.show_step_details, config?.features?.unsafe_allow_html, config?.features?.user_message_markdown, onElementRefClick, diff --git a/frontend/src/contexts/MessageContext.tsx b/frontend/src/contexts/MessageContext.tsx index 45735b61f2..e0350db23c 100644 --- a/frontend/src/contexts/MessageContext.tsx +++ b/frontend/src/contexts/MessageContext.tsx @@ -11,7 +11,9 @@ const defaultMessageContext = { showFeedbackButtons: true, onError: () => undefined, uiName: '', - cot: 'hidden' as const + cot: 'hidden' as const, + cotDisplay: 'list' as const, + showStepDetails: true }; const MessageContext = createContext(defaultMessageContext); diff --git a/frontend/src/types/messageContext.ts b/frontend/src/types/messageContext.ts index 87b79f1800..8b81f6c394 100644 --- a/frontend/src/types/messageContext.ts +++ b/frontend/src/types/messageContext.ts @@ -12,6 +12,8 @@ interface IMessageContext { onProgress: (progress: number) => void ) => { xhr: XMLHttpRequest; promise: Promise }; cot: 'hidden' | 'tool_call' | 'full'; + cotDisplay: 'list' | 'compact'; + showStepDetails: boolean; askUser?: IAsk; editable: boolean; loading: boolean; diff --git a/libs/react-client/src/types/config.ts b/libs/react-client/src/types/config.ts index 52a28cc7a3..ed3d2a2a94 100644 --- a/libs/react-client/src/types/config.ts +++ b/libs/react-client/src/types/config.ts @@ -46,6 +46,8 @@ export interface IChainlitConfig { default_chat_settings_open?: boolean; confirm_new_chat?: boolean; cot: 'hidden' | 'tool_call' | 'full'; + cot_display?: 'list' | 'compact'; + show_step_details?: boolean; github?: string; custom_css?: string; custom_js?: string;