getkova 2026.4.21 → 2026.4.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/control-ui/apple-touch-icon.png +0 -0
  2. package/dist/control-ui/assets/agents-CLtFQRXd.js +918 -0
  3. package/dist/control-ui/assets/agents-CLtFQRXd.js.map +1 -0
  4. package/dist/control-ui/assets/anthropic-CFEPAL-v.js +37 -0
  5. package/dist/control-ui/assets/anthropic-CFEPAL-v.js.map +1 -0
  6. package/dist/control-ui/assets/azure-openai-responses-CxiWQLmZ.js +2 -0
  7. package/dist/control-ui/assets/azure-openai-responses-CxiWQLmZ.js.map +1 -0
  8. package/dist/control-ui/assets/briefing-DS4VWpUL.js +121 -0
  9. package/dist/control-ui/assets/briefing-DS4VWpUL.js.map +1 -0
  10. package/dist/control-ui/assets/canvas-BfC_2Nqy.js +17 -0
  11. package/dist/control-ui/assets/canvas-BfC_2Nqy.js.map +1 -0
  12. package/dist/control-ui/assets/channel-config-extras-YNNd-4PG.js +2 -0
  13. package/dist/control-ui/assets/channel-config-extras-YNNd-4PG.js.map +1 -0
  14. package/dist/control-ui/assets/channels-BKdM7i5r.js +471 -0
  15. package/dist/control-ui/assets/channels-BKdM7i5r.js.map +1 -0
  16. package/dist/control-ui/assets/cron-C11m3yJi.js +928 -0
  17. package/dist/control-ui/assets/cron-C11m3yJi.js.map +1 -0
  18. package/dist/control-ui/assets/de-rLAkQOBc.js +2 -0
  19. package/dist/control-ui/assets/de-rLAkQOBc.js.map +1 -0
  20. package/dist/control-ui/assets/debug-DFf2qCcM.js +94 -0
  21. package/dist/control-ui/assets/debug-DFf2qCcM.js.map +1 -0
  22. package/dist/control-ui/assets/dist-D8DZLmCF.js +18 -0
  23. package/dist/control-ui/assets/dist-D8DZLmCF.js.map +1 -0
  24. package/dist/control-ui/assets/employees-DV-5FV4K.js +104 -0
  25. package/dist/control-ui/assets/employees-DV-5FV4K.js.map +1 -0
  26. package/dist/control-ui/assets/es-CIeD3O54.js +2 -0
  27. package/dist/control-ui/assets/es-CIeD3O54.js.map +1 -0
  28. package/dist/control-ui/assets/event-stream-B8X6sYaV.js +2 -0
  29. package/dist/control-ui/assets/event-stream-B8X6sYaV.js.map +1 -0
  30. package/dist/control-ui/assets/format-BahKhiOC.js +2 -0
  31. package/dist/control-ui/assets/format-BahKhiOC.js.map +1 -0
  32. package/dist/control-ui/assets/github-copilot-headers-CrI0CIJ7.js +2 -0
  33. package/dist/control-ui/assets/github-copilot-headers-CrI0CIJ7.js.map +1 -0
  34. package/dist/control-ui/assets/google-BT0bmsh5.js +2 -0
  35. package/dist/control-ui/assets/google-BT0bmsh5.js.map +1 -0
  36. package/dist/control-ui/assets/google-gemini-cli-BpxbH95Q.js +3 -0
  37. package/dist/control-ui/assets/google-gemini-cli-BpxbH95Q.js.map +1 -0
  38. package/dist/control-ui/assets/google-shared-CbPHVnPr.js +12 -0
  39. package/dist/control-ui/assets/google-shared-CbPHVnPr.js.map +1 -0
  40. package/dist/control-ui/assets/google-vertex-lQwbjEII.js +2 -0
  41. package/dist/control-ui/assets/google-vertex-lQwbjEII.js.map +1 -0
  42. package/dist/control-ui/assets/hash-Bt1aVMQ3.js +2 -0
  43. package/dist/control-ui/assets/hash-Bt1aVMQ3.js.map +1 -0
  44. package/dist/control-ui/assets/inbox-C4tOnlJr.js +100 -0
  45. package/dist/control-ui/assets/inbox-C4tOnlJr.js.map +1 -0
  46. package/dist/control-ui/assets/index-DYMuTfvX.css +1 -0
  47. package/dist/control-ui/assets/index-XGDpaFxG.js +5482 -0
  48. package/dist/control-ui/assets/index-XGDpaFxG.js.map +1 -0
  49. package/dist/control-ui/assets/instances-Cyr-tbN6.js +57 -0
  50. package/dist/control-ui/assets/instances-Cyr-tbN6.js.map +1 -0
  51. package/dist/control-ui/assets/kova-logo.png +0 -0
  52. package/dist/control-ui/assets/lit-zdTgzAJI.js +3 -0
  53. package/dist/control-ui/assets/lit-zdTgzAJI.js.map +1 -0
  54. package/dist/control-ui/assets/local-storage-D3baoRWx.js +2 -0
  55. package/dist/control-ui/assets/local-storage-D3baoRWx.js.map +1 -0
  56. package/dist/control-ui/assets/logs-B7--7dYP.js +74 -0
  57. package/dist/control-ui/assets/logs-B7--7dYP.js.map +1 -0
  58. package/dist/control-ui/assets/meetings-DSqn6s7n.js +185 -0
  59. package/dist/control-ui/assets/meetings-DSqn6s7n.js.map +1 -0
  60. package/dist/control-ui/assets/mistral-CBrDC_Gv.js +8 -0
  61. package/dist/control-ui/assets/mistral-CBrDC_Gv.js.map +1 -0
  62. package/dist/control-ui/assets/nodes-Cvq_sAqT.js +430 -0
  63. package/dist/control-ui/assets/nodes-Cvq_sAqT.js.map +1 -0
  64. package/dist/control-ui/assets/openai-Cn7eGqwa.js +17 -0
  65. package/dist/control-ui/assets/openai-Cn7eGqwa.js.map +1 -0
  66. package/dist/control-ui/assets/openai-codex-responses-DuhESMYF.js +8 -0
  67. package/dist/control-ui/assets/openai-codex-responses-DuhESMYF.js.map +1 -0
  68. package/dist/control-ui/assets/openai-completions-Bv33lqKL.js +6 -0
  69. package/dist/control-ui/assets/openai-completions-Bv33lqKL.js.map +1 -0
  70. package/dist/control-ui/assets/openai-responses-BPxpapOg.js +2 -0
  71. package/dist/control-ui/assets/openai-responses-BPxpapOg.js.map +1 -0
  72. package/dist/control-ui/assets/openai-responses-shared-8nKH8ywL.js +11 -0
  73. package/dist/control-ui/assets/openai-responses-shared-8nKH8ywL.js.map +1 -0
  74. package/dist/control-ui/assets/pdf-BwYFZMZM.js +57 -0
  75. package/dist/control-ui/assets/pdf-BwYFZMZM.js.map +1 -0
  76. package/dist/control-ui/assets/pdf.worker.min-BmpgcBpm.js +2 -0
  77. package/dist/control-ui/assets/pdf.worker.min-BmpgcBpm.js.map +1 -0
  78. package/dist/control-ui/assets/pdf.worker.min-C8PGFc0r.mjs +28 -0
  79. package/dist/control-ui/assets/preload-helper-Chd9yIcd.js +1 -0
  80. package/dist/control-ui/assets/pt-BR-lSsBb08k.js +2 -0
  81. package/dist/control-ui/assets/pt-BR-lSsBb08k.js.map +1 -0
  82. package/dist/control-ui/assets/routing-DizI_FiJ.js +157 -0
  83. package/dist/control-ui/assets/routing-DizI_FiJ.js.map +1 -0
  84. package/dist/control-ui/assets/sessions-N9rgJP2R.js +236 -0
  85. package/dist/control-ui/assets/sessions-N9rgJP2R.js.map +1 -0
  86. package/dist/control-ui/assets/skills-D1vP4MkL.js +280 -0
  87. package/dist/control-ui/assets/skills-D1vP4MkL.js.map +1 -0
  88. package/dist/control-ui/assets/skills-shared-Bg0Qcnkp.js +11 -0
  89. package/dist/control-ui/assets/skills-shared-Bg0Qcnkp.js.map +1 -0
  90. package/dist/control-ui/assets/transform-messages-XKqwKV3D.js +2 -0
  91. package/dist/control-ui/assets/transform-messages-XKqwKV3D.js.map +1 -0
  92. package/dist/control-ui/assets/zh-CN-C5tPG8Eu.js +2 -0
  93. package/dist/control-ui/assets/zh-CN-C5tPG8Eu.js.map +1 -0
  94. package/dist/control-ui/assets/zh-TW-CPSoC7Wz.js +2 -0
  95. package/dist/control-ui/assets/zh-TW-CPSoC7Wz.js.map +1 -0
  96. package/dist/control-ui/favicon-32.png +0 -0
  97. package/dist/control-ui/favicon.ico +0 -0
  98. package/dist/control-ui/favicon.png +0 -0
  99. package/dist/control-ui/favicon.svg +22 -0
  100. package/dist/control-ui/index.html +73 -0
  101. package/dist/control-ui/openclaw-canvas-auth-sw.js +57 -0
  102. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ var e=`modulepreload`,t=function(e,t){return new URL(e,t).href},n={},r=function(r,i,a){let o=Promise.resolve();if(i&&i.length>0){let r=document.getElementsByTagName(`link`),s=document.querySelector(`meta[property=csp-nonce]`),c=s?.nonce||s?.getAttribute(`nonce`);function l(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}o=l(i.map(i=>{if(i=t(i,a),i in n)return;n[i]=!0;let o=i.endsWith(`.css`),s=o?`[rel="stylesheet"]`:``;if(a)for(let e=r.length-1;e>=0;e--){let t=r[e];if(t.href===i&&(!o||t.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${i}"]${s}`))return;let l=document.createElement(`link`);if(l.rel=o?`stylesheet`:e,o||(l.as=`script`),l.crossOrigin=``,l.href=i,c&&l.setAttribute(`nonce`,c),document.head.appendChild(l),o)return new Promise((e,t)=>{l.addEventListener(`load`,e),l.addEventListener(`error`,()=>t(Error(`Unable to preload CSS for ${i}`)))})}))}function s(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return o.then(e=>{for(let t of e||[])t.status===`rejected`&&s(t.reason);return r().catch(s)})};export{r as t};
@@ -0,0 +1,2 @@
1
+ var e={common:{health:`Saúde`,ok:`OK`,online:`Online`,offline:`Offline`,connect:`Conectar`,refresh:`Atualizar`,enabled:`Ativado`,disabled:`Desativado`,na:`n/a`,version:`Versão`,docs:`Docs`,resources:`Recursos`,search:`Pesquisar`},nav:{chat:`Chat`,control:`Controle`,agent:`Agente`,settings:`Configurações`,expand:`Expandir barra lateral`,collapse:`Recolher barra lateral`,resize:`Redimensionar barra lateral`},tabs:{employees:`Equipe`,agents:`Agentes`,overview:`Visão Geral`,channels:`Canais`,instances:`Instâncias`,sessions:`Sessões`,usage:`Uso`,cron:`Tarefas Cron`,skills:`Habilidades`,nodes:`Nós`,chat:`Chat`,config:`Config`,apiKeys:`API Keys`,communications:`Comunicações`,appearance:`Aparência e Configuração`,automation:`Automação`,infrastructure:`Infraestrutura`,aiAgents:`IA e Agentes`,debug:`Debug`,logs:`Logs`},subtitles:{employees:`Equipe de IA e atividade recente.`,agents:`Espaços, ferramentas, identidades.`,overview:`Status, entrada, saúde.`,channels:`Canais e configurações.`,instances:`Clientes e nós conectados.`,sessions:`Sessões ativas e padrões.`,usage:`Uso e custos da API.`,cron:`Despertares e execuções.`,skills:`Habilidades e chaves API.`,nodes:`Dispositivos e comandos.`,chat:`Chat do gateway para intervenções rápidas.`,config:`Editar openclaw.json.`,apiKeys:`OpenRouter key and model defaults.`,communications:`Configurações de canais, mensagens e áudio.`,appearance:`Configurações de tema, UI e assistente de configuração.`,automation:`Configurações de comandos, hooks, cron e plugins.`,infrastructure:`Configurações de gateway, web, browser e mídia.`,aiAgents:`Configurações de agentes, modelos, habilidades, ferramentas, memória e sessão.`,debug:`Snapshots, eventos, RPC.`,logs:`Logs ao vivo do gateway.`},overview:{access:{title:`Acesso ao Gateway`,subtitle:`Onde o dashboard se conecta e como ele se autentica.`,wsUrl:`URL WebSocket`,token:`Token do Gateway`,password:`Senha (não armazenada)`,sessionKey:`Chave de Sessão Padrão`,language:`Idioma`,connectHint:`Clique em Conectar para aplicar as alterações de conexão.`,trustedProxy:`Autenticado por proxy confiável.`},snapshot:{title:`Snapshot`,subtitle:`Informações mais recentes do handshake do gateway.`,status:`Status`,uptime:`Tempo de Atividade`,tickInterval:`Intervalo de Tick`,lastChannelsRefresh:`Última Atualização de Canais`,channelsHint:`Use Canais para vincular WhatsApp, Telegram, Discord, Signal ou iMessage.`},stats:{instances:`Instâncias`,instancesHint:`Beacons de presença nos últimos 5 minutos.`,sessions:`Sessões`,sessionsHint:`Chaves de sessão recentes rastreadas pelo gateway.`,cron:`Cron`,cronNext:`Próximo despertar {time}`},notes:{title:`Notas`,subtitle:`Lembretes rápidos para configurações de controle remoto.`,tailscaleTitle:`Tailscale serve`,tailscaleText:`Prefira o modo serve para manter o gateway em loopback com autenticação tailnet.`,sessionTitle:`Higiene de sessão`,sessionText:`Use /new ou sessions.patch para redefinir o contexto.`,cronTitle:`Lembretes de Cron`,cronText:`Use sessões isoladas para execuções recorrentes.`},auth:{required:`Este gateway requer autenticação. Adicione um token ou senha e clique em Conectar.`,failed:`Falha na autenticação. Recopie uma URL com token usando {command}, ou atualize o token e clique em Conectar.`},pairing:{hint:`Este dispositivo precisa de aprovação de pareamento do host do gateway.`,mobileHint:`No celular? Copie a URL completa (incluindo #token=...) executando kova dashboard --no-open no desktop.`},insecure:{hint:`Esta página é HTTP, então o navegador bloqueia a identidade do dispositivo. Use HTTPS (Tailscale Serve) ou abra {url} no host do gateway.`,stayHttp:`Se você precisar permanecer em HTTP, defina {config} (apenas token).`},connection:{title:`Como conectar`,step1:`Inicie o gateway na sua máquina host:`,step2:`Obtenha uma URL do painel com token:`,step3:`Cole a URL do WebSocket e o token acima, ou abra a URL com token diretamente.`,step4:`Ou gere um token reutilizável:`,docsHint:`Para acesso remoto, recomendamos o Tailscale Serve. `,docsLink:`Leia a documentação →`},cards:{cost:`Custo`,skills:`Habilidades`,recentSessions:`Sessões Recentes`},attention:{title:`Atenção`},eventLog:{title:`Log de Eventos`},logTail:{title:`Logs do Gateway`},quickActions:{newSession:`Nova Sessão`,automation:`Automação`,refreshAll:`Atualizar Tudo`,terminal:`Terminal`},palette:{placeholder:`Digite um comando…`,noResults:`Sem resultados`}},login:{subtitle:`Painel do Gateway`,passwordPlaceholder:`opcional`},chat:{disconnected:`Desconectado do gateway.`,refreshTitle:`Atualizar dados do chat`,thinkingToggle:`Alternar saída de pensamento/trabalho do assistente`,focusToggle:`Alternar modo de foco (ocultar barra lateral + cabeçalho da página)`,hideCronSessions:`Ocultar sessões de cron`,showCronSessions:`Mostrar sessões de cron`,showCronSessionsHidden:`Mostrar sessões de cron ({count} ocultas)`,onboardingDisabled:`Desativado durante a integração`},languages:{en:`English`,zhCN:`简体中文 (Chinês Simplificado)`,zhTW:`繁體中文 (Chinês Tradicional)`,ptBR:`Português (Português Brasileiro)`,de:`Deutsch (Alemão)`,es:`Español (Espanhol)`}};export{e as pt_BR};
2
+ //# sourceMappingURL=pt-BR-lSsBb08k.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pt-BR-lSsBb08k.js","names":[],"sources":["../../../ui/src/i18n/locales/pt-BR.ts"],"sourcesContent":["import type { TranslationMap } from \"../lib/types.ts\";\n\nexport const pt_BR: TranslationMap = {\n common: {\n health: \"Saúde\",\n ok: \"OK\",\n online: \"Online\",\n offline: \"Offline\",\n connect: \"Conectar\",\n refresh: \"Atualizar\",\n enabled: \"Ativado\",\n disabled: \"Desativado\",\n na: \"n/a\",\n version: \"Versão\",\n docs: \"Docs\",\n resources: \"Recursos\",\n search: \"Pesquisar\",\n },\n nav: {\n chat: \"Chat\",\n control: \"Controle\",\n agent: \"Agente\",\n settings: \"Configurações\",\n expand: \"Expandir barra lateral\",\n collapse: \"Recolher barra lateral\",\n resize: \"Redimensionar barra lateral\",\n },\n tabs: {\n employees: \"Equipe\",\n agents: \"Agentes\",\n overview: \"Visão Geral\",\n channels: \"Canais\",\n instances: \"Instâncias\",\n sessions: \"Sessões\",\n usage: \"Uso\",\n cron: \"Tarefas Cron\",\n skills: \"Habilidades\",\n nodes: \"Nós\",\n chat: \"Chat\",\n config: \"Config\",\n apiKeys: \"API Keys\",\n communications: \"Comunicações\",\n appearance: \"Aparência e Configuração\",\n automation: \"Automação\",\n infrastructure: \"Infraestrutura\",\n aiAgents: \"IA e Agentes\",\n debug: \"Debug\",\n logs: \"Logs\",\n },\n subtitles: {\n employees: \"Equipe de IA e atividade recente.\",\n agents: \"Espaços, ferramentas, identidades.\",\n overview: \"Status, entrada, saúde.\",\n channels: \"Canais e configurações.\",\n instances: \"Clientes e nós conectados.\",\n sessions: \"Sessões ativas e padrões.\",\n usage: \"Uso e custos da API.\",\n cron: \"Despertares e execuções.\",\n skills: \"Habilidades e chaves API.\",\n nodes: \"Dispositivos e comandos.\",\n chat: \"Chat do gateway para intervenções rápidas.\",\n config: \"Editar openclaw.json.\",\n apiKeys: \"OpenRouter key and model defaults.\",\n communications: \"Configurações de canais, mensagens e áudio.\",\n appearance: \"Configurações de tema, UI e assistente de configuração.\",\n automation: \"Configurações de comandos, hooks, cron e plugins.\",\n infrastructure: \"Configurações de gateway, web, browser e mídia.\",\n aiAgents: \"Configurações de agentes, modelos, habilidades, ferramentas, memória e sessão.\",\n debug: \"Snapshots, eventos, RPC.\",\n logs: \"Logs ao vivo do gateway.\",\n },\n overview: {\n access: {\n title: \"Acesso ao Gateway\",\n subtitle: \"Onde o dashboard se conecta e como ele se autentica.\",\n wsUrl: \"URL WebSocket\",\n token: \"Token do Gateway\",\n password: \"Senha (não armazenada)\",\n sessionKey: \"Chave de Sessão Padrão\",\n language: \"Idioma\",\n connectHint: \"Clique em Conectar para aplicar as alterações de conexão.\",\n trustedProxy: \"Autenticado por proxy confiável.\",\n },\n snapshot: {\n title: \"Snapshot\",\n subtitle: \"Informações mais recentes do handshake do gateway.\",\n status: \"Status\",\n uptime: \"Tempo de Atividade\",\n tickInterval: \"Intervalo de Tick\",\n lastChannelsRefresh: \"Última Atualização de Canais\",\n channelsHint: \"Use Canais para vincular WhatsApp, Telegram, Discord, Signal ou iMessage.\",\n },\n stats: {\n instances: \"Instâncias\",\n instancesHint: \"Beacons de presença nos últimos 5 minutos.\",\n sessions: \"Sessões\",\n sessionsHint: \"Chaves de sessão recentes rastreadas pelo gateway.\",\n cron: \"Cron\",\n cronNext: \"Próximo despertar {time}\",\n },\n notes: {\n title: \"Notas\",\n subtitle: \"Lembretes rápidos para configurações de controle remoto.\",\n tailscaleTitle: \"Tailscale serve\",\n tailscaleText:\n \"Prefira o modo serve para manter o gateway em loopback com autenticação tailnet.\",\n sessionTitle: \"Higiene de sessão\",\n sessionText: \"Use /new ou sessions.patch para redefinir o contexto.\",\n cronTitle: \"Lembretes de Cron\",\n cronText: \"Use sessões isoladas para execuções recorrentes.\",\n },\n auth: {\n required:\n \"Este gateway requer autenticação. Adicione um token ou senha e clique em Conectar.\",\n failed:\n \"Falha na autenticação. Recopie uma URL com token usando {command}, ou atualize o token e clique em Conectar.\",\n },\n pairing: {\n hint: \"Este dispositivo precisa de aprovação de pareamento do host do gateway.\",\n mobileHint:\n \"No celular? Copie a URL completa (incluindo #token=...) executando kova dashboard --no-open no desktop.\",\n },\n insecure: {\n hint: \"Esta página é HTTP, então o navegador bloqueia a identidade do dispositivo. Use HTTPS (Tailscale Serve) ou abra {url} no host do gateway.\",\n stayHttp: \"Se você precisar permanecer em HTTP, defina {config} (apenas token).\",\n },\n connection: {\n title: \"Como conectar\",\n step1: \"Inicie o gateway na sua máquina host:\",\n step2: \"Obtenha uma URL do painel com token:\",\n step3: \"Cole a URL do WebSocket e o token acima, ou abra a URL com token diretamente.\",\n step4: \"Ou gere um token reutilizável:\",\n docsHint: \"Para acesso remoto, recomendamos o Tailscale Serve. \",\n docsLink: \"Leia a documentação →\",\n },\n cards: {\n cost: \"Custo\",\n skills: \"Habilidades\",\n recentSessions: \"Sessões Recentes\",\n },\n attention: {\n title: \"Atenção\",\n },\n eventLog: {\n title: \"Log de Eventos\",\n },\n logTail: {\n title: \"Logs do Gateway\",\n },\n quickActions: {\n newSession: \"Nova Sessão\",\n automation: \"Automação\",\n refreshAll: \"Atualizar Tudo\",\n terminal: \"Terminal\",\n },\n palette: {\n placeholder: \"Digite um comando…\",\n noResults: \"Sem resultados\",\n },\n },\n login: {\n subtitle: \"Painel do Gateway\",\n passwordPlaceholder: \"opcional\",\n },\n chat: {\n disconnected: \"Desconectado do gateway.\",\n refreshTitle: \"Atualizar dados do chat\",\n thinkingToggle: \"Alternar saída de pensamento/trabalho do assistente\",\n focusToggle: \"Alternar modo de foco (ocultar barra lateral + cabeçalho da página)\",\n hideCronSessions: \"Ocultar sessões de cron\",\n showCronSessions: \"Mostrar sessões de cron\",\n showCronSessionsHidden: \"Mostrar sessões de cron ({count} ocultas)\",\n onboardingDisabled: \"Desativado durante a integração\",\n },\n languages: {\n en: \"English\",\n zhCN: \"简体中文 (Chinês Simplificado)\",\n zhTW: \"繁體中文 (Chinês Tradicional)\",\n ptBR: \"Português (Português Brasileiro)\",\n de: \"Deutsch (Alemão)\",\n es: \"Español (Espanhol)\",\n },\n};\n"],"mappings":"AAEA,IAAa,EAAwB,CACnC,OAAQ,CACN,OAAQ,QACR,GAAI,KACJ,OAAQ,SACR,QAAS,UACT,QAAS,WACT,QAAS,YACT,QAAS,UACT,SAAU,aACV,GAAI,MACJ,QAAS,SACT,KAAM,OACN,UAAW,WACX,OAAQ,YACT,CACD,IAAK,CACH,KAAM,OACN,QAAS,WACT,MAAO,SACP,SAAU,gBACV,OAAQ,yBACR,SAAU,yBACV,OAAQ,8BACT,CACD,KAAM,CACJ,UAAW,SACX,OAAQ,UACR,SAAU,cACV,SAAU,SACV,UAAW,aACX,SAAU,UACV,MAAO,MACP,KAAM,eACN,OAAQ,cACR,MAAO,MACP,KAAM,OACN,OAAQ,SACR,QAAS,WACT,eAAgB,eAChB,WAAY,2BACZ,WAAY,YACZ,eAAgB,iBAChB,SAAU,eACV,MAAO,QACP,KAAM,OACP,CACD,UAAW,CACT,UAAW,oCACX,OAAQ,qCACR,SAAU,0BACV,SAAU,0BACV,UAAW,6BACX,SAAU,4BACV,MAAO,uBACP,KAAM,2BACN,OAAQ,4BACR,MAAO,2BACP,KAAM,6CACN,OAAQ,wBACR,QAAS,qCACT,eAAgB,8CAChB,WAAY,0DACZ,WAAY,oDACZ,eAAgB,kDAChB,SAAU,iFACV,MAAO,2BACP,KAAM,2BACP,CACD,SAAU,CACR,OAAQ,CACN,MAAO,oBACP,SAAU,uDACV,MAAO,gBACP,MAAO,mBACP,SAAU,yBACV,WAAY,yBACZ,SAAU,SACV,YAAa,4DACb,aAAc,mCACf,CACD,SAAU,CACR,MAAO,WACP,SAAU,qDACV,OAAQ,SACR,OAAQ,qBACR,aAAc,oBACd,oBAAqB,+BACrB,aAAc,4EACf,CACD,MAAO,CACL,UAAW,aACX,cAAe,6CACf,SAAU,UACV,aAAc,qDACd,KAAM,OACN,SAAU,2BACX,CACD,MAAO,CACL,MAAO,QACP,SAAU,2DACV,eAAgB,kBAChB,cACE,mFACF,aAAc,oBACd,YAAa,wDACb,UAAW,oBACX,SAAU,mDACX,CACD,KAAM,CACJ,SACE,qFACF,OACE,+GACH,CACD,QAAS,CACP,KAAM,0EACN,WACE,0GACH,CACD,SAAU,CACR,KAAM,4IACN,SAAU,uEACX,CACD,WAAY,CACV,MAAO,gBACP,MAAO,wCACP,MAAO,uCACP,MAAO,gFACP,MAAO,iCACP,SAAU,uDACV,SAAU,wBACX,CACD,MAAO,CACL,KAAM,QACN,OAAQ,cACR,eAAgB,mBACjB,CACD,UAAW,CACT,MAAO,UACR,CACD,SAAU,CACR,MAAO,iBACR,CACD,QAAS,CACP,MAAO,kBACR,CACD,aAAc,CACZ,WAAY,cACZ,WAAY,YACZ,WAAY,iBACZ,SAAU,WACX,CACD,QAAS,CACP,YAAa,qBACb,UAAW,iBACZ,CACF,CACD,MAAO,CACL,SAAU,oBACV,oBAAqB,WACtB,CACD,KAAM,CACJ,aAAc,2BACd,aAAc,0BACd,eAAgB,sDAChB,YAAa,sEACb,iBAAkB,0BAClB,iBAAkB,0BAClB,uBAAwB,4CACxB,mBAAoB,kCACrB,CACD,UAAW,CACT,GAAI,UACJ,KAAM,6BACN,KAAM,4BACN,KAAM,mCACN,GAAI,mBACJ,GAAI,qBACL,CACF"}
@@ -0,0 +1,157 @@
1
+ import{i as e,n as t}from"./lit-zdTgzAJI.js";import{I as n,d as r,f as i}from"./index-XGDpaFxG.js";function a(n){return n?e`<div class="callout ${n.kind===`error`?`danger`:`success`}">${n.text}</div>`:t}function o(e){return e===`telegram`?n.send:n.messageSquare}function s(e,t){return t===`telegram`?e.telegramConnected:e.whatsappConnected}function c(e,t){return e===`main`?`Main agent (default)`:t.get(e)?.name??e}function l(e,t){return e===`main`?`Current Kova default`:t.get(e)?.role??`Assigned employee`}function u(u){let d=new Map(u.employees.map(e=>[e.id,e]));return e`
2
+ <section class="page page--settings" style="display: grid; gap: 20px;">
3
+ <div class="callout" style="display: grid; gap: 8px;">
4
+ <div class="card-title">Multi-Agent Routing</div>
5
+ <div style="line-height: 1.6;">
6
+ When someone messages you on Telegram, which employee should respond? Set that here.
7
+ </div>
8
+ </div>
9
+
10
+ <section class="card" style="display: grid; gap: 14px;">
11
+ <div class="row" style="justify-content: space-between; gap: 12px; align-items: center; flex-wrap: wrap;">
12
+ <div style="display: grid; gap: 4px;">
13
+ <div class="card-title">Quick Presets</div>
14
+ <div class="card-sub">Pick a starting point, then fine-tune each channel below.</div>
15
+ </div>
16
+ ${u.loading?e`<span class="chip">Loading current routing...</span>`:t}
17
+ </div>
18
+
19
+ <div class="row" style="gap: 10px; flex-wrap: wrap;">
20
+ <button
21
+ type="button"
22
+ class="btn"
23
+ ?disabled=${u.saving}
24
+ @click=${()=>u.onPreset(`kova-jordan`)}
25
+ >
26
+ Jordan handles everything
27
+ </button>
28
+ <button
29
+ type="button"
30
+ class="btn"
31
+ ?disabled=${u.saving}
32
+ @click=${()=>u.onPreset(`kova-alex`)}
33
+ >
34
+ Alex handles everything
35
+ </button>
36
+ <button
37
+ type="button"
38
+ class="btn"
39
+ ?disabled=${u.saving}
40
+ @click=${()=>u.onPreset(`main`)}
41
+ >
42
+ Reset to default
43
+ </button>
44
+ </div>
45
+ </section>
46
+
47
+ <section class="card" style="display: grid; gap: 16px; overflow: hidden;">
48
+ <div style="display: grid; gap: 4px;">
49
+ <div class="card-title">Channel Routing</div>
50
+ <div class="card-sub">
51
+ Telegram and WhatsApp always stay visible here. Inactive channels show as not connected until you finish setup.
52
+ </div>
53
+ </div>
54
+
55
+ <div style="overflow-x: auto;">
56
+ <table style="width: 100%; border-collapse: collapse; min-width: 980px;">
57
+ <thead>
58
+ <tr>
59
+ <th style="text-align: left; padding: 0 0 14px; min-width: 220px;">Channel</th>
60
+ <th style="text-align: left; padding: 0 0 14px; min-width: 180px;">Current owner</th>
61
+ ${u.employees.map(t=>e`
62
+ <th style="text-align: center; padding: 0 8px 14px; min-width: 120px;">
63
+ <div style="display: grid; gap: 4px; justify-items: center;">
64
+ <span>${t.name}</span>
65
+ <span class="muted" style="font-size: 12px;">
66
+ ${t.isCustom?`Custom`:t.role??`Employee`}
67
+ </span>
68
+ </div>
69
+ </th>
70
+ `)}
71
+ <th style="text-align: left; padding: 0 0 14px; min-width: 220px;">Assign to</th>
72
+ </tr>
73
+ </thead>
74
+ <tbody>
75
+ ${r.map(t=>{let r=u.assignments[t]??`main`,a=s(u,t);return e`
76
+ <tr style="border-top: 1px solid var(--border);">
77
+ <td style="padding: 16px 0; vertical-align: top;">
78
+ <div style="display: grid; gap: 8px;">
79
+ <div class="row" style="gap: 10px; align-items: center;">
80
+ <span
81
+ style="
82
+ display: inline-flex;
83
+ width: 34px;
84
+ height: 34px;
85
+ align-items: center;
86
+ justify-content: center;
87
+ border-radius: 999px;
88
+ background: var(--surface-elevated);
89
+ border: 1px solid var(--border);
90
+ "
91
+ aria-hidden="true"
92
+ >${o(t)}</span
93
+ >
94
+ <div style="display: grid; gap: 3px;">
95
+ <strong>${i(t)}</strong>
96
+ <span class="muted" style="font-size: 12px;">
97
+ ${a?`Connected and ready to route`:`Not connected`}
98
+ </span>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </td>
103
+ <td style="padding: 16px 8px; vertical-align: top;">
104
+ <div style="display: grid; gap: 4px;">
105
+ <span class="chip">${c(r,d)}</span>
106
+ <span class="muted" style="font-size: 12px;">
107
+ ${l(r,d)}
108
+ </span>
109
+ </div>
110
+ </td>
111
+ ${u.employees.map(t=>e`
112
+ <td style="padding: 16px 8px; text-align: center; vertical-align: top;">
113
+ ${r===t.id?e`<span class="chip" style="background: var(--surface-elevated);">
114
+ ${n.check}
115
+ <span>Assigned</span>
116
+ </span>`:e`<span class="muted">-</span>`}
117
+ </td>
118
+ `)}
119
+ <td style="padding: 16px 0 16px 8px; vertical-align: top;">
120
+ <label class="field" style="margin: 0;">
121
+ <select
122
+ .value=${r}
123
+ ?disabled=${u.saving}
124
+ @change=${e=>u.onAssignmentChange(t,e.target.value)}
125
+ >
126
+ <option value="main">Main agent (default)</option>
127
+ ${u.employees.map(t=>e`<option value=${t.id}>${t.name}</option>`)}
128
+ </select>
129
+ </label>
130
+ </td>
131
+ </tr>
132
+ `})}
133
+ </tbody>
134
+ </table>
135
+ </div>
136
+ </section>
137
+
138
+ <section class="card" style="display: grid; gap: 12px;">
139
+ <div class="row" style="justify-content: space-between; gap: 12px; align-items: center; flex-wrap: wrap;">
140
+ <div style="display: grid; gap: 4px;">
141
+ <div class="card-title">Save routing</div>
142
+ <div class="card-sub">Changes take effect immediately - no restart needed.</div>
143
+ </div>
144
+ <button
145
+ type="button"
146
+ class="btn primary"
147
+ ?disabled=${!u.connected||u.saving}
148
+ @click=${u.onSave}
149
+ >
150
+ ${u.saving?`Saving...`:`Save Routing`}
151
+ </button>
152
+ </div>
153
+ ${a(u.message)}
154
+ </section>
155
+ </section>
156
+ `}export{u as renderRouting};
157
+ //# sourceMappingURL=routing-DizI_FiJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-DizI_FiJ.js","names":[],"sources":["../../../ui/src/ui/views/routing.ts"],"sourcesContent":["import { html, nothing } from \"lit\";\nimport { icons } from \"../icons.ts\";\nimport {\n ROUTING_CHANNELS,\n labelForRoutingChannel,\n type RoutingAssignments,\n type RoutingChannelId,\n type RoutingEmployeeOption,\n type RoutingMessage,\n} from \"../controllers/routing.ts\";\n\nexport type RoutingProps = {\n connected: boolean;\n loading: boolean;\n saving: boolean;\n message: RoutingMessage | null;\n assignments: RoutingAssignments;\n employees: RoutingEmployeeOption[];\n telegramConnected: boolean;\n whatsappConnected: boolean;\n onAssignmentChange: (channel: RoutingChannelId, agentId: string) => void;\n onPreset: (agentId: string) => void;\n onSave: () => Promise<void> | void;\n};\n\nfunction renderMessage(message: RoutingMessage | null) {\n if (!message) {\n return nothing;\n }\n return html`<div class=\"callout ${message.kind === \"error\" ? \"danger\" : \"success\"}\">${message.text}</div>`;\n}\n\nfunction resolveChannelIcon(channel: RoutingChannelId) {\n return channel === \"telegram\" ? icons.send : icons.messageSquare;\n}\n\nfunction resolveChannelConnected(props: RoutingProps, channel: RoutingChannelId): boolean {\n return channel === \"telegram\" ? props.telegramConnected : props.whatsappConnected;\n}\n\nfunction resolveOwnerLabel(agentId: string, employees: Map<string, RoutingEmployeeOption>): string {\n if (agentId === \"main\") {\n return \"Main agent (default)\";\n }\n return employees.get(agentId)?.name ?? agentId;\n}\n\nfunction resolveOwnerSubLabel(agentId: string, employees: Map<string, RoutingEmployeeOption>): string {\n if (agentId === \"main\") {\n return \"Current Kova default\";\n }\n return employees.get(agentId)?.role ?? \"Assigned employee\";\n}\n\nexport function renderRouting(props: RoutingProps) {\n const employeesById = new Map(props.employees.map((employee) => [employee.id, employee] as const));\n\n return html`\n <section class=\"page page--settings\" style=\"display: grid; gap: 20px;\">\n <div class=\"callout\" style=\"display: grid; gap: 8px;\">\n <div class=\"card-title\">Multi-Agent Routing</div>\n <div style=\"line-height: 1.6;\">\n When someone messages you on Telegram, which employee should respond? Set that here.\n </div>\n </div>\n\n <section class=\"card\" style=\"display: grid; gap: 14px;\">\n <div class=\"row\" style=\"justify-content: space-between; gap: 12px; align-items: center; flex-wrap: wrap;\">\n <div style=\"display: grid; gap: 4px;\">\n <div class=\"card-title\">Quick Presets</div>\n <div class=\"card-sub\">Pick a starting point, then fine-tune each channel below.</div>\n </div>\n ${props.loading ? html`<span class=\"chip\">Loading current routing...</span>` : nothing}\n </div>\n\n <div class=\"row\" style=\"gap: 10px; flex-wrap: wrap;\">\n <button\n type=\"button\"\n class=\"btn\"\n ?disabled=${props.saving}\n @click=${() => props.onPreset(\"kova-jordan\")}\n >\n Jordan handles everything\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n ?disabled=${props.saving}\n @click=${() => props.onPreset(\"kova-alex\")}\n >\n Alex handles everything\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n ?disabled=${props.saving}\n @click=${() => props.onPreset(\"main\")}\n >\n Reset to default\n </button>\n </div>\n </section>\n\n <section class=\"card\" style=\"display: grid; gap: 16px; overflow: hidden;\">\n <div style=\"display: grid; gap: 4px;\">\n <div class=\"card-title\">Channel Routing</div>\n <div class=\"card-sub\">\n Telegram and WhatsApp always stay visible here. Inactive channels show as not connected until you finish setup.\n </div>\n </div>\n\n <div style=\"overflow-x: auto;\">\n <table style=\"width: 100%; border-collapse: collapse; min-width: 980px;\">\n <thead>\n <tr>\n <th style=\"text-align: left; padding: 0 0 14px; min-width: 220px;\">Channel</th>\n <th style=\"text-align: left; padding: 0 0 14px; min-width: 180px;\">Current owner</th>\n ${props.employees.map(\n (employee) => html`\n <th style=\"text-align: center; padding: 0 8px 14px; min-width: 120px;\">\n <div style=\"display: grid; gap: 4px; justify-items: center;\">\n <span>${employee.name}</span>\n <span class=\"muted\" style=\"font-size: 12px;\">\n ${employee.isCustom ? \"Custom\" : employee.role ?? \"Employee\"}\n </span>\n </div>\n </th>\n `,\n )}\n <th style=\"text-align: left; padding: 0 0 14px; min-width: 220px;\">Assign to</th>\n </tr>\n </thead>\n <tbody>\n ${ROUTING_CHANNELS.map((channel) => {\n const assignedAgentId = props.assignments[channel] ?? \"main\";\n const connected = resolveChannelConnected(props, channel);\n return html`\n <tr style=\"border-top: 1px solid var(--border);\">\n <td style=\"padding: 16px 0; vertical-align: top;\">\n <div style=\"display: grid; gap: 8px;\">\n <div class=\"row\" style=\"gap: 10px; align-items: center;\">\n <span\n style=\"\n display: inline-flex;\n width: 34px;\n height: 34px;\n align-items: center;\n justify-content: center;\n border-radius: 999px;\n background: var(--surface-elevated);\n border: 1px solid var(--border);\n \"\n aria-hidden=\"true\"\n >${resolveChannelIcon(channel)}</span\n >\n <div style=\"display: grid; gap: 3px;\">\n <strong>${labelForRoutingChannel(channel)}</strong>\n <span class=\"muted\" style=\"font-size: 12px;\">\n ${connected ? \"Connected and ready to route\" : \"Not connected\"}\n </span>\n </div>\n </div>\n </div>\n </td>\n <td style=\"padding: 16px 8px; vertical-align: top;\">\n <div style=\"display: grid; gap: 4px;\">\n <span class=\"chip\">${resolveOwnerLabel(assignedAgentId, employeesById)}</span>\n <span class=\"muted\" style=\"font-size: 12px;\">\n ${resolveOwnerSubLabel(assignedAgentId, employeesById)}\n </span>\n </div>\n </td>\n ${props.employees.map((employee) => {\n const active = assignedAgentId === employee.id;\n return html`\n <td style=\"padding: 16px 8px; text-align: center; vertical-align: top;\">\n ${active\n ? html`<span class=\"chip\" style=\"background: var(--surface-elevated);\">\n ${icons.check}\n <span>Assigned</span>\n </span>`\n : html`<span class=\"muted\">-</span>`}\n </td>\n `;\n })}\n <td style=\"padding: 16px 0 16px 8px; vertical-align: top;\">\n <label class=\"field\" style=\"margin: 0;\">\n <select\n .value=${assignedAgentId}\n ?disabled=${props.saving}\n @change=${(event: Event) =>\n props.onAssignmentChange(\n channel,\n (event.target as HTMLSelectElement).value,\n )}\n >\n <option value=\"main\">Main agent (default)</option>\n ${props.employees.map(\n (employee) =>\n html`<option value=${employee.id}>${employee.name}</option>`,\n )}\n </select>\n </label>\n </td>\n </tr>\n `;\n })}\n </tbody>\n </table>\n </div>\n </section>\n\n <section class=\"card\" style=\"display: grid; gap: 12px;\">\n <div class=\"row\" style=\"justify-content: space-between; gap: 12px; align-items: center; flex-wrap: wrap;\">\n <div style=\"display: grid; gap: 4px;\">\n <div class=\"card-title\">Save routing</div>\n <div class=\"card-sub\">Changes take effect immediately - no restart needed.</div>\n </div>\n <button\n type=\"button\"\n class=\"btn primary\"\n ?disabled=${!props.connected || props.saving}\n @click=${props.onSave}\n >\n ${props.saving ? \"Saving...\" : \"Save Routing\"}\n </button>\n </div>\n ${renderMessage(props.message)}\n </section>\n </section>\n `;\n}\n"],"mappings":"mGAyBA,SAAS,EAAc,EAAgC,CAIrD,OAHK,EAGE,CAAI,uBAAuB,EAAQ,OAAS,QAAU,SAAW,UAAU,IAAI,EAAQ,KAAK,QAF1F,EAKX,SAAS,EAAmB,EAA2B,CACrD,OAAO,IAAY,WAAa,EAAM,KAAO,EAAM,cAGrD,SAAS,EAAwB,EAAqB,EAAoC,CACxF,OAAO,IAAY,WAAa,EAAM,kBAAoB,EAAM,kBAGlE,SAAS,EAAkB,EAAiB,EAAuD,CAIjG,OAHI,IAAY,OACP,uBAEF,EAAU,IAAI,EAAQ,EAAE,MAAQ,EAGzC,SAAS,EAAqB,EAAiB,EAAuD,CAIpG,OAHI,IAAY,OACP,uBAEF,EAAU,IAAI,EAAQ,EAAE,MAAQ,oBAGzC,SAAgB,EAAc,EAAqB,CACjD,IAAM,EAAgB,IAAI,IAAI,EAAM,UAAU,IAAK,GAAa,CAAC,EAAS,GAAI,EAAS,CAAU,CAAC,CAElG,MAAO,EAAI;;;;;;;;;;;;;;;YAeD,EAAM,QAAU,CAAI,uDAAyD,EAAQ;;;;;;;wBAOzE,EAAM,OAAO;yBACV,EAAM,SAAS,cAAc,CAAC;;;;;;;wBAOjC,EAAM,OAAO;yBACV,EAAM,SAAS,YAAY,CAAC;;;;;;;wBAO/B,EAAM,OAAO;yBACV,EAAM,SAAS,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;kBAqBhC,EAAM,UAAU,IACf,GAAa,CAAI;;;gCAGJ,EAAS,KAAK;;4BAElB,EAAS,SAAW,SAAW,EAAS,MAAQ,WAAW;;;;oBAKtE,CAAC;;;;;gBAKF,EAAiB,IAAK,GAAY,CAClC,IAAM,EAAkB,EAAM,YAAY,IAAY,OAChD,EAAY,EAAwB,EAAO,EAAQ,CACzD,MAAO,EAAI;;;;;;;;;;;;;;;;;+BAiBI,EAAmB,EAAQ,CAAC;;;sCAGrB,EAAuB,EAAQ,CAAC;;gCAEtC,EAAY,+BAAiC,gBAAgB;;;;;;;;6CAQhD,EAAkB,EAAiB,EAAc,CAAC;;4BAEnE,EAAqB,EAAiB,EAAc,CAAC;;;;sBAI3D,EAAM,UAAU,IAAK,GAEd,CAAI;;4BADI,IAAoB,EAAS,GAIpC,CAAI;kCACA,EAAM,MAAM;;uCAGhB,CAAI,+BAA+B;;wBAG3C,CAAC;;;;mCAIY,EAAgB;sCACb,EAAM,OAAO;oCACd,GACT,EAAM,mBACJ,EACC,EAAM,OAA6B,MACrC,CAAC;;;4BAGF,EAAM,UAAU,IACf,GACC,CAAI,iBAAiB,EAAS,GAAG,GAAG,EAAS,KAAK,WACrD,CAAC;;;;;mBAMZ,CAAC;;;;;;;;;;;;;;;wBAeO,CAAC,EAAM,WAAa,EAAM,OAAO;qBACpC,EAAM,OAAO;;cAEpB,EAAM,OAAS,YAAc,eAAe;;;UAGhD,EAAc,EAAM,QAAQ,CAAC"}
@@ -0,0 +1,236 @@
1
+ import{i as e,n as t}from"./lit-zdTgzAJI.js";import{l as n}from"./format-BahKhiOC.js";import{I as r,c as i,z as a}from"./index-XGDpaFxG.js";var o=[``,`off`,`minimal`,`low`,`medium`,`high`,`xhigh`],s=[``,`off`,`on`],c=[{value:``,label:`inherit`},{value:`off`,label:`off (explicit)`},{value:`on`,label:`on`},{value:`full`,label:`full`}],l=[{value:``,label:`inherit`},{value:`on`,label:`on`},{value:`off`,label:`off`}],u=[``,`off`,`on`,`stream`],d=[10,25,50,100];function f(e){if(!e)return``;let t=e.trim().toLowerCase();return t===`z.ai`||t===`z-ai`?`zai`:t}function p(e){return f(e)===`zai`}function m(e){return p(e)?s:o}function h(e,t){return!t||e.includes(t)?[...e]:[...e,t]}function g(e,t){return!t||e.some(e=>e.value===t)?[...e]:[...e,{value:t,label:`${t} (custom)`}]}function _(e,t){return!t||!e||e===`off`?e:`on`}function v(e,t){return e?t&&e===`on`?`low`:e:null}function y(e,t){let n=t.trim().toLowerCase();return n?e.filter(e=>{let t=(e.key??``).toLowerCase(),r=(e.label??``).toLowerCase(),i=(e.kind??``).toLowerCase(),a=(e.displayName??``).toLowerCase();return t.includes(n)||r.includes(n)||i.includes(n)||a.includes(n)}):e}function b(e,t,n){let r=n===`asc`?1:-1;return[...e].toSorted((e,n)=>{let i=0;switch(t){case`key`:i=(e.key??``).localeCompare(n.key??``);break;case`kind`:i=(e.kind??``).localeCompare(n.kind??``);break;case`updated`:i=(e.updatedAt??0)-(n.updatedAt??0);break;case`tokens`:i=(e.totalTokens??e.inputTokens??e.outputTokens??0)-(n.totalTokens??n.inputTokens??n.outputTokens??0);break}return i*r})}function x(e,t,n){let r=t*n;return e.slice(r,r+n)}function S(n){let i=b(y(n.result?.sessions??[],n.searchQuery),n.sortColumn,n.sortDir),a=i.length,o=Math.max(1,Math.ceil(a/n.pageSize)),s=Math.min(n.page,o-1),c=x(i,s,n.pageSize),l=(t,i,a=``)=>{let o=n.sortColumn===t,s=o&&n.sortDir===`asc`?`desc`:`asc`;return e`
2
+ <th
3
+ class=${a}
4
+ data-sortable
5
+ data-sort-dir=${o?n.sortDir:``}
6
+ @click=${()=>n.onSortChange(t,o?s:`desc`)}
7
+ >
8
+ ${i}
9
+ <span class="data-table-sort-icon">${r.arrowUpDown}</span>
10
+ </th>
11
+ `};return e`
12
+ <section class="card">
13
+ <div class="row" style="justify-content: space-between; margin-bottom: 12px;">
14
+ <div>
15
+ <div class="card-title">Sessions</div>
16
+ <div class="card-sub">
17
+ ${n.result?`Store: ${n.result.path}`:`Active session keys and per-session overrides.`}
18
+ </div>
19
+ </div>
20
+ <button class="btn" ?disabled=${n.loading} @click=${n.onRefresh}>
21
+ ${n.loading?`Loading…`:`Refresh`}
22
+ </button>
23
+ </div>
24
+
25
+ <div class="filters" style="margin-bottom: 12px;">
26
+ <label class="field-inline">
27
+ <span>Active</span>
28
+ <input
29
+ style="width: 72px;"
30
+ placeholder="min"
31
+ .value=${n.activeMinutes}
32
+ @input=${e=>n.onFiltersChange({activeMinutes:e.target.value,limit:n.limit,includeGlobal:n.includeGlobal,includeUnknown:n.includeUnknown})}
33
+ />
34
+ </label>
35
+ <label class="field-inline">
36
+ <span>Limit</span>
37
+ <input
38
+ style="width: 64px;"
39
+ .value=${n.limit}
40
+ @input=${e=>n.onFiltersChange({activeMinutes:n.activeMinutes,limit:e.target.value,includeGlobal:n.includeGlobal,includeUnknown:n.includeUnknown})}
41
+ />
42
+ </label>
43
+ <label class="field-inline checkbox">
44
+ <input
45
+ type="checkbox"
46
+ .checked=${n.includeGlobal}
47
+ @change=${e=>n.onFiltersChange({activeMinutes:n.activeMinutes,limit:n.limit,includeGlobal:e.target.checked,includeUnknown:n.includeUnknown})}
48
+ />
49
+ <span>Global</span>
50
+ </label>
51
+ <label class="field-inline checkbox">
52
+ <input
53
+ type="checkbox"
54
+ .checked=${n.includeUnknown}
55
+ @change=${e=>n.onFiltersChange({activeMinutes:n.activeMinutes,limit:n.limit,includeGlobal:n.includeGlobal,includeUnknown:e.target.checked})}
56
+ />
57
+ <span>Unknown</span>
58
+ </label>
59
+ </div>
60
+
61
+ ${n.error?e`<div class="callout danger" style="margin-bottom: 12px;">${n.error}</div>`:t}
62
+
63
+ <div class="data-table-wrapper">
64
+ <div class="data-table-toolbar">
65
+ <div class="data-table-search">
66
+ <input
67
+ type="text"
68
+ placeholder="Filter by key, label, kind…"
69
+ .value=${n.searchQuery}
70
+ @input=${e=>n.onSearchChange(e.target.value)}
71
+ />
72
+ </div>
73
+ </div>
74
+
75
+ ${n.selectedKeys.size>0?e`
76
+ <div class="data-table-bulk-bar">
77
+ <span>${n.selectedKeys.size} selected</span>
78
+ <button class="btn btn--sm" @click=${n.onDeselectAll}>Unselect</button>
79
+ <button
80
+ class="btn btn--sm danger"
81
+ ?disabled=${n.loading}
82
+ @click=${n.onDeleteSelected}
83
+ >
84
+ ${r.trash} Delete
85
+ </button>
86
+ </div>
87
+ `:t}
88
+
89
+ <div class="data-table-container">
90
+ <table class="data-table">
91
+ <thead>
92
+ <tr>
93
+ <th class="data-table-checkbox-col">
94
+ ${c.length>0?e`<input
95
+ type="checkbox"
96
+ .checked=${c.length>0&&c.every(e=>n.selectedKeys.has(e.key))}
97
+ .indeterminate=${c.some(e=>n.selectedKeys.has(e.key))&&!c.every(e=>n.selectedKeys.has(e.key))}
98
+ @change=${()=>{c.every(e=>n.selectedKeys.has(e.key))?n.onDeselectPage(c.map(e=>e.key)):n.onSelectPage(c.map(e=>e.key))}}
99
+ aria-label="Select all on page"
100
+ />`:t}
101
+ </th>
102
+ ${l(`key`,`Key`,`data-table-key-col`)}
103
+ <th>Label</th>
104
+ ${l(`kind`,`Kind`)} ${l(`updated`,`Updated`)}
105
+ ${l(`tokens`,`Tokens`)}
106
+ <th>Thinking</th>
107
+ <th>Fast</th>
108
+ <th>Verbose</th>
109
+ <th>Reasoning</th>
110
+ </tr>
111
+ </thead>
112
+ <tbody>
113
+ ${c.length===0?e`
114
+ <tr>
115
+ <td
116
+ colspan="10"
117
+ style="text-align: center; padding: 48px 16px; color: var(--muted)"
118
+ >
119
+ No sessions found.
120
+ </td>
121
+ </tr>
122
+ `:c.map(e=>C(e,n.basePath,n.onPatch,n.selectedKeys.has(e.key),n.onToggleSelect,n.loading,n.onNavigateToChat))}
123
+ </tbody>
124
+ </table>
125
+ </div>
126
+
127
+ ${a>0?e`
128
+ <div class="data-table-pagination">
129
+ <div class="data-table-pagination__info">
130
+ ${s*n.pageSize+1}-${Math.min((s+1)*n.pageSize,a)}
131
+ of ${a} row${a===1?``:`s`}
132
+ </div>
133
+ <div class="data-table-pagination__controls">
134
+ <select
135
+ style="height: 32px; padding: 0 8px; font-size: 13px; border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--card);"
136
+ .value=${String(n.pageSize)}
137
+ @change=${e=>n.onPageSizeChange(Number(e.target.value))}
138
+ >
139
+ ${d.map(t=>e`<option value=${t}>${t} per page</option>`)}
140
+ </select>
141
+ <button ?disabled=${s<=0} @click=${()=>n.onPageChange(s-1)}>
142
+ Previous
143
+ </button>
144
+ <button
145
+ ?disabled=${s>=o-1}
146
+ @click=${()=>n.onPageChange(s+1)}
147
+ >
148
+ Next
149
+ </button>
150
+ </div>
151
+ </div>
152
+ `:t}
153
+ </div>
154
+ </section>
155
+ `}function C(r,o,s,d,f,y,b){let x=r.updatedAt?n(r.updatedAt):`n/a`,S=r.thinkingLevel??``,C=p(r.modelProvider),w=_(S,C),T=h(m(r.modelProvider),w),E=r.fastMode===!0?`on`:r.fastMode===!1?`off`:``,D=g(l,E),O=r.verboseLevel??``,k=g(c,O),A=r.reasoningLevel??``,j=h(u,A),M=typeof r.displayName==`string`&&r.displayName.trim().length>0?r.displayName.trim():null,N=!!(M&&M!==r.key&&M!==(typeof r.label==`string`?r.label.trim():``)),P=r.kind!==`global`,F=P?`${a(`chat`,o)}?session=${encodeURIComponent(r.key)}`:null,I=r.kind===`direct`?`data-table-badge--direct`:r.kind===`group`?`data-table-badge--group`:r.kind===`global`?`data-table-badge--global`:`data-table-badge--unknown`;return e`
156
+ <tr>
157
+ <td class="data-table-checkbox-col">
158
+ <input
159
+ type="checkbox"
160
+ .checked=${d}
161
+ @change=${()=>f(r.key)}
162
+ aria-label="Select session"
163
+ />
164
+ </td>
165
+ <td class="data-table-key-col">
166
+ <div class="mono session-key-cell">
167
+ ${P?e`<a
168
+ href=${F}
169
+ class="session-link"
170
+ @click=${e=>{e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||b&&(e.preventDefault(),b(r.key))}}
171
+ >${r.key}</a
172
+ >`:r.key}
173
+ ${N?e`<span class="muted session-key-display-name">${M}</span>`:t}
174
+ </div>
175
+ </td>
176
+ <td>
177
+ <input
178
+ .value=${r.label??``}
179
+ ?disabled=${y}
180
+ placeholder="(optional)"
181
+ style="width: 100%; max-width: 140px; padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm);"
182
+ @change=${e=>{let t=e.target.value.trim();s(r.key,{label:t||null})}}
183
+ />
184
+ </td>
185
+ <td>
186
+ <span class="data-table-badge ${I}">${r.kind}</span>
187
+ </td>
188
+ <td>${x}</td>
189
+ <td>${i(r)}</td>
190
+ <td>
191
+ <select
192
+ ?disabled=${y}
193
+ style="padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;"
194
+ @change=${e=>{let t=e.target.value;s(r.key,{thinkingLevel:v(t,C)})}}
195
+ >
196
+ ${T.map(t=>e`<option value=${t} ?selected=${w===t}>
197
+ ${t||`inherit`}
198
+ </option>`)}
199
+ </select>
200
+ </td>
201
+ <td>
202
+ <select
203
+ ?disabled=${y}
204
+ style="padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;"
205
+ @change=${e=>{let t=e.target.value;s(r.key,{fastMode:t===``?null:t===`on`})}}
206
+ >
207
+ ${D.map(t=>e`<option value=${t.value} ?selected=${E===t.value}>
208
+ ${t.label}
209
+ </option>`)}
210
+ </select>
211
+ </td>
212
+ <td>
213
+ <select
214
+ ?disabled=${y}
215
+ style="padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;"
216
+ @change=${e=>{let t=e.target.value;s(r.key,{verboseLevel:t||null})}}
217
+ >
218
+ ${k.map(t=>e`<option value=${t.value} ?selected=${O===t.value}>
219
+ ${t.label}
220
+ </option>`)}
221
+ </select>
222
+ </td>
223
+ <td>
224
+ <select
225
+ ?disabled=${y}
226
+ style="padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;"
227
+ @change=${e=>{let t=e.target.value;s(r.key,{reasoningLevel:t||null})}}
228
+ >
229
+ ${j.map(t=>e`<option value=${t} ?selected=${A===t}>
230
+ ${t||`inherit`}
231
+ </option>`)}
232
+ </select>
233
+ </td>
234
+ </tr>
235
+ `}export{S as renderSessions};
236
+ //# sourceMappingURL=sessions-N9rgJP2R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions-N9rgJP2R.js","names":[],"sources":["../../../ui/src/ui/views/sessions.ts"],"sourcesContent":["import { html, nothing } from \"lit\";\nimport { formatRelativeTimestamp } from \"../format.ts\";\nimport { icons } from \"../icons.ts\";\nimport { pathForTab } from \"../navigation.ts\";\nimport { formatSessionTokens } from \"../presenter.ts\";\nimport type { GatewaySessionRow, SessionsListResult } from \"../types.ts\";\n\nexport type SessionsProps = {\n loading: boolean;\n result: SessionsListResult | null;\n error: string | null;\n activeMinutes: string;\n limit: string;\n includeGlobal: boolean;\n includeUnknown: boolean;\n basePath: string;\n searchQuery: string;\n sortColumn: \"key\" | \"kind\" | \"updated\" | \"tokens\";\n sortDir: \"asc\" | \"desc\";\n page: number;\n pageSize: number;\n selectedKeys: Set<string>;\n onFiltersChange: (next: {\n activeMinutes: string;\n limit: string;\n includeGlobal: boolean;\n includeUnknown: boolean;\n }) => void;\n onSearchChange: (query: string) => void;\n onSortChange: (column: \"key\" | \"kind\" | \"updated\" | \"tokens\", dir: \"asc\" | \"desc\") => void;\n onPageChange: (page: number) => void;\n onPageSizeChange: (size: number) => void;\n onRefresh: () => void;\n onPatch: (\n key: string,\n patch: {\n label?: string | null;\n thinkingLevel?: string | null;\n fastMode?: boolean | null;\n verboseLevel?: string | null;\n reasoningLevel?: string | null;\n },\n ) => void;\n onToggleSelect: (key: string) => void;\n onSelectPage: (keys: string[]) => void;\n onDeselectPage: (keys: string[]) => void;\n onDeselectAll: () => void;\n onDeleteSelected: () => void;\n onNavigateToChat?: (sessionKey: string) => void;\n};\n\nconst THINK_LEVELS = [\"\", \"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"] as const;\nconst BINARY_THINK_LEVELS = [\"\", \"off\", \"on\"] as const;\nconst VERBOSE_LEVELS = [\n { value: \"\", label: \"inherit\" },\n { value: \"off\", label: \"off (explicit)\" },\n { value: \"on\", label: \"on\" },\n { value: \"full\", label: \"full\" },\n] as const;\nconst FAST_LEVELS = [\n { value: \"\", label: \"inherit\" },\n { value: \"on\", label: \"on\" },\n { value: \"off\", label: \"off\" },\n] as const;\nconst REASONING_LEVELS = [\"\", \"off\", \"on\", \"stream\"] as const;\nconst PAGE_SIZES = [10, 25, 50, 100] as const;\n\nfunction normalizeProviderId(provider?: string | null): string {\n if (!provider) {\n return \"\";\n }\n const normalized = provider.trim().toLowerCase();\n if (normalized === \"z.ai\" || normalized === \"z-ai\") {\n return \"zai\";\n }\n return normalized;\n}\n\nfunction isBinaryThinkingProvider(provider?: string | null): boolean {\n return normalizeProviderId(provider) === \"zai\";\n}\n\nfunction resolveThinkLevelOptions(provider?: string | null): readonly string[] {\n return isBinaryThinkingProvider(provider) ? BINARY_THINK_LEVELS : THINK_LEVELS;\n}\n\nfunction withCurrentOption(options: readonly string[], current: string): string[] {\n if (!current) {\n return [...options];\n }\n if (options.includes(current)) {\n return [...options];\n }\n return [...options, current];\n}\n\nfunction withCurrentLabeledOption(\n options: readonly { value: string; label: string }[],\n current: string,\n): Array<{ value: string; label: string }> {\n if (!current) {\n return [...options];\n }\n if (options.some((option) => option.value === current)) {\n return [...options];\n }\n return [...options, { value: current, label: `${current} (custom)` }];\n}\n\nfunction resolveThinkLevelDisplay(value: string, isBinary: boolean): string {\n if (!isBinary) {\n return value;\n }\n if (!value || value === \"off\") {\n return value;\n }\n return \"on\";\n}\n\nfunction resolveThinkLevelPatchValue(value: string, isBinary: boolean): string | null {\n if (!value) {\n return null;\n }\n if (!isBinary) {\n return value;\n }\n if (value === \"on\") {\n return \"low\";\n }\n return value;\n}\n\nfunction filterRows(rows: GatewaySessionRow[], query: string): GatewaySessionRow[] {\n const q = query.trim().toLowerCase();\n if (!q) {\n return rows;\n }\n return rows.filter((row) => {\n const key = (row.key ?? \"\").toLowerCase();\n const label = (row.label ?? \"\").toLowerCase();\n const kind = (row.kind ?? \"\").toLowerCase();\n const displayName = (row.displayName ?? \"\").toLowerCase();\n return key.includes(q) || label.includes(q) || kind.includes(q) || displayName.includes(q);\n });\n}\n\nfunction sortRows(\n rows: GatewaySessionRow[],\n column: \"key\" | \"kind\" | \"updated\" | \"tokens\",\n dir: \"asc\" | \"desc\",\n): GatewaySessionRow[] {\n const cmp = dir === \"asc\" ? 1 : -1;\n return [...rows].toSorted((a, b) => {\n let diff = 0;\n switch (column) {\n case \"key\":\n diff = (a.key ?? \"\").localeCompare(b.key ?? \"\");\n break;\n case \"kind\":\n diff = (a.kind ?? \"\").localeCompare(b.kind ?? \"\");\n break;\n case \"updated\": {\n const au = a.updatedAt ?? 0;\n const bu = b.updatedAt ?? 0;\n diff = au - bu;\n break;\n }\n case \"tokens\": {\n const at = a.totalTokens ?? a.inputTokens ?? a.outputTokens ?? 0;\n const bt = b.totalTokens ?? b.inputTokens ?? b.outputTokens ?? 0;\n diff = at - bt;\n break;\n }\n }\n return diff * cmp;\n });\n}\n\nfunction paginateRows<T>(rows: T[], page: number, pageSize: number): T[] {\n const start = page * pageSize;\n return rows.slice(start, start + pageSize);\n}\n\nexport function renderSessions(props: SessionsProps) {\n const rawRows = props.result?.sessions ?? [];\n const filtered = filterRows(rawRows, props.searchQuery);\n const sorted = sortRows(filtered, props.sortColumn, props.sortDir);\n const totalRows = sorted.length;\n const totalPages = Math.max(1, Math.ceil(totalRows / props.pageSize));\n const page = Math.min(props.page, totalPages - 1);\n const paginated = paginateRows(sorted, page, props.pageSize);\n\n const sortHeader = (\n col: \"key\" | \"kind\" | \"updated\" | \"tokens\",\n label: string,\n extraClass = \"\",\n ) => {\n const isActive = props.sortColumn === col;\n const nextDir = isActive && props.sortDir === \"asc\" ? (\"desc\" as const) : (\"asc\" as const);\n return html`\n <th\n class=${extraClass}\n data-sortable\n data-sort-dir=${isActive ? props.sortDir : \"\"}\n @click=${() => props.onSortChange(col, isActive ? nextDir : \"desc\")}\n >\n ${label}\n <span class=\"data-table-sort-icon\">${icons.arrowUpDown}</span>\n </th>\n `;\n };\n\n return html`\n <section class=\"card\">\n <div class=\"row\" style=\"justify-content: space-between; margin-bottom: 12px;\">\n <div>\n <div class=\"card-title\">Sessions</div>\n <div class=\"card-sub\">\n ${props.result\n ? `Store: ${props.result.path}`\n : \"Active session keys and per-session overrides.\"}\n </div>\n </div>\n <button class=\"btn\" ?disabled=${props.loading} @click=${props.onRefresh}>\n ${props.loading ? \"Loading…\" : \"Refresh\"}\n </button>\n </div>\n\n <div class=\"filters\" style=\"margin-bottom: 12px;\">\n <label class=\"field-inline\">\n <span>Active</span>\n <input\n style=\"width: 72px;\"\n placeholder=\"min\"\n .value=${props.activeMinutes}\n @input=${(e: Event) =>\n props.onFiltersChange({\n activeMinutes: (e.target as HTMLInputElement).value,\n limit: props.limit,\n includeGlobal: props.includeGlobal,\n includeUnknown: props.includeUnknown,\n })}\n />\n </label>\n <label class=\"field-inline\">\n <span>Limit</span>\n <input\n style=\"width: 64px;\"\n .value=${props.limit}\n @input=${(e: Event) =>\n props.onFiltersChange({\n activeMinutes: props.activeMinutes,\n limit: (e.target as HTMLInputElement).value,\n includeGlobal: props.includeGlobal,\n includeUnknown: props.includeUnknown,\n })}\n />\n </label>\n <label class=\"field-inline checkbox\">\n <input\n type=\"checkbox\"\n .checked=${props.includeGlobal}\n @change=${(e: Event) =>\n props.onFiltersChange({\n activeMinutes: props.activeMinutes,\n limit: props.limit,\n includeGlobal: (e.target as HTMLInputElement).checked,\n includeUnknown: props.includeUnknown,\n })}\n />\n <span>Global</span>\n </label>\n <label class=\"field-inline checkbox\">\n <input\n type=\"checkbox\"\n .checked=${props.includeUnknown}\n @change=${(e: Event) =>\n props.onFiltersChange({\n activeMinutes: props.activeMinutes,\n limit: props.limit,\n includeGlobal: props.includeGlobal,\n includeUnknown: (e.target as HTMLInputElement).checked,\n })}\n />\n <span>Unknown</span>\n </label>\n </div>\n\n ${props.error\n ? html`<div class=\"callout danger\" style=\"margin-bottom: 12px;\">${props.error}</div>`\n : nothing}\n\n <div class=\"data-table-wrapper\">\n <div class=\"data-table-toolbar\">\n <div class=\"data-table-search\">\n <input\n type=\"text\"\n placeholder=\"Filter by key, label, kind…\"\n .value=${props.searchQuery}\n @input=${(e: Event) => props.onSearchChange((e.target as HTMLInputElement).value)}\n />\n </div>\n </div>\n\n ${props.selectedKeys.size > 0\n ? html`\n <div class=\"data-table-bulk-bar\">\n <span>${props.selectedKeys.size} selected</span>\n <button class=\"btn btn--sm\" @click=${props.onDeselectAll}>Unselect</button>\n <button\n class=\"btn btn--sm danger\"\n ?disabled=${props.loading}\n @click=${props.onDeleteSelected}\n >\n ${icons.trash} Delete\n </button>\n </div>\n `\n : nothing}\n\n <div class=\"data-table-container\">\n <table class=\"data-table\">\n <thead>\n <tr>\n <th class=\"data-table-checkbox-col\">\n ${paginated.length > 0\n ? html`<input\n type=\"checkbox\"\n .checked=${paginated.length > 0 &&\n paginated.every((r) => props.selectedKeys.has(r.key))}\n .indeterminate=${paginated.some((r) => props.selectedKeys.has(r.key)) &&\n !paginated.every((r) => props.selectedKeys.has(r.key))}\n @change=${() => {\n const allSelected = paginated.every((r) => props.selectedKeys.has(r.key));\n if (allSelected) {\n props.onDeselectPage(paginated.map((r) => r.key));\n } else {\n props.onSelectPage(paginated.map((r) => r.key));\n }\n }}\n aria-label=\"Select all on page\"\n />`\n : nothing}\n </th>\n ${sortHeader(\"key\", \"Key\", \"data-table-key-col\")}\n <th>Label</th>\n ${sortHeader(\"kind\", \"Kind\")} ${sortHeader(\"updated\", \"Updated\")}\n ${sortHeader(\"tokens\", \"Tokens\")}\n <th>Thinking</th>\n <th>Fast</th>\n <th>Verbose</th>\n <th>Reasoning</th>\n </tr>\n </thead>\n <tbody>\n ${paginated.length === 0\n ? html`\n <tr>\n <td\n colspan=\"10\"\n style=\"text-align: center; padding: 48px 16px; color: var(--muted)\"\n >\n No sessions found.\n </td>\n </tr>\n `\n : paginated.map((row) =>\n renderRow(\n row,\n props.basePath,\n props.onPatch,\n props.selectedKeys.has(row.key),\n props.onToggleSelect,\n props.loading,\n props.onNavigateToChat,\n ),\n )}\n </tbody>\n </table>\n </div>\n\n ${totalRows > 0\n ? html`\n <div class=\"data-table-pagination\">\n <div class=\"data-table-pagination__info\">\n ${page * props.pageSize + 1}-${Math.min((page + 1) * props.pageSize, totalRows)}\n of ${totalRows} row${totalRows === 1 ? \"\" : \"s\"}\n </div>\n <div class=\"data-table-pagination__controls\">\n <select\n style=\"height: 32px; padding: 0 8px; font-size: 13px; border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--card);\"\n .value=${String(props.pageSize)}\n @change=${(e: Event) =>\n props.onPageSizeChange(Number((e.target as HTMLSelectElement).value))}\n >\n ${PAGE_SIZES.map((s) => html`<option value=${s}>${s} per page</option>`)}\n </select>\n <button ?disabled=${page <= 0} @click=${() => props.onPageChange(page - 1)}>\n Previous\n </button>\n <button\n ?disabled=${page >= totalPages - 1}\n @click=${() => props.onPageChange(page + 1)}\n >\n Next\n </button>\n </div>\n </div>\n `\n : nothing}\n </div>\n </section>\n `;\n}\n\nfunction renderRow(\n row: GatewaySessionRow,\n basePath: string,\n onPatch: SessionsProps[\"onPatch\"],\n selected: boolean,\n onToggleSelect: SessionsProps[\"onToggleSelect\"],\n disabled: boolean,\n onNavigateToChat?: (sessionKey: string) => void,\n) {\n const updated = row.updatedAt ? formatRelativeTimestamp(row.updatedAt) : \"n/a\";\n const rawThinking = row.thinkingLevel ?? \"\";\n const isBinaryThinking = isBinaryThinkingProvider(row.modelProvider);\n const thinking = resolveThinkLevelDisplay(rawThinking, isBinaryThinking);\n const thinkLevels = withCurrentOption(resolveThinkLevelOptions(row.modelProvider), thinking);\n const fastMode = row.fastMode === true ? \"on\" : row.fastMode === false ? \"off\" : \"\";\n const fastLevels = withCurrentLabeledOption(FAST_LEVELS, fastMode);\n const verbose = row.verboseLevel ?? \"\";\n const verboseLevels = withCurrentLabeledOption(VERBOSE_LEVELS, verbose);\n const reasoning = row.reasoningLevel ?? \"\";\n const reasoningLevels = withCurrentOption(REASONING_LEVELS, reasoning);\n const displayName =\n typeof row.displayName === \"string\" && row.displayName.trim().length > 0\n ? row.displayName.trim()\n : null;\n const showDisplayName = Boolean(\n displayName &&\n displayName !== row.key &&\n displayName !== (typeof row.label === \"string\" ? row.label.trim() : \"\"),\n );\n const canLink = row.kind !== \"global\";\n const chatUrl = canLink\n ? `${pathForTab(\"chat\", basePath)}?session=${encodeURIComponent(row.key)}`\n : null;\n const badgeClass =\n row.kind === \"direct\"\n ? \"data-table-badge--direct\"\n : row.kind === \"group\"\n ? \"data-table-badge--group\"\n : row.kind === \"global\"\n ? \"data-table-badge--global\"\n : \"data-table-badge--unknown\";\n\n return html`\n <tr>\n <td class=\"data-table-checkbox-col\">\n <input\n type=\"checkbox\"\n .checked=${selected}\n @change=${() => onToggleSelect(row.key)}\n aria-label=\"Select session\"\n />\n </td>\n <td class=\"data-table-key-col\">\n <div class=\"mono session-key-cell\">\n ${canLink\n ? html`<a\n href=${chatUrl}\n class=\"session-link\"\n @click=${(e: MouseEvent) => {\n if (\n e.defaultPrevented ||\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey\n ) {\n return;\n }\n if (onNavigateToChat) {\n e.preventDefault();\n onNavigateToChat(row.key);\n }\n }}\n >${row.key}</a\n >`\n : row.key}\n ${showDisplayName\n ? html`<span class=\"muted session-key-display-name\">${displayName}</span>`\n : nothing}\n </div>\n </td>\n <td>\n <input\n .value=${row.label ?? \"\"}\n ?disabled=${disabled}\n placeholder=\"(optional)\"\n style=\"width: 100%; max-width: 140px; padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm);\"\n @change=${(e: Event) => {\n const value = (e.target as HTMLInputElement).value.trim();\n onPatch(row.key, { label: value || null });\n }}\n />\n </td>\n <td>\n <span class=\"data-table-badge ${badgeClass}\">${row.kind}</span>\n </td>\n <td>${updated}</td>\n <td>${formatSessionTokens(row)}</td>\n <td>\n <select\n ?disabled=${disabled}\n style=\"padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;\"\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value;\n onPatch(row.key, {\n thinkingLevel: resolveThinkLevelPatchValue(value, isBinaryThinking),\n });\n }}\n >\n ${thinkLevels.map(\n (level) =>\n html`<option value=${level} ?selected=${thinking === level}>\n ${level || \"inherit\"}\n </option>`,\n )}\n </select>\n </td>\n <td>\n <select\n ?disabled=${disabled}\n style=\"padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;\"\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value;\n onPatch(row.key, { fastMode: value === \"\" ? null : value === \"on\" });\n }}\n >\n ${fastLevels.map(\n (level) =>\n html`<option value=${level.value} ?selected=${fastMode === level.value}>\n ${level.label}\n </option>`,\n )}\n </select>\n </td>\n <td>\n <select\n ?disabled=${disabled}\n style=\"padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;\"\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value;\n onPatch(row.key, { verboseLevel: value || null });\n }}\n >\n ${verboseLevels.map(\n (level) =>\n html`<option value=${level.value} ?selected=${verbose === level.value}>\n ${level.label}\n </option>`,\n )}\n </select>\n </td>\n <td>\n <select\n ?disabled=${disabled}\n style=\"padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;\"\n @change=${(e: Event) => {\n const value = (e.target as HTMLSelectElement).value;\n onPatch(row.key, { reasoningLevel: value || null });\n }}\n >\n ${reasoningLevels.map(\n (level) =>\n html`<option value=${level} ?selected=${reasoning === level}>\n ${level || \"inherit\"}\n </option>`,\n )}\n </select>\n </td>\n </tr>\n `;\n}\n"],"mappings":"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"}