From 3c5a7c69ffa47b01a0d09eae40142ec3401cbf28 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:13:18 +0000 Subject: [PATCH 1/2] New chat page: What Shell:B can do (everyday tasks, agents and apps) Below the agent's starter cards, the empty chat now shows three tabs: Try something (everyday tasks, each switching the chat to the agent best at it, then sending), Agents (who does what; picking one sets it for the chat) and Apps (every app with its one-line hint). Hide collapses it to a single link, remembered per browser. Shared by the desktop and phone UIs. Synthfield's card readout uses decimal-leading-zero so a tenth card reads 10, not 010. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01G13cXQwUfmo3PoJsiW8g8j --- docs/changelog.json | 6 + ui/src/App.tsx | 29 ++--- ui/src/components/NewChatHome.tsx | 200 ++++++++++++++++++++++++++++++ ui/src/i18n/fr.json | 45 ++++++- ui/src/mobile/MobileApp.tsx | 58 ++++----- ui/src/themes/synthfield.css | 2 +- 6 files changed, 289 insertions(+), 51 deletions(-) create mode 100644 ui/src/components/NewChatHome.tsx diff --git a/docs/changelog.json b/docs/changelog.json index 96f29f0..ee99e90 100644 --- a/docs/changelog.json +++ b/docs/changelog.json @@ -1,4 +1,10 @@ [ + { + "date": "2026-10-13", + "area": "Chat", + "title": "The new chat page shows what Shell:B can do", + "summary": "Below the agent's starter cards, a new chat now has a What Shell:B can do section with three tabs. Try something offers everyday tasks (prepare for a job interview, polish a cover letter, identify a sea creature, step back into the Neolithic, get notes on your writing, research a topic, summarise a document, make a picture, get a quick answer); each one switches the chat to the agent best at it and sends. Agents lists every agent with what it is for: picking one sets it for the chat and its starter cards change to match. Apps lists every app with a line on what it does, and opens it. Hide tucks the section away behind a single link, remembered per browser, for anyone who already knows their way around." + }, { "date": "2026-10-13", "area": "Chat", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 1b6e18a..daa607c 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -41,6 +41,7 @@ import { ContextMeter } from './components/composer/ContextMeter'; import { isCell, type GuestPanel } from './cell'; import { GuestPanelHost } from './components/CellGuest'; import { DocPanel } from './components/DocPanel'; +import { NewChatHome } from './components/NewChatHome'; import { onDocView, type DocTarget } from './docView'; const Studio = lazy(() => import('./studio/Studio')); @@ -466,6 +467,15 @@ export function App() { } }, [conv]); + // a "Try something" card on the empty chat: the chat switches to the card's agent, and the prompt goes once it has + const [pendingStart, setPendingStart] = useState<{ agentId: string; prompt: string } | null>(null); + const startWith = useCallback((a: Agent, prompt: string) => { selectAgent(a); setPendingStart({ agentId: a.id, prompt }); }, [selectAgent]); + useEffect(() => { + if (!pendingStart || agent?.id !== pendingStart.agentId) return; + setPendingStart(null); + send(pendingStart.prompt, []); + }, [pendingStart, agent, send]); + /** `incognito`: an incognito copy of the chat (server/privacy.py); the original is left as it was */ const forkChat = useCallback(async (convId: string, messageId?: string, incognito = false) => { try { @@ -883,23 +893,8 @@ export function App() {
{ const el = e.currentTarget; setAtBottom(el.scrollHeight - el.scrollTop - el.clientHeight < 80); }}> {messages.length === 0 ? ( -
- -

{greeting()}.

-

- {agent.name} · {agent.description} -

-
- {quick.map(q => ( - - ))} -
-

{t('Overthink. Overcomplicate. Overengineer.')}

-
+ send(prompt, [])} onStart={startWith} onSelectAgent={selectAgent} onOpenApp={openApp} /> ) : (
{foldSummarized(messages).map(m => ( diff --git a/ui/src/components/NewChatHome.tsx b/ui/src/components/NewChatHome.tsx new file mode 100644 index 0000000..cb412a2 --- /dev/null +++ b/ui/src/components/NewChatHome.tsx @@ -0,0 +1,200 @@ +import { useRef, useState } from 'react'; +import clsx from 'clsx'; +import { ChevronDown, ChevronUp } from 'lucide-react'; +import { t } from '../i18n'; +import type { Agent } from '../types'; +import type { Suggestion } from '../suggestions'; +import { APPS, GROUP_LABEL, type AppGroup, type AppId } from './apps'; +import { AgentAvatar } from './common'; + +/** + * The empty chat: the agent's own starter cards, then "What Shell:B can do" for people who don't know yet. Three tabs: + * Try something (everyday tasks, each answered by the agent best at it), Agents (who is who) and Apps (everything on the + * rail and in All apps). People who know their way around hide it once; that and the tab are remembered per browser. + */ + +interface Starter { title: string; blurb: string; prompt: string; agent: string } + +// Everyday tasks across the range of people who use Shell:B. General on purpose: this screen can be seen by anyone. +const STARTERS: Starter[] = [ + { agent: 'shellb', title: 'Prepare for a job interview', blurb: 'A practice run with feedback after each answer.', + prompt: "Run a mock job interview with me. First ask me about the role and the organisation, then ask one question at a time and give me honest feedback after each answer." }, + { agent: 'shellb', title: 'Polish a cover letter', blurb: 'Matched to the job ad, still in your voice.', + prompt: 'I will paste a cover letter and the job ad it is for. Tighten the letter, match it to what the ad asks for, and keep my voice.' }, + { agent: 'thalassa', title: 'Identify a sea creature', blurb: 'Species names checked against the World Register of Marine Species.', + prompt: 'Help me identify a marine animal. I will tell you where I saw it, its size, colour and shape. Give me the likely species with scientific names and how to tell them apart.' }, + { agent: 'mimir', title: 'Step back into the Neolithic', blurb: 'Ancient sites, skies and coastlines, worked out on this machine.', + prompt: 'When Newgrange was built, about 5,200 years ago, where did the midwinter sun rise as seen from it, and how different was the sea level then?' }, + { agent: 'shellb', title: 'Get notes on my writing', blurb: "An editor's read on pacing, voice and clarity.", + prompt: "I will paste a passage from something I am writing. Give me an editor's notes on pacing, voice and clarity, then suggest three lines I could cut." }, + { agent: 'minerva', title: 'Research a topic in depth', blurb: 'A report built from many sources, with citations.', + prompt: 'Research a topic in depth for me: search widely, read the primary sources and write a short report with citations. Ask me for the topic first.' }, + { agent: 'shellb', title: 'Summarise a long document', blurb: 'The key points, decisions and deadlines.', + prompt: 'I will attach a long document. Summarise it in one paragraph, then list the decisions, deadlines and open questions in it.' }, + { agent: 'nyx', title: 'Make a picture', blurb: 'Images, music and sound effects, made locally.', + prompt: 'Paint a watercolour of a quiet harbour at dawn: fishing boats, gulls and soft pastel light.' }, + { agent: 'spark', title: 'Get a quick answer', blurb: 'Fast replies for small questions.', + prompt: 'Convert 180 °C to Fahrenheit, and tell me which gas mark that is.' }, +]; + +// each grid numbers its own cards in themes that number them (synthfield's 01, 02…) +const NUMBERED = { counterReset: 'sf-mission' }; + +// opt-in agents stay in the agent menu but are never advertised on a screen others can see +const NOT_SHOWCASED = new Set(['aphrodite']); +const APP_GROUPS: AppGroup[] = ['make', 'know', 'run']; + +type Tab = 'try' | 'agents' | 'apps'; +const KEY = 'shellb.home'; +interface Prefs { hidden?: boolean; tab?: Tab } +const readPrefs = (): Prefs => { try { return JSON.parse(localStorage.getItem(KEY) || '{}') as Prefs; } catch { return {}; } }; +const savePrefs = (p: Prefs) => { try { localStorage.setItem(KEY, JSON.stringify(p)); } catch { /* private mode */ } }; + +const card = 'sf-card rounded-xl border border-[var(--border-subtle)] bg-[var(--bg-secondary)] hover:bg-[var(--bg-hover)] hover:border-[var(--border-strong)] transition group text-left'; + +export function NewChatHome({ agent, agents, quick, greeting, onSend, onStart, onSelectAgent, onOpenApp }: { + agent: Agent; + agents: Agent[]; + quick: Suggestion[]; + greeting: string; + /** a starter card for the agent on screen */ + onSend: (prompt: string) => void; + /** a "Try something" card: switch the new chat to that agent, then send */ + onStart: (agent: Agent, prompt: string) => void; + onSelectAgent: (agent: Agent) => void; + onOpenApp: (app: AppId) => void; +}) { + const [prefs, setPrefs] = useState(readPrefs); + const tab = prefs.tab ?? 'try'; + const update = (p: Prefs) => { const next = { ...prefs, ...p }; setPrefs(next); savePrefs(next); }; + const top = useRef(null); + + const showcased = agents.filter(a => !a.unavailable && !NOT_SHOWCASED.has(a.id)); + const byId = new Map(showcased.map(a => [a.id, a])); + const starters = STARTERS.flatMap(s => { const a = byId.get(s.agent); return a ? [{ ...s, a }] : []; }); + const local = showcased.filter(a => !a.cloud), cloud = showcased.filter(a => a.cloud); + + const tabs: { id: Tab; label: string }[] = [ + { id: 'try', label: t('Try something') }, { id: 'agents', label: t('Agents') }, { id: 'apps', label: t('Apps') }, + ]; + + const agentCard = (a: Agent) => ( + + ); + + return ( +
+ +

{greeting}.

+

+ {agent.name} · {agent.description} +

+
+ {quick.map(q => ( + + ))} +
+ + {prefs.hidden ? ( + + ) : ( +
+
+

{t('What Shell:B can do')}

+
+ {tabs.map(x => ( + + ))} +
+ +
+ + {tab === 'try' && ( +
+ {starters.map(s => ( + + ))} +
+ )} + + {tab === 'agents' && ( +
+

+ {t('Each agent is good at something different. Pick one for this chat, or type @ and a name to ask one for a single reply.')} +

+
{local.map(agentCard)}
+ {cloud.length > 0 && (<> +

{t('Cloud')}

+
{cloud.map(agentCard)}
+ )} + +
+ )} + + {tab === 'apps' && ( +
+ {APP_GROUPS.map(g => { + const apps = APPS.filter(a => a.group === g); + if (!apps.length) return null; + return ( +
+

{t(GROUP_LABEL[g])}

+
+ {apps.map(a => { + const Icon = a.icon; + return ( + + ); + })} +
+
+ ); + })} +
+ )} +
+ )} + +

{t('Overthink. Overcomplicate. Overengineer.')}

+
+ ); +} diff --git a/ui/src/i18n/fr.json b/ui/src/i18n/fr.json index 663213d..d17f2b9 100644 --- a/ui/src/i18n/fr.json +++ b/ui/src/i18n/fr.json @@ -4035,5 +4035,48 @@ "Checks finished": "Vérifications terminées", "Run checks": "Lancer les vérifications", "Passes checks": "passent les vérifications", - "Every string passes the checks": "Toutes les chaînes passent les vérifications" + "Every string passes the checks": "Toutes les chaînes passent les vérifications", + "Try something": "Essayer", + "What Shell:B can do": "Ce que Shell:B sait faire", + "Show what Shell:B can do": "Voir ce que Shell:B sait faire", + "Hide this until you show it again": "Masquer jusqu’à ce que vous l’affichiez de nouveau", + "Chat with {name}": "Discuter avec {name}", + "Each agent is good at something different. Pick one for this chat, or type @ and a name to ask one for a single reply.": "Chaque agent a son domaine. Choisissez-en un pour cette discussion, ou tapez @ suivi d’un nom pour lui demander une seule réponse.", + "Create or tune agents in the Agent Lab": "Créer ou ajuster des agents dans le Labo des agents", + "Prepare for a job interview": "Préparer un entretien d’embauche", + "A practice run with feedback after each answer.": "Un entraînement, avec un retour après chaque réponse.", + "Run a mock job interview with me. First ask me about the role and the organisation, then ask one question at a time and give me honest feedback after each answer.": "Fais-moi passer un entretien d’embauche fictif. Demande-moi d’abord le poste et l’organisation, puis pose une question à la fois et donne-moi un retour honnête après chaque réponse.", + "Polish a cover letter": "Peaufiner une lettre de motivation", + "Matched to the job ad, still in your voice.": "Adaptée à l’annonce, toujours avec votre voix.", + "I will paste a cover letter and the job ad it is for. Tighten the letter, match it to what the ad asks for, and keep my voice.": "Je vais coller une lettre de motivation et l’annonce à laquelle elle répond. Resserre la lettre, adapte-la à ce que demande l’annonce et garde ma voix.", + "Identify a sea creature": "Identifier un animal marin", + "Species names checked against the World Register of Marine Species.": "Noms d’espèces vérifiés dans le Registre mondial des espèces marines.", + "Help me identify a marine animal. I will tell you where I saw it, its size, colour and shape. Give me the likely species with scientific names and how to tell them apart.": "Aide-moi à identifier un animal marin. Je vais te dire où je l’ai vu, sa taille, sa couleur et sa forme. Donne-moi les espèces probables avec leur nom scientifique et comment les distinguer.", + "Step back into the Neolithic": "Remonter au Néolithique", + "Ancient sites, skies and coastlines, worked out on this machine.": "Sites anciens, ciels et rivages d’autrefois, calculés sur cette machine.", + "When Newgrange was built, about 5,200 years ago, where did the midwinter sun rise as seen from it, and how different was the sea level then?": "Quand Newgrange a été construit, il y a environ 5 200 ans, où se levait le soleil du solstice d’hiver vu du monument, et quelle était alors la différence de niveau de la mer ?", + "Get notes on my writing": "Avoir un avis sur mon texte", + "An editor's read on pacing, voice and clarity.": "Une lecture d’éditeur : rythme, voix et clarté.", + "I will paste a passage from something I am writing. Give me an editor's notes on pacing, voice and clarity, then suggest three lines I could cut.": "Je vais coller un passage de ce que j’écris. Donne-moi des notes d’éditeur sur le rythme, la voix et la clarté, puis propose trois lignes que je pourrais couper.", + "Research a topic in depth": "Approfondir un sujet", + "A report built from many sources, with citations.": "Un rapport tiré de nombreuses sources, avec citations.", + "Research a topic in depth for me: search widely, read the primary sources and write a short report with citations. Ask me for the topic first.": "Fais une recherche approfondie pour moi : cherche largement, lis les sources primaires et rédige un court rapport avec citations. Demande-moi d’abord le sujet.", + "Summarise a long document": "Résumer un long document", + "The key points, decisions and deadlines.": "Les points clés, les décisions et les échéances.", + "I will attach a long document. Summarise it in one paragraph, then list the decisions, deadlines and open questions in it.": "Je vais joindre un long document. Résume-le en un paragraphe, puis liste les décisions, les échéances et les questions ouvertes qu’il contient.", + "Make a picture": "Créer une image", + "Images, music and sound effects, made locally.": "Images, musique et effets sonores, créés en local.", + "Paint a watercolour of a quiet harbour at dawn: fishing boats, gulls and soft pastel light.": "Peins une aquarelle d’un port paisible à l’aube : bateaux de pêche, mouettes et douce lumière pastel.", + "Get a quick answer": "Obtenir une réponse rapide", + "Fast replies for small questions.": "Des réponses rapides aux petites questions.", + "Convert 180 °C to Fahrenheit, and tell me which gas mark that is.": "Convertis 180 °C en Fahrenheit et dis-moi à quel thermostat cela correspond.", + "Engineering": "Ingénierie", + "Research & Analysis": "Recherche et analyse", + "Vision": "Vision", + "Generative Art": "Art génératif", + "Quick Answers": "Réponses rapides", + "Asset Scout": "Éclaireur de ressources", + "Worlds & Cartography": "Mondes et cartographie", + "Marine Biology": "Biologie marine", + "Studio Architect": "Architecte du Studio" } diff --git a/ui/src/mobile/MobileApp.tsx b/ui/src/mobile/MobileApp.tsx index aaa3680..6735a41 100644 --- a/ui/src/mobile/MobileApp.tsx +++ b/ui/src/mobile/MobileApp.tsx @@ -58,6 +58,7 @@ import { CircleShareHost } from '../components/CircleCards'; import { MobileAppsSheet } from './MobileAppsSheet'; import { MobileArtifactSheet } from './MobileArtifactSheet'; import { DocPanel } from '../components/DocPanel'; +import { NewChatHome } from '../components/NewChatHome'; import { onDocView, type DocTarget } from '../docView'; import { MobileAgentPicker } from './MobileAgentPicker'; import { MobileRadioBar } from './MobileRadioBar'; @@ -504,6 +505,21 @@ export function MobileApp() { [conv, setConv] ); + // a "Try something" card on the empty chat: the chat switches to the card's agent, and the prompt goes once it has + const [pendingStart, setPendingStart] = useState<{ agentId: string; prompt: string } | null>(null); + const startWith = useCallback( + (a: Agent, prompt: string) => { + selectAgent(a); + setPendingStart({ agentId: a.id, prompt }); + }, + [selectAgent] + ); + useEffect(() => { + if (!pendingStart || agent?.id !== pendingStart.agentId) return; + setPendingStart(null); + send(pendingStart.prompt, []); + }, [pendingStart, agent, send]); + const moveToGroup = useCallback( async (convId: string, gid: string | null) => { await api.patchConversation(convId, { groupId: gid }); @@ -908,38 +924,16 @@ export function MobileApp() { }} > {messages.length === 0 ? ( -
- -

- {greeting()}. -

-

- {agent.name} ·{' '} - {agent.description} -

- - {/* Suggestion Chips */} -
- {quick.map(q => ( - - ))} -
- -

- {t('Overthink. Overcomplicate. Overengineer.')} -

-
+ send(prompt, [])} + onStart={startWith} + onSelectAgent={selectAgent} + onOpenApp={openApp} + /> ) : (
{foldSummarized(messages).map(m => ( diff --git a/ui/src/themes/synthfield.css b/ui/src/themes/synthfield.css index 8159218..5aaa08c 100644 --- a/ui/src/themes/synthfield.css +++ b/ui/src/themes/synthfield.css @@ -244,7 +244,7 @@ html[data-skin='synthfield'] .sf-card { transition: border-color .15s, transform .15s, background-color .15s; } html[data-skin='synthfield'] .sf-card::before { - content: '0' counter(sf-mission); position: absolute; top: 14px; right: 16px; + content: counter(sf-mission, decimal-leading-zero); position: absolute; top: 14px; right: 16px; font-family: 'JetBrains Mono Variable', monospace; font-size: 10px; letter-spacing: 0.1em; color: var(--text-muted); } html[data-skin='synthfield'] .sf-card::after { From b6a299d0c75810d7c6b362846e4426cfdd68ab72 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 02:14:40 +0000 Subject: [PATCH 2/2] New chat page: same for everyone, no Hide The section always shows and opens on Try something; nothing is remembered per browser, so every person sees the same page. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01G13cXQwUfmo3PoJsiW8g8j --- docs/changelog.json | 2 +- ui/src/components/NewChatHome.tsx | 160 +++++++++++++----------------- ui/src/i18n/fr.json | 2 - 3 files changed, 72 insertions(+), 92 deletions(-) diff --git a/docs/changelog.json b/docs/changelog.json index ee99e90..45ece30 100644 --- a/docs/changelog.json +++ b/docs/changelog.json @@ -3,7 +3,7 @@ "date": "2026-10-13", "area": "Chat", "title": "The new chat page shows what Shell:B can do", - "summary": "Below the agent's starter cards, a new chat now has a What Shell:B can do section with three tabs. Try something offers everyday tasks (prepare for a job interview, polish a cover letter, identify a sea creature, step back into the Neolithic, get notes on your writing, research a topic, summarise a document, make a picture, get a quick answer); each one switches the chat to the agent best at it and sends. Agents lists every agent with what it is for: picking one sets it for the chat and its starter cards change to match. Apps lists every app with a line on what it does, and opens it. Hide tucks the section away behind a single link, remembered per browser, for anyone who already knows their way around." + "summary": "Below the agent's starter cards, a new chat now has a What Shell:B can do section with three tabs. Try something offers everyday tasks (prepare for a job interview, polish a cover letter, identify a sea creature, step back into the Neolithic, get notes on your writing, research a topic, summarise a document, make a picture, get a quick answer); each one switches the chat to the agent best at it and sends. Agents lists every agent with what it is for: picking one sets it for the chat and its starter cards change to match. Apps lists every app with a line on what it does, and opens it." }, { "date": "2026-10-13", diff --git a/ui/src/components/NewChatHome.tsx b/ui/src/components/NewChatHome.tsx index cb412a2..8880a93 100644 --- a/ui/src/components/NewChatHome.tsx +++ b/ui/src/components/NewChatHome.tsx @@ -1,6 +1,5 @@ import { useRef, useState } from 'react'; import clsx from 'clsx'; -import { ChevronDown, ChevronUp } from 'lucide-react'; import { t } from '../i18n'; import type { Agent } from '../types'; import type { Suggestion } from '../suggestions'; @@ -10,7 +9,7 @@ import { AgentAvatar } from './common'; /** * The empty chat: the agent's own starter cards, then "What Shell:B can do" for people who don't know yet. Three tabs: * Try something (everyday tasks, each answered by the agent best at it), Agents (who is who) and Apps (everything on the - * rail and in All apps). People who know their way around hide it once; that and the tab are remembered per browser. + * rail and in All apps). Everyone sees the same page. */ interface Starter { title: string; blurb: string; prompt: string; agent: string } @@ -45,10 +44,6 @@ const NOT_SHOWCASED = new Set(['aphrodite']); const APP_GROUPS: AppGroup[] = ['make', 'know', 'run']; type Tab = 'try' | 'agents' | 'apps'; -const KEY = 'shellb.home'; -interface Prefs { hidden?: boolean; tab?: Tab } -const readPrefs = (): Prefs => { try { return JSON.parse(localStorage.getItem(KEY) || '{}') as Prefs; } catch { return {}; } }; -const savePrefs = (p: Prefs) => { try { localStorage.setItem(KEY, JSON.stringify(p)); } catch { /* private mode */ } }; const card = 'sf-card rounded-xl border border-[var(--border-subtle)] bg-[var(--bg-secondary)] hover:bg-[var(--bg-hover)] hover:border-[var(--border-strong)] transition group text-left'; @@ -64,9 +59,7 @@ export function NewChatHome({ agent, agents, quick, greeting, onSend, onStart, o onSelectAgent: (agent: Agent) => void; onOpenApp: (app: AppId) => void; }) { - const [prefs, setPrefs] = useState(readPrefs); - const tab = prefs.tab ?? 'try'; - const update = (p: Prefs) => { const next = { ...prefs, ...p }; setPrefs(next); savePrefs(next); }; + const [tab, setTab] = useState('try'); const top = useRef(null); const showcased = agents.filter(a => !a.unavailable && !NOT_SHOWCASED.has(a.id)); @@ -110,89 +103,78 @@ export function NewChatHome({ agent, agents, quick, greeting, onSend, onStart, o ))}
- {prefs.hidden ? ( - - ) : ( -
-
-

{t('What Shell:B can do')}

-
- {tabs.map(x => ( - - ))} -
- +
+
+

{t('What Shell:B can do')}

+
+ {tabs.map(x => ( + + ))}
- - {tab === 'try' && ( -
- {starters.map(s => ( - - ))} -
- )} - - {tab === 'agents' && ( -
-

- {t('Each agent is good at something different. Pick one for this chat, or type @ and a name to ask one for a single reply.')} -

-
{local.map(agentCard)}
- {cloud.length > 0 && (<> -

{t('Cloud')}

-
{cloud.map(agentCard)}
- )} -
+ + {tab === 'try' && ( +
+ {starters.map(s => ( + -
- )} - - {tab === 'apps' && ( -
- {APP_GROUPS.map(g => { - const apps = APPS.filter(a => a.group === g); - if (!apps.length) return null; - return ( -
-

{t(GROUP_LABEL[g])}

-
- {apps.map(a => { - const Icon = a.icon; - return ( - - ); - })} -
+ ))} +
+ )} + + {tab === 'agents' && ( +
+

+ {t('Each agent is good at something different. Pick one for this chat, or type @ and a name to ask one for a single reply.')} +

+
{local.map(agentCard)}
+ {cloud.length > 0 && (<> +

{t('Cloud')}

+
{cloud.map(agentCard)}
+ )} + +
+ )} + + {tab === 'apps' && ( +
+ {APP_GROUPS.map(g => { + const apps = APPS.filter(a => a.group === g); + if (!apps.length) return null; + return ( +
+

{t(GROUP_LABEL[g])}

+
+ {apps.map(a => { + const Icon = a.icon; + return ( + + ); + })}
- ); - })} -
- )} -
- )} +
+ ); + })} +
+ )} +

{t('Overthink. Overcomplicate. Overengineer.')}

diff --git a/ui/src/i18n/fr.json b/ui/src/i18n/fr.json index d17f2b9..7f1886d 100644 --- a/ui/src/i18n/fr.json +++ b/ui/src/i18n/fr.json @@ -4038,8 +4038,6 @@ "Every string passes the checks": "Toutes les chaînes passent les vérifications", "Try something": "Essayer", "What Shell:B can do": "Ce que Shell:B sait faire", - "Show what Shell:B can do": "Voir ce que Shell:B sait faire", - "Hide this until you show it again": "Masquer jusqu’à ce que vous l’affichiez de nouveau", "Chat with {name}": "Discuter avec {name}", "Each agent is good at something different. Pick one for this chat, or type @ and a name to ask one for a single reply.": "Chaque agent a son domaine. Choisissez-en un pour cette discussion, ou tapez @ suivi d’un nom pour lui demander une seule réponse.", "Create or tune agents in the Agent Lab": "Créer ou ajuster des agents dans le Labo des agents",