@chaaskit/client 0.1.0 → 0.1.2

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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/dist/lib/index.js +1023 -160
  3. package/dist/lib/index.js.map +1 -1
  4. package/dist/lib/routes/AcceptInviteRoute.js +1 -1
  5. package/dist/lib/routes/AcceptInviteRoute.js.map +1 -1
  6. package/dist/lib/routes/AdminDashboardRoute.js +1 -1
  7. package/dist/lib/routes/AdminDashboardRoute.js.map +1 -1
  8. package/dist/lib/routes/AdminPromoCodesRoute.js +19 -0
  9. package/dist/lib/routes/AdminPromoCodesRoute.js.map +1 -0
  10. package/dist/lib/routes/AdminTeamRoute.js +1 -1
  11. package/dist/lib/routes/AdminTeamRoute.js.map +1 -1
  12. package/dist/lib/routes/AdminTeamsRoute.js +1 -1
  13. package/dist/lib/routes/AdminTeamsRoute.js.map +1 -1
  14. package/dist/lib/routes/AdminUsersRoute.js +1 -1
  15. package/dist/lib/routes/AdminUsersRoute.js.map +1 -1
  16. package/dist/lib/routes/AdminWaitlistRoute.js +19 -0
  17. package/dist/lib/routes/AdminWaitlistRoute.js.map +1 -0
  18. package/dist/lib/routes/ApiKeysRoute.js +1 -1
  19. package/dist/lib/routes/ApiKeysRoute.js.map +1 -1
  20. package/dist/lib/routes/AutomationsRoute.js +1 -1
  21. package/dist/lib/routes/AutomationsRoute.js.map +1 -1
  22. package/dist/lib/routes/ChatRoute.js +1 -1
  23. package/dist/lib/routes/ChatRoute.js.map +1 -1
  24. package/dist/lib/routes/DocumentsRoute.js +1 -1
  25. package/dist/lib/routes/DocumentsRoute.js.map +1 -1
  26. package/dist/lib/routes/OAuthConsentRoute.js +1 -1
  27. package/dist/lib/routes/OAuthConsentRoute.js.map +1 -1
  28. package/dist/lib/routes/PricingRoute.js +1 -1
  29. package/dist/lib/routes/PricingRoute.js.map +1 -1
  30. package/dist/lib/routes/PrivacyRoute.js +1 -1
  31. package/dist/lib/routes/PrivacyRoute.js.map +1 -1
  32. package/dist/lib/routes/TeamSettingsRoute.js +1 -1
  33. package/dist/lib/routes/TeamSettingsRoute.js.map +1 -1
  34. package/dist/lib/routes/TermsRoute.js +1 -1
  35. package/dist/lib/routes/TermsRoute.js.map +1 -1
  36. package/dist/lib/routes/VerifyEmailRoute.js +1 -1
  37. package/dist/lib/routes/VerifyEmailRoute.js.map +1 -1
  38. package/dist/lib/routes.js +47 -37
  39. package/dist/lib/routes.js.map +1 -1
  40. package/dist/lib/ssr-utils.js +64 -1
  41. package/dist/lib/ssr-utils.js.map +1 -1
  42. package/dist/lib/ssr.js +23 -0
  43. package/dist/lib/ssr.js.map +1 -1
  44. package/dist/lib/styles.css +58 -62
  45. package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -1
  46. package/package.json +25 -12
  47. package/src/components/MessageItem.tsx +35 -4
  48. package/src/components/MessageList.tsx +51 -5
  49. package/src/components/OAuthAppsSection.tsx +1 -1
  50. package/src/components/Sidebar.tsx +1 -3
  51. package/src/components/ToolCallDisplay.tsx +102 -11
  52. package/src/components/tool-renderers/DocumentListRenderer.tsx +44 -0
  53. package/src/components/tool-renderers/DocumentReadRenderer.tsx +33 -0
  54. package/src/components/tool-renderers/DocumentSaveRenderer.tsx +32 -0
  55. package/src/components/tool-renderers/DocumentSearchRenderer.tsx +33 -0
  56. package/src/components/tool-renderers/index.ts +36 -0
  57. package/src/components/tool-renderers/utils.ts +7 -0
  58. package/src/contexts/AuthContext.tsx +16 -6
  59. package/src/contexts/ConfigContext.tsx +60 -28
  60. package/src/contexts/ThemeContext.tsx +39 -68
  61. package/src/extensions/registry.ts +2 -1
  62. package/src/hooks/__tests__/basePath.test.ts +42 -0
  63. package/src/index.tsx +11 -2
  64. package/src/pages/AdminDashboardPage.tsx +15 -1
  65. package/src/pages/AdminPromoCodesPage.tsx +378 -0
  66. package/src/pages/AdminTeamPage.tsx +29 -1
  67. package/src/pages/AdminTeamsPage.tsx +15 -1
  68. package/src/pages/AdminUsersPage.tsx +15 -1
  69. package/src/pages/AdminWaitlistPage.tsx +156 -0
  70. package/src/pages/RegisterPage.tsx +91 -9
  71. package/src/routes/AcceptInviteRoute.tsx +1 -1
  72. package/src/routes/AdminDashboardRoute.tsx +1 -1
  73. package/src/routes/AdminPromoCodesRoute.tsx +24 -0
  74. package/src/routes/AdminTeamRoute.tsx +1 -1
  75. package/src/routes/AdminTeamsRoute.tsx +1 -1
  76. package/src/routes/AdminUsersRoute.tsx +1 -1
  77. package/src/routes/AdminWaitlistRoute.tsx +24 -0
  78. package/src/routes/ApiKeysRoute.tsx +1 -1
  79. package/src/routes/AutomationsRoute.tsx +1 -1
  80. package/src/routes/ChatRoute.tsx +2 -1
  81. package/src/routes/DocumentsRoute.tsx +1 -1
  82. package/src/routes/OAuthConsentRoute.tsx +1 -1
  83. package/src/routes/PricingRoute.tsx +1 -1
  84. package/src/routes/PrivacyRoute.tsx +1 -1
  85. package/src/routes/TeamSettingsRoute.tsx +1 -1
  86. package/src/routes/TermsRoute.tsx +1 -1
  87. package/src/routes/VerifyEmailRoute.tsx +1 -1
  88. package/src/routes/index.ts +2 -0
  89. package/src/ssr-utils.tsx +100 -1
  90. package/src/ssr.ts +59 -0
  91. package/src/stores/chatStore.ts +5 -0
  92. package/src/styles/index.css +16 -63
  93. package/src/tailwind-preset.js +360 -0
  94. package/dist/favicon.svg +0 -11
  95. package/dist/index.html +0 -17
  96. package/dist/logo.svg +0 -12
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.js","sources":["../../src/ssr.ts"],"sourcesContent":["/**\n * SSR-safe exports for server-side rendering.\n *\n * This module exports components and utilities that can be used on the server\n * without browser APIs like window, document, or localStorage.\n */\n\nimport type { AppConfig } from '@chaaskit/shared';\n\n// SSR-safe context providers\nexport {\n ServerConfigProvider,\n useServerConfig,\n useServerConfigLoaded,\n type ServerConfigProviderProps,\n} from './contexts/ServerConfigProvider';\n\nexport {\n ServerThemeProvider,\n useServerTheme,\n type ServerThemeProviderProps,\n} from './contexts/ServerThemeProvider';\n\n// NOTE: SSRMessageList and SSRMarkdownRenderer are NOT exported here because\n// they depend on react-markdown which has browser-only dependencies.\n// Use them only in client-side code wrapped in <ClientOnly>.\n\n// ============================================\n// Theme utilities for SSR\n// ============================================\n\n/**\n * Converts a hex color to RGB values string (e.g., \"#ff0000\" -> \"255 0 0\")\n */\nfunction hexToRgb(hex: string): string {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return '';\n return `${parseInt(result[1]!, 16)} ${parseInt(result[2]!, 16)} ${parseInt(result[3]!, 16)}`;\n}\n\n/**\n * Generates CSS variables for a theme.\n * Use this in your React Router root.tsx to inject theme styles.\n *\n * @example\n * ```tsx\n * // app/root.tsx\n * import { generateThemeCSS } from '@chaaskit/client/ssr';\n *\n * export default function Root() {\n * const themeCSS = generateThemeCSS(config, 'dark');\n * return (\n * <html>\n * <head>\n * <style dangerouslySetInnerHTML={{ __html: themeCSS }} />\n * </head>\n * ...\n * </html>\n * );\n * }\n * ```\n */\nexport function generateThemeCSS(config: AppConfig, theme: string): string {\n const themeConfig = config.theming.themes[theme];\n if (!themeConfig) return '';\n\n const cssVars = Object.entries(themeConfig.colors)\n .map(([key, value]) => {\n const cssKey = `--color-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`;\n return `${cssKey}: ${hexToRgb(value)};`;\n })\n .join('\\n ');\n\n return `\n :root {\n ${cssVars}\n --font-sans: ${config.theming.fonts.sans};\n --font-mono: ${config.theming.fonts.mono};\n --radius-sm: ${config.theming.borderRadius.sm};\n --radius-md: ${config.theming.borderRadius.md};\n --radius-lg: ${config.theming.borderRadius.lg};\n --radius-full: ${config.theming.borderRadius.full};\n }\n `;\n}\n\n/**\n * Returns an object of CSS variable name -> value pairs for a theme.\n * Useful if you need programmatic access to theme values.\n */\nexport function getThemeVariables(config: AppConfig, theme: string): Record<string, string> {\n const themeConfig = config.theming.themes[theme];\n if (!themeConfig) return {};\n\n const vars: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(themeConfig.colors)) {\n const cssKey = `--color-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`;\n vars[cssKey] = hexToRgb(value);\n }\n\n vars['--font-sans'] = config.theming.fonts.sans;\n vars['--font-mono'] = config.theming.fonts.mono;\n vars['--radius-sm'] = config.theming.borderRadius.sm;\n vars['--radius-md'] = config.theming.borderRadius.md;\n vars['--radius-lg'] = config.theming.borderRadius.lg;\n vars['--radius-full'] = config.theming.borderRadius.full;\n\n return vars;\n}\n\n/**\n * Base CSS styles for SSR pages.\n * Include this in your HTML template for consistent styling.\n */\nexport const baseStyles = `\n html { font-family: var(--font-sans); }\n body {\n margin: 0;\n background-color: rgb(var(--color-background));\n color: rgb(var(--color-text-primary));\n }\n`;\n"],"names":[],"mappings":";AAkCA,SAAS,SAAS,KAAqB;AACrC,QAAM,SAAS,4CAA4C,KAAK,GAAG;AACnE,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,GAAG,SAAS,OAAO,CAAC,GAAI,EAAE,CAAC,IAAI,SAAS,OAAO,CAAC,GAAI,EAAE,CAAC,IAAI,SAAS,OAAO,CAAC,GAAI,EAAE,CAAC;AAC5F;AAwBO,SAAS,iBAAiB,QAAmB,OAAuB;AACzE,QAAM,cAAc,OAAO,QAAQ,OAAO,KAAK;AAC/C,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,UAAU,OAAO,QAAQ,YAAY,MAAM,EAC9C,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AACrB,UAAM,SAAS,WAAW,IAAI,QAAQ,YAAY,KAAK,EAAE,aAAa;AACtE,WAAO,GAAG,MAAM,KAAK,SAAS,KAAK,CAAC;AAAA,EACtC,CAAC,EACA,KAAK,QAAQ;AAEhB,SAAO;AAAA;AAAA,QAED,OAAO;AAAA,qBACM,OAAO,QAAQ,MAAM,IAAI;AAAA,qBACzB,OAAO,QAAQ,MAAM,IAAI;AAAA,qBACzB,OAAO,QAAQ,aAAa,EAAE;AAAA,qBAC9B,OAAO,QAAQ,aAAa,EAAE;AAAA,qBAC9B,OAAO,QAAQ,aAAa,EAAE;AAAA,uBAC5B,OAAO,QAAQ,aAAa,IAAI;AAAA;AAAA;AAGvD;AAMO,SAAS,kBAAkB,QAAmB,OAAuC;AAC1F,QAAM,cAAc,OAAO,QAAQ,OAAO,KAAK;AAC/C,MAAI,CAAC,YAAa,QAAO,CAAA;AAEzB,QAAM,OAA+B,CAAA;AAErC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,MAAM,GAAG;AAC7D,UAAM,SAAS,WAAW,IAAI,QAAQ,YAAY,KAAK,EAAE,aAAa;AACtE,SAAK,MAAM,IAAI,SAAS,KAAK;AAAA,EAC/B;AAEA,OAAK,aAAa,IAAI,OAAO,QAAQ,MAAM;AAC3C,OAAK,aAAa,IAAI,OAAO,QAAQ,MAAM;AAC3C,OAAK,aAAa,IAAI,OAAO,QAAQ,aAAa;AAClD,OAAK,aAAa,IAAI,OAAO,QAAQ,aAAa;AAClD,OAAK,aAAa,IAAI,OAAO,QAAQ,aAAa;AAClD,OAAK,eAAe,IAAI,OAAO,QAAQ,aAAa;AAEpD,SAAO;AACT;AAMO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;"}
1
+ {"version":3,"file":"ssr.js","sources":["../../src/ssr.ts"],"sourcesContent":["/**\n * SSR-safe exports for server-side rendering.\n *\n * This module exports components and utilities that can be used on the server\n * without browser APIs like window, document, or localStorage.\n */\n\nimport type { AppConfig } from '@chaaskit/shared';\n\n// SSR-safe context providers\nexport {\n ServerConfigProvider,\n useServerConfig,\n useServerConfigLoaded,\n type ServerConfigProviderProps,\n} from './contexts/ServerConfigProvider';\n\nexport {\n ServerThemeProvider,\n useServerTheme,\n type ServerThemeProviderProps,\n} from './contexts/ServerThemeProvider';\n\n// NOTE: SSRMessageList and SSRMarkdownRenderer are NOT exported here because\n// they depend on react-markdown which has browser-only dependencies.\n// Use them only in client-side code wrapped in <ClientOnly>.\n\n// ============================================\n// Theme utilities for SSR\n// ============================================\n\n/**\n * Converts a hex color to RGB values string (e.g., \"#ff0000\" -> \"255 0 0\")\n */\nfunction hexToRgb(hex: string): string {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (!result) return '';\n return `${parseInt(result[1]!, 16)} ${parseInt(result[2]!, 16)} ${parseInt(result[3]!, 16)}`;\n}\n\n/**\n * Generates CSS variables for a theme.\n * Use this in your React Router root.tsx to inject theme styles.\n *\n * @example\n * ```tsx\n * // app/root.tsx\n * import { generateThemeCSS } from '@chaaskit/client/ssr';\n *\n * export default function Root() {\n * const themeCSS = generateThemeCSS(config, 'dark');\n * return (\n * <html>\n * <head>\n * <style dangerouslySetInnerHTML={{ __html: themeCSS }} />\n * </head>\n * ...\n * </html>\n * );\n * }\n * ```\n */\nexport function generateThemeCSS(config: AppConfig, theme: string): string {\n const themeConfig = config.theming.themes[theme];\n if (!themeConfig) return '';\n\n const cssVars = Object.entries(themeConfig.colors)\n .map(([key, value]) => {\n const cssKey = `--color-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`;\n return `${cssKey}: ${hexToRgb(value)};`;\n })\n .join('\\n ');\n\n return `\n :root {\n ${cssVars}\n --font-sans: ${config.theming.fonts.sans};\n --font-mono: ${config.theming.fonts.mono};\n --radius-sm: ${config.theming.borderRadius.sm};\n --radius-md: ${config.theming.borderRadius.md};\n --radius-lg: ${config.theming.borderRadius.lg};\n --radius-full: ${config.theming.borderRadius.full};\n }\n `;\n}\n\n/**\n * Returns an object of CSS variable name -> value pairs for a theme.\n * Useful if you need programmatic access to theme values.\n */\nexport function getThemeVariables(config: AppConfig, theme: string): Record<string, string> {\n const themeConfig = config.theming.themes[theme];\n if (!themeConfig) return {};\n\n const vars: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(themeConfig.colors)) {\n const cssKey = `--color-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`;\n vars[cssKey] = hexToRgb(value);\n }\n\n vars['--font-sans'] = config.theming.fonts.sans;\n vars['--font-mono'] = config.theming.fonts.mono;\n vars['--radius-sm'] = config.theming.borderRadius.sm;\n vars['--radius-md'] = config.theming.borderRadius.md;\n vars['--radius-lg'] = config.theming.borderRadius.lg;\n vars['--radius-full'] = config.theming.borderRadius.full;\n\n return vars;\n}\n\n/**\n * Generates CSS for ALL themes using html[data-theme] selectors.\n * This is the recommended way to include theme styles - it allows instant\n * theme switching by just changing the data-theme attribute on <html>.\n *\n * @example\n * ```tsx\n * // app/root.tsx\n * import { generateAllThemesCSS, baseStyles } from '@chaaskit/client/ssr';\n *\n * // Cache on server - only generate once\n * let cachedCSS: string | null = null;\n *\n * export async function loader() {\n * if (!cachedCSS) cachedCSS = generateAllThemesCSS(config);\n * return { themeCSS: cachedCSS };\n * }\n *\n * export default function Root() {\n * const { themeCSS } = useLoaderData();\n * return (\n * <html data-theme=\"dark\">\n * <head>\n * <style dangerouslySetInnerHTML={{ __html: themeCSS + baseStyles }} />\n * </head>\n * ...\n * </html>\n * );\n * }\n * ```\n */\nexport function generateAllThemesCSS(config: AppConfig): string {\n const themes = config.theming.themes;\n let css = '';\n\n for (const [themeName, themeConfig] of Object.entries(themes)) {\n const cssVars = Object.entries(themeConfig.colors)\n .map(([key, value]) => {\n const cssKey = `--color-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`;\n return `${cssKey}: ${hexToRgb(value)};`;\n })\n .join('\\n ');\n\n css += `\n html[data-theme=\"${themeName}\"] {\n ${cssVars}\n --font-sans: ${config.theming.fonts.sans};\n --font-mono: ${config.theming.fonts.mono};\n --radius-sm: ${config.theming.borderRadius.sm};\n --radius-md: ${config.theming.borderRadius.md};\n --radius-lg: ${config.theming.borderRadius.lg};\n --radius-full: ${config.theming.borderRadius.full};\n }\n`;\n }\n\n return css;\n}\n\n/**\n * Base CSS styles for SSR pages.\n * Include this in your HTML template for consistent styling.\n */\nexport const baseStyles = `\n html { font-family: var(--font-sans); }\n body {\n margin: 0;\n background-color: rgb(var(--color-background));\n color: rgb(var(--color-text-primary));\n }\n`;\n"],"names":[],"mappings":";AAkCA,SAAS,SAAS,KAAqB;AACrC,QAAM,SAAS,4CAA4C,KAAK,GAAG;AACnE,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,GAAG,SAAS,OAAO,CAAC,GAAI,EAAE,CAAC,IAAI,SAAS,OAAO,CAAC,GAAI,EAAE,CAAC,IAAI,SAAS,OAAO,CAAC,GAAI,EAAE,CAAC;AAC5F;AAwBO,SAAS,iBAAiB,QAAmB,OAAuB;AACzE,QAAM,cAAc,OAAO,QAAQ,OAAO,KAAK;AAC/C,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,UAAU,OAAO,QAAQ,YAAY,MAAM,EAC9C,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AACrB,UAAM,SAAS,WAAW,IAAI,QAAQ,YAAY,KAAK,EAAE,aAAa;AACtE,WAAO,GAAG,MAAM,KAAK,SAAS,KAAK,CAAC;AAAA,EACtC,CAAC,EACA,KAAK,QAAQ;AAEhB,SAAO;AAAA;AAAA,QAED,OAAO;AAAA,qBACM,OAAO,QAAQ,MAAM,IAAI;AAAA,qBACzB,OAAO,QAAQ,MAAM,IAAI;AAAA,qBACzB,OAAO,QAAQ,aAAa,EAAE;AAAA,qBAC9B,OAAO,QAAQ,aAAa,EAAE;AAAA,qBAC9B,OAAO,QAAQ,aAAa,EAAE;AAAA,uBAC5B,OAAO,QAAQ,aAAa,IAAI;AAAA;AAAA;AAGvD;AAMO,SAAS,kBAAkB,QAAmB,OAAuC;AAC1F,QAAM,cAAc,OAAO,QAAQ,OAAO,KAAK;AAC/C,MAAI,CAAC,YAAa,QAAO,CAAA;AAEzB,QAAM,OAA+B,CAAA;AAErC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,MAAM,GAAG;AAC7D,UAAM,SAAS,WAAW,IAAI,QAAQ,YAAY,KAAK,EAAE,aAAa;AACtE,SAAK,MAAM,IAAI,SAAS,KAAK;AAAA,EAC/B;AAEA,OAAK,aAAa,IAAI,OAAO,QAAQ,MAAM;AAC3C,OAAK,aAAa,IAAI,OAAO,QAAQ,MAAM;AAC3C,OAAK,aAAa,IAAI,OAAO,QAAQ,aAAa;AAClD,OAAK,aAAa,IAAI,OAAO,QAAQ,aAAa;AAClD,OAAK,aAAa,IAAI,OAAO,QAAQ,aAAa;AAClD,OAAK,eAAe,IAAI,OAAO,QAAQ,aAAa;AAEpD,SAAO;AACT;AAiCO,SAAS,qBAAqB,QAA2B;AAC9D,QAAM,SAAS,OAAO,QAAQ;AAC9B,MAAI,MAAM;AAEV,aAAW,CAAC,WAAW,WAAW,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC7D,UAAM,UAAU,OAAO,QAAQ,YAAY,MAAM,EAC9C,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AACrB,YAAM,SAAS,WAAW,IAAI,QAAQ,YAAY,KAAK,EAAE,aAAa;AACtE,aAAO,GAAG,MAAM,KAAK,SAAS,KAAK,CAAC;AAAA,IACtC,CAAC,EACA,KAAK,UAAU;AAElB,WAAO;AAAA,uBACY,SAAS;AAAA,QACxB,OAAO;AAAA,qBACM,OAAO,QAAQ,MAAM,IAAI;AAAA,qBACzB,OAAO,QAAQ,MAAM,IAAI;AAAA,qBACzB,OAAO,QAAQ,aAAa,EAAE;AAAA,qBAC9B,OAAO,QAAQ,aAAa,EAAE;AAAA,qBAC9B,OAAO,QAAQ,aAAa,EAAE;AAAA,uBAC5B,OAAO,QAAQ,aAAa,IAAI;AAAA;AAAA;AAAA,EAGrD;AAEA,SAAO;AACT;AAMO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;"}
@@ -801,6 +801,9 @@ video {
801
801
  .min-w-0 {
802
802
  min-width: 0px;
803
803
  }
804
+ .min-w-\[160px\] {
805
+ min-width: 160px;
806
+ }
804
807
  .min-w-\[200px\] {
805
808
  min-width: 200px;
806
809
  }
@@ -1179,6 +1182,9 @@ video {
1179
1182
  .border-purple-500\/20 {
1180
1183
  border-color: rgb(168 85 247 / 0.2);
1181
1184
  }
1185
+ .border-success\/30 {
1186
+ border-color: rgb(var(--color-success) / 0.3);
1187
+ }
1182
1188
  .border-success\/50 {
1183
1189
  border-color: rgb(var(--color-success) / 0.5);
1184
1190
  }
@@ -1219,6 +1225,9 @@ video {
1219
1225
  .bg-background-secondary\/30 {
1220
1226
  background-color: rgb(var(--color-background-secondary) / 0.3);
1221
1227
  }
1228
+ .bg-background-secondary\/40 {
1229
+ background-color: rgb(var(--color-background-secondary) / 0.4);
1230
+ }
1222
1231
  .bg-background-secondary\/50 {
1223
1232
  background-color: rgb(var(--color-background-secondary) / 0.5);
1224
1233
  }
@@ -1615,6 +1624,9 @@ video {
1615
1624
  --tw-text-opacity: 1;
1616
1625
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1617
1626
  }
1627
+ .underline {
1628
+ text-decoration-line: underline;
1629
+ }
1618
1630
  .placeholder-text-muted::-moz-placeholder {
1619
1631
  --tw-placeholder-opacity: 1;
1620
1632
  color: rgb(var(--color-text-muted) / var(--tw-placeholder-opacity, 1));
@@ -1684,70 +1696,26 @@ video {
1684
1696
  .ease-in-out {
1685
1697
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1686
1698
  }
1687
-
1688
- /* Default theme variables (Light) */
1689
- :root {
1690
- --color-primary: 99 102 241;
1691
- --color-primary-hover: 79 70 229;
1692
- --color-secondary: 139 92 246;
1693
-
1694
- --color-background: 255 255 255;
1695
- --color-background-secondary: 249 250 251;
1696
- --color-sidebar: 243 244 246;
1697
-
1698
- --color-text-primary: 17 24 39;
1699
- --color-text-secondary: 107 114 128;
1700
- --color-text-muted: 156 163 175;
1701
-
1702
- --color-border: 229 231 235;
1703
- --color-input-background: 255 255 255;
1704
- --color-input-border: 209 213 219;
1705
-
1706
- --color-user-message-bg: 99 102 241;
1707
- --color-user-message-text: 255 255 255;
1708
- --color-assistant-message-bg: 243 244 246;
1709
- --color-assistant-message-text: 17 24 39;
1710
-
1711
- --color-success: 16 185 129;
1712
- --color-warning: 245 158 11;
1713
- --color-error: 239 68 68;
1714
-
1715
- --font-sans: 'Inter', system-ui, sans-serif;
1716
- --font-mono: 'JetBrains Mono', Menlo, monospace;
1717
-
1718
- --radius-sm: 0.25rem;
1719
- --radius-md: 0.5rem;
1720
- --radius-lg: 0.75rem;
1721
- --radius-full: 9999px;
1699
+ .ease-out {
1700
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
1722
1701
  }
1723
1702
 
1724
- /* Dark theme */
1725
- [data-theme="dark"] {
1726
- --color-primary: 129 140 248;
1727
- --color-primary-hover: 165 180 252;
1728
- --color-secondary: 167 139 250;
1729
-
1730
- --color-background: 17 24 39;
1731
- --color-background-secondary: 31 41 55;
1732
- --color-sidebar: 15 23 42;
1733
-
1734
- --color-text-primary: 249 250 251;
1735
- --color-text-secondary: 209 213 219;
1736
- --color-text-muted: 107 114 128;
1737
-
1738
- --color-border: 55 65 81;
1739
- --color-input-background: 31 41 55;
1740
- --color-input-border: 75 85 99;
1741
-
1742
- --color-user-message-bg: 79 70 229;
1743
- --color-user-message-text: 255 255 255;
1744
- --color-assistant-message-bg: 31 41 55;
1745
- --color-assistant-message-text: 249 250 251;
1746
-
1747
- --color-success: 52 211 153;
1748
- --color-warning: 251 191 36;
1749
- --color-error: 248 113 113;
1750
- }
1703
+ /*
1704
+ * Theme CSS variables are NOT defined here.
1705
+ * The consuming app (chaaskit-app) is responsible for providing CSS variable values
1706
+ * via inline styles on <html> from config/app.config.ts.
1707
+ *
1708
+ * Required variables:
1709
+ * --color-primary, --color-primary-hover, --color-secondary
1710
+ * --color-background, --color-background-secondary, --color-sidebar
1711
+ * --color-text-primary, --color-text-secondary, --color-text-muted
1712
+ * --color-border, --color-input-background, --color-input-border
1713
+ * --color-user-message-bg, --color-user-message-text
1714
+ * --color-assistant-message-bg, --color-assistant-message-text
1715
+ * --color-success, --color-warning, --color-error
1716
+ * --font-sans, --font-mono
1717
+ * --radius-sm, --radius-md, --radius-lg, --radius-full
1718
+ */
1751
1719
 
1752
1720
  /* Base styles */
1753
1721
  html {
@@ -2327,6 +2295,18 @@ kbd {
2327
2295
  }
2328
2296
  @media (min-width: 768px) {
2329
2297
 
2298
+ .md\:col-span-2 {
2299
+ grid-column: span 2 / span 2;
2300
+ }
2301
+
2302
+ .md\:col-span-3 {
2303
+ grid-column: span 3 / span 3;
2304
+ }
2305
+
2306
+ .md\:col-span-4 {
2307
+ grid-column: span 4 / span 4;
2308
+ }
2309
+
2330
2310
  .md\:block {
2331
2311
  display: block;
2332
2312
  }
@@ -2339,6 +2319,10 @@ kbd {
2339
2319
  display: none;
2340
2320
  }
2341
2321
 
2322
+ .md\:grid-cols-12 {
2323
+ grid-template-columns: repeat(12, minmax(0, 1fr));
2324
+ }
2325
+
2342
2326
  .md\:grid-cols-2 {
2343
2327
  grid-template-columns: repeat(2, minmax(0, 1fr));
2344
2328
  }
@@ -2354,6 +2338,10 @@ kbd {
2354
2338
  .md\:justify-between {
2355
2339
  justify-content: space-between;
2356
2340
  }
2341
+
2342
+ .md\:gap-4 {
2343
+ gap: 1rem;
2344
+ }
2357
2345
  }
2358
2346
  @media (min-width: 1024px) {
2359
2347
 
@@ -2361,6 +2349,14 @@ kbd {
2361
2349
  position: relative;
2362
2350
  }
2363
2351
 
2352
+ .lg\:col-span-1 {
2353
+ grid-column: span 1 / span 1;
2354
+ }
2355
+
2356
+ .lg\:col-span-2 {
2357
+ grid-column: span 2 / span 2;
2358
+ }
2359
+
2364
2360
  .lg\:hidden {
2365
2361
  display: none;
2366
2362
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useExtensions-B5nX_8XD.js","sources":["../../src/extensions/registry.ts","../../src/extensions/useExtensions.ts"],"sourcesContent":["import type { ComponentType } from 'react';\n\n/**\n * Page extension configuration\n */\nexport interface PageExtension {\n /** Unique identifier for the page */\n id: string;\n /** URL path for the page (e.g., \"/analytics\") */\n path: string;\n /** Display label for navigation */\n label: string;\n /** Optional icon name from lucide-react */\n icon?: string;\n /** React component to render for this page */\n component: ComponentType;\n /** Whether to show in the sidebar navigation */\n showInSidebar?: boolean;\n /** Whether this page requires authentication */\n requiresAuth?: boolean;\n /** Whether this page requires admin access */\n requiresAdmin?: boolean;\n}\n\n/**\n * Tool result renderer extension\n */\nexport interface ToolExtension {\n /** Tool name to match */\n name: string;\n /** Description of the tool */\n description: string;\n /** Custom renderer for tool results */\n resultRenderer?: ComponentType<{ result: unknown }>;\n}\n\n/**\n * Component override extension\n */\nexport interface ComponentOverride {\n /** Component slot to override */\n slot: 'header' | 'footer' | 'sidebar-header' | 'sidebar-footer' | 'message-actions';\n /** Component to render in the slot */\n component: ComponentType<Record<string, unknown>>;\n}\n\n/**\n * Client-side extension registry for customizing the ChaasKit UI\n *\n * Note: getPages(), getSidebarPages(), and getTools() cache their results\n * to support React's useSyncExternalStore which requires stable references.\n */\nclass ClientExtensionRegistry {\n private pages: Map<string, PageExtension> = new Map();\n private tools: Map<string, ToolExtension> = new Map();\n private overrides: Map<string, ComponentOverride> = new Map();\n private listeners: Set<() => void> = new Set();\n\n // Cached arrays for useSyncExternalStore compatibility\n // These are only updated when the underlying data changes\n private cachedPages: PageExtension[] = [];\n private cachedSidebarPages: PageExtension[] = [];\n private cachedTools: ToolExtension[] = [];\n\n /**\n * Register a custom page\n */\n registerPage(page: PageExtension): void {\n this.pages.set(page.id, page);\n this.invalidateCaches();\n this.notifyListeners();\n }\n\n /**\n * Unregister a custom page\n */\n unregisterPage(id: string): boolean {\n const result = this.pages.delete(id);\n if (result) {\n this.invalidateCaches();\n this.notifyListeners();\n }\n return result;\n }\n\n /**\n * Get all registered pages\n * Returns a cached array for useSyncExternalStore compatibility\n */\n getPages(): PageExtension[] {\n return this.cachedPages;\n }\n\n /**\n * Get pages that should appear in the sidebar\n * Returns a cached array for useSyncExternalStore compatibility\n */\n getSidebarPages(): PageExtension[] {\n return this.cachedSidebarPages;\n }\n\n /**\n * Register a custom tool renderer\n */\n registerTool(tool: ToolExtension): void {\n this.tools.set(tool.name, tool);\n this.invalidateCaches();\n this.notifyListeners();\n }\n\n /**\n * Unregister a tool renderer\n */\n unregisterTool(name: string): boolean {\n const result = this.tools.delete(name);\n if (result) {\n this.invalidateCaches();\n this.notifyListeners();\n }\n return result;\n }\n\n /**\n * Get all registered tools\n * Returns a cached array for useSyncExternalStore compatibility\n */\n getTools(): ToolExtension[] {\n return this.cachedTools;\n }\n\n /**\n * Get a specific tool by name\n */\n getTool(name: string): ToolExtension | undefined {\n return this.tools.get(name);\n }\n\n /**\n * Register a component override\n */\n registerOverride(override: ComponentOverride): void {\n this.overrides.set(override.slot, override);\n this.notifyListeners();\n }\n\n /**\n * Get a component override for a slot\n */\n getOverride(slot: string): ComponentOverride | undefined {\n return this.overrides.get(slot);\n }\n\n /**\n * Subscribe to registry changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private invalidateCaches(): void {\n this.cachedPages = Array.from(this.pages.values());\n this.cachedSidebarPages = this.cachedPages.filter(p => p.showInSidebar);\n this.cachedTools = Array.from(this.tools.values());\n }\n\n private notifyListeners(): void {\n this.listeners.forEach(listener => listener());\n }\n\n /**\n * Clear all registered extensions\n */\n clear(): void {\n this.pages.clear();\n this.tools.clear();\n this.overrides.clear();\n this.invalidateCaches();\n this.notifyListeners();\n }\n}\n\n// Singleton instance\nexport const clientRegistry = new ClientExtensionRegistry();\n\n// Export for use in user extensions\nexport default clientRegistry;\n","import { useSyncExternalStore, useCallback } from 'react';\nimport { clientRegistry, type PageExtension, type ToolExtension, type ComponentOverride } from './registry';\n\n/**\n * Hook to access extension pages with automatic updates\n */\nexport function useExtensionPages(): PageExtension[] {\n const getSnapshot = useCallback(() => clientRegistry.getPages(), []);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to access sidebar pages with automatic updates\n */\nexport function useSidebarPages(): PageExtension[] {\n const getSnapshot = useCallback(() => clientRegistry.getSidebarPages(), []);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to access extension tools with automatic updates\n */\nexport function useExtensionTools(): ToolExtension[] {\n const getSnapshot = useCallback(() => clientRegistry.getTools(), []);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to get a specific tool renderer\n */\nexport function useToolRenderer(toolName: string): ToolExtension | undefined {\n const getSnapshot = useCallback(() => clientRegistry.getTool(toolName), [toolName]);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to get a component override\n */\nexport function useComponentOverride(slot: string): ComponentOverride | undefined {\n const getSnapshot = useCallback(() => clientRegistry.getOverride(slot), [slot]);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useExtensions-B5nX_8XD.js","sources":["../../src/extensions/registry.ts","../../src/extensions/useExtensions.ts"],"sourcesContent":["import type { ComponentType } from 'react';\nimport type { ToolCall, ToolResult } from '@chaaskit/shared';\n\n/**\n * Page extension configuration\n */\nexport interface PageExtension {\n /** Unique identifier for the page */\n id: string;\n /** URL path for the page (e.g., \"/analytics\") */\n path: string;\n /** Display label for navigation */\n label: string;\n /** Optional icon name from lucide-react */\n icon?: string;\n /** React component to render for this page */\n component: ComponentType;\n /** Whether to show in the sidebar navigation */\n showInSidebar?: boolean;\n /** Whether this page requires authentication */\n requiresAuth?: boolean;\n /** Whether this page requires admin access */\n requiresAdmin?: boolean;\n}\n\n/**\n * Tool result renderer extension\n */\nexport interface ToolExtension {\n /** Tool name to match */\n name: string;\n /** Description of the tool */\n description: string;\n /** Custom renderer for tool results */\n resultRenderer?: ComponentType<{ toolCall: ToolCall; toolResult: ToolResult }>;\n}\n\n/**\n * Component override extension\n */\nexport interface ComponentOverride {\n /** Component slot to override */\n slot: 'header' | 'footer' | 'sidebar-header' | 'sidebar-footer' | 'message-actions';\n /** Component to render in the slot */\n component: ComponentType<Record<string, unknown>>;\n}\n\n/**\n * Client-side extension registry for customizing the ChaasKit UI\n *\n * Note: getPages(), getSidebarPages(), and getTools() cache their results\n * to support React's useSyncExternalStore which requires stable references.\n */\nclass ClientExtensionRegistry {\n private pages: Map<string, PageExtension> = new Map();\n private tools: Map<string, ToolExtension> = new Map();\n private overrides: Map<string, ComponentOverride> = new Map();\n private listeners: Set<() => void> = new Set();\n\n // Cached arrays for useSyncExternalStore compatibility\n // These are only updated when the underlying data changes\n private cachedPages: PageExtension[] = [];\n private cachedSidebarPages: PageExtension[] = [];\n private cachedTools: ToolExtension[] = [];\n\n /**\n * Register a custom page\n */\n registerPage(page: PageExtension): void {\n this.pages.set(page.id, page);\n this.invalidateCaches();\n this.notifyListeners();\n }\n\n /**\n * Unregister a custom page\n */\n unregisterPage(id: string): boolean {\n const result = this.pages.delete(id);\n if (result) {\n this.invalidateCaches();\n this.notifyListeners();\n }\n return result;\n }\n\n /**\n * Get all registered pages\n * Returns a cached array for useSyncExternalStore compatibility\n */\n getPages(): PageExtension[] {\n return this.cachedPages;\n }\n\n /**\n * Get pages that should appear in the sidebar\n * Returns a cached array for useSyncExternalStore compatibility\n */\n getSidebarPages(): PageExtension[] {\n return this.cachedSidebarPages;\n }\n\n /**\n * Register a custom tool renderer\n */\n registerTool(tool: ToolExtension): void {\n this.tools.set(tool.name, tool);\n this.invalidateCaches();\n this.notifyListeners();\n }\n\n /**\n * Unregister a tool renderer\n */\n unregisterTool(name: string): boolean {\n const result = this.tools.delete(name);\n if (result) {\n this.invalidateCaches();\n this.notifyListeners();\n }\n return result;\n }\n\n /**\n * Get all registered tools\n * Returns a cached array for useSyncExternalStore compatibility\n */\n getTools(): ToolExtension[] {\n return this.cachedTools;\n }\n\n /**\n * Get a specific tool by name\n */\n getTool(name: string): ToolExtension | undefined {\n return this.tools.get(name);\n }\n\n /**\n * Register a component override\n */\n registerOverride(override: ComponentOverride): void {\n this.overrides.set(override.slot, override);\n this.notifyListeners();\n }\n\n /**\n * Get a component override for a slot\n */\n getOverride(slot: string): ComponentOverride | undefined {\n return this.overrides.get(slot);\n }\n\n /**\n * Subscribe to registry changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private invalidateCaches(): void {\n this.cachedPages = Array.from(this.pages.values());\n this.cachedSidebarPages = this.cachedPages.filter(p => p.showInSidebar);\n this.cachedTools = Array.from(this.tools.values());\n }\n\n private notifyListeners(): void {\n this.listeners.forEach(listener => listener());\n }\n\n /**\n * Clear all registered extensions\n */\n clear(): void {\n this.pages.clear();\n this.tools.clear();\n this.overrides.clear();\n this.invalidateCaches();\n this.notifyListeners();\n }\n}\n\n// Singleton instance\nexport const clientRegistry = new ClientExtensionRegistry();\n\n// Export for use in user extensions\nexport default clientRegistry;\n","import { useSyncExternalStore, useCallback } from 'react';\nimport { clientRegistry, type PageExtension, type ToolExtension, type ComponentOverride } from './registry';\n\n/**\n * Hook to access extension pages with automatic updates\n */\nexport function useExtensionPages(): PageExtension[] {\n const getSnapshot = useCallback(() => clientRegistry.getPages(), []);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to access sidebar pages with automatic updates\n */\nexport function useSidebarPages(): PageExtension[] {\n const getSnapshot = useCallback(() => clientRegistry.getSidebarPages(), []);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to access extension tools with automatic updates\n */\nexport function useExtensionTools(): ToolExtension[] {\n const getSnapshot = useCallback(() => clientRegistry.getTools(), []);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to get a specific tool renderer\n */\nexport function useToolRenderer(toolName: string): ToolExtension | undefined {\n const getSnapshot = useCallback(() => clientRegistry.getTool(toolName), [toolName]);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Hook to get a component override\n */\nexport function useComponentOverride(slot: string): ComponentOverride | undefined {\n const getSnapshot = useCallback(() => clientRegistry.getOverride(slot), [slot]);\n const subscribe = useCallback((callback: () => void) => clientRegistry.subscribe(callback), []);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaaskit/client",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React frontend client for ChaasKit AI chat applications",
5
5
  "license": "MIT",
6
6
  "author": "Matt Ferrante <@ferrants>",
@@ -13,7 +13,15 @@
13
13
  "bugs": {
14
14
  "url": "https://github.com/ferrants/chaaskit/issues"
15
15
  },
16
- "keywords": ["chaaskit", "ai", "chat", "saas", "client", "react", "vite"],
16
+ "keywords": [
17
+ "chaaskit",
18
+ "ai",
19
+ "chat",
20
+ "saas",
21
+ "client",
22
+ "react",
23
+ "vite"
24
+ ],
17
25
  "type": "module",
18
26
  "exports": {
19
27
  ".": {
@@ -41,22 +49,19 @@
41
49
  "import": "./dist/lib/routes/*.js"
42
50
  },
43
51
  "./styles": "./dist/lib/styles.css",
52
+ "./tailwind-preset": {
53
+ "types": "./src/tailwind-preset.js",
54
+ "import": "./src/tailwind-preset.js",
55
+ "require": "./src/tailwind-preset.js"
56
+ },
44
57
  "./src/*": "./src/*"
45
58
  },
46
59
  "files": [
47
60
  "dist",
48
61
  "src"
49
62
  ],
50
- "scripts": {
51
- "dev": "vite --host",
52
- "build": "tsc && vite build --config vite.config.lib.ts",
53
- "build:lib": "vite build --config vite.config.lib.ts",
54
- "preview": "vite preview",
55
- "typecheck": "tsc --noEmit",
56
- "clean": "rm -rf dist"
57
- },
58
63
  "dependencies": {
59
- "@chaaskit/shared": "^0.1.0",
64
+ "@chaaskit/shared": "^0.1.2",
60
65
  "lucide-react": "^0.321.0",
61
66
  "react": "^18.2.0",
62
67
  "react-dom": "^18.2.0",
@@ -80,5 +85,13 @@
80
85
  "react": "^18.2.0",
81
86
  "react-dom": "^18.2.0",
82
87
  "react-router": "^7.0.0"
88
+ },
89
+ "scripts": {
90
+ "dev": "vite --host",
91
+ "build": "tsc && vite build --config vite.config.lib.ts",
92
+ "build:lib": "vite build --config vite.config.lib.ts",
93
+ "preview": "vite preview",
94
+ "typecheck": "tsc --noEmit",
95
+ "clean": "rm -rf dist"
83
96
  }
84
- }
97
+ }
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useMemo, useState } from 'react';
2
2
  import { Copy, Check, RefreshCw, ThumbsUp, ThumbsDown, User, Bot, GitBranch } from 'lucide-react';
3
3
  import { useNavigate } from 'react-router';
4
4
  import type { Message } from '@chaaskit/shared';
@@ -6,6 +6,7 @@ import { useChatStore } from '../stores/chatStore';
6
6
  import { useTheme } from '../contexts/ThemeContext';
7
7
  import { useConfig } from '../contexts/ConfigContext';
8
8
  import { useAppPath } from '../hooks/useAppPath';
9
+ import { useExtensionTools } from '../extensions/useExtensions';
9
10
  import MarkdownRenderer from './content/MarkdownRenderer';
10
11
  import ToolCallDisplay, { UIResourceWidget } from './ToolCallDisplay';
11
12
  import BranchModal from './BranchModal';
@@ -27,6 +28,12 @@ export default function MessageItem({ message, isStreaming, messageIndex = 0, pr
27
28
  const config = useConfig();
28
29
  const navigate = useNavigate();
29
30
  const appPath = useAppPath();
31
+ const extensionTools = useExtensionTools();
32
+ const toolRendererMap = useMemo(() => {
33
+ const map = new Map<string, typeof extensionTools[number]>();
34
+ extensionTools.forEach((tool) => map.set(tool.name, tool));
35
+ return map;
36
+ }, [extensionTools]);
30
37
 
31
38
  const isUser = message.role === 'user';
32
39
  const showToolCalls = config.mcp?.showToolCalls !== false;
@@ -88,9 +95,22 @@ export default function MessageItem({ message, isStreaming, messageIndex = 0, pr
88
95
  toolResult: message.toolResults?.find((r) => r.toolCallId === toolCall.id),
89
96
  })) || [];
90
97
 
98
+ const renderedToolCalls = toolCallsWithResults
99
+ .filter(({ toolCall, toolResult }) =>
100
+ toolCall.serverId === 'native' &&
101
+ !!toolResult &&
102
+ !!toolRendererMap.get(toolCall.toolName)?.resultRenderer
103
+ )
104
+ .map(({ toolCall, toolResult }) => {
105
+ const renderer = toolRendererMap.get(toolCall.toolName)!.resultRenderer!;
106
+ return { toolCall, toolResult: toolResult!, Renderer: renderer };
107
+ });
108
+
109
+ const renderedToolCallIds = new Set(renderedToolCalls.map((entry) => entry.toolCall.id));
110
+
91
111
  // Check if any tool has a UI resource to render
92
112
  const uiResources = toolCallsWithResults
93
- .filter((tc) => tc.toolResult?.uiResource?.text)
113
+ .filter((tc) => tc.toolResult?.uiResource?.text && !renderedToolCallIds.has(tc.toolCall.id))
94
114
  .map((tc) => tc.toolResult!.uiResource!);
95
115
 
96
116
  // Debug logging
@@ -188,7 +208,18 @@ export default function MessageItem({ message, isStreaming, messageIndex = 0, pr
188
208
  </div>
189
209
  )}
190
210
 
191
- {/* 2. UI Resource Widgets (outside bubble, full width) */}
211
+ {/* 2. Tool Renderers (native tools) */}
212
+ {renderedToolCalls.length > 0 && (
213
+ <div className="space-y-3">
214
+ {renderedToolCalls.map(({ toolCall, toolResult, Renderer }) => (
215
+ <div key={toolCall.id} className="rounded-lg border border-border bg-background-secondary/40 p-3">
216
+ <Renderer toolCall={toolCall} toolResult={toolResult} />
217
+ </div>
218
+ ))}
219
+ </div>
220
+ )}
221
+
222
+ {/* 3. UI Resource Widgets (outside bubble, full width) */}
192
223
  {uiResources.length > 0 && (
193
224
  <div className="space-y-3">
194
225
  {uiResources.map((uiResource, index) => (
@@ -197,7 +228,7 @@ export default function MessageItem({ message, isStreaming, messageIndex = 0, pr
197
228
  </div>
198
229
  )}
199
230
 
200
- {/* 3. Text Response Bubble (with avatar) */}
231
+ {/* 4. Text Response Bubble (with avatar) */}
201
232
  {(message.content || isStreaming) && (
202
233
  <div className="group flex gap-3">
203
234
  {/* Avatar */}
@@ -1,8 +1,9 @@
1
- import { useRef, useEffect } from 'react';
2
- import type { Message, MCPContent, UIResource } from '@chaaskit/shared';
1
+ import { useMemo, useRef, useEffect } from 'react';
2
+ import type { Message, MCPContent, UIResource, ToolCall, ToolResult } from '@chaaskit/shared';
3
3
  import { Bot } from 'lucide-react';
4
4
  import { useTheme } from '../contexts/ThemeContext';
5
5
  import { useConfig } from '../contexts/ConfigContext';
6
+ import { useExtensionTools } from '../extensions/useExtensions';
6
7
  import MessageItem from './MessageItem';
7
8
  import ToolCallDisplay, { UIResourceWidget } from './ToolCallDisplay';
8
9
 
@@ -17,6 +18,7 @@ interface CompletedToolCall extends PendingToolCall {
17
18
  result: MCPContent[];
18
19
  isError?: boolean;
19
20
  uiResource?: UIResource;
21
+ structuredContent?: Record<string, unknown>;
20
22
  }
21
23
 
22
24
  interface MessageListProps {
@@ -35,6 +37,12 @@ export default function MessageList({
35
37
  const bottomRef = useRef<HTMLDivElement>(null);
36
38
  const { theme } = useTheme();
37
39
  const config = useConfig();
40
+ const extensionTools = useExtensionTools();
41
+ const toolRendererMap = useMemo(() => {
42
+ const map = new Map<string, typeof extensionTools[number]>();
43
+ extensionTools.forEach((tool) => map.set(tool.name, tool));
44
+ return map;
45
+ }, [extensionTools]);
38
46
  const showToolCalls = config.mcp?.showToolCalls !== false;
39
47
 
40
48
  useEffect(() => {
@@ -45,8 +53,34 @@ export default function MessageList({
45
53
  const isStreaming = Boolean(streamingContent) || hasToolActivity;
46
54
 
47
55
  // Get UI resources from completed tool calls for separate rendering
56
+ const renderedToolCalls = completedToolCalls
57
+ .filter((call) =>
58
+ call.serverId === 'native' &&
59
+ !!toolRendererMap.get(call.name)?.resultRenderer
60
+ )
61
+ .map((call) => {
62
+ const renderer = toolRendererMap.get(call.name)!.resultRenderer!;
63
+ const toolCall: ToolCall = {
64
+ id: call.id,
65
+ serverId: call.serverId,
66
+ toolName: call.name,
67
+ arguments: call.input,
68
+ status: call.isError ? 'error' : 'completed',
69
+ };
70
+ const toolResult: ToolResult = {
71
+ toolCallId: call.id,
72
+ content: call.result,
73
+ isError: call.isError,
74
+ uiResource: call.uiResource,
75
+ structuredContent: call.structuredContent,
76
+ };
77
+ return { toolCall, toolResult, Renderer: renderer };
78
+ });
79
+
80
+ const renderedToolCallIds = new Set(renderedToolCalls.map((entry) => entry.toolCall.id));
81
+
48
82
  const uiResources = completedToolCalls
49
- .filter((tc) => tc.uiResource?.text)
83
+ .filter((tc) => tc.uiResource?.text && !renderedToolCallIds.has(tc.id))
50
84
  .map((tc) => tc.uiResource!);
51
85
 
52
86
  // Debug logging
@@ -96,6 +130,7 @@ export default function MessageList({
96
130
  toolCallId: call.id,
97
131
  content: call.result,
98
132
  isError: call.isError,
133
+ structuredContent: call.structuredContent,
99
134
  }}
100
135
  hideUiResource
101
136
  />
@@ -118,7 +153,18 @@ export default function MessageList({
118
153
  </div>
119
154
  )}
120
155
 
121
- {/* 2. UI Resource Widgets (outside bubble, full width) */}
156
+ {/* 2. Tool Renderers (native tools) */}
157
+ {renderedToolCalls.length > 0 && (
158
+ <div className="space-y-3">
159
+ {renderedToolCalls.map(({ toolCall, toolResult, Renderer }) => (
160
+ <div key={toolCall.id} className="rounded-lg border border-border bg-background-secondary/40 p-3">
161
+ <Renderer toolCall={toolCall} toolResult={toolResult} />
162
+ </div>
163
+ ))}
164
+ </div>
165
+ )}
166
+
167
+ {/* 3. UI Resource Widgets (outside bubble, full width) */}
122
168
  {uiResources.length > 0 && (
123
169
  <div className="space-y-3">
124
170
  {uiResources.map((uiResource, index) => (
@@ -127,7 +173,7 @@ export default function MessageList({
127
173
  </div>
128
174
  )}
129
175
 
130
- {/* 3. Text Response Bubble (with avatar) */}
176
+ {/* 4. Text Response Bubble (with avatar) */}
131
177
  {streamingContent && (
132
178
  <div className="group flex gap-3">
133
179
  {/* Avatar */}
@@ -18,7 +18,7 @@ export default function OAuthAppsSection() {
18
18
  const [revokingId, setRevokingId] = useState<string | null>(null);
19
19
 
20
20
  // Check if OAuth is enabled
21
- const oauthEnabled = config.mcp?.server?.oauth?.enabled;
21
+ const oauthEnabled = config.mcp?.servers?.some((server) => server.authMode === 'user-oauth');
22
22
 
23
23
  useEffect(() => {
24
24
  if (oauthEnabled) {
@@ -373,9 +373,7 @@ export default function Sidebar({ onClose, onOpenSearch }: SidebarProps) {
373
373
  )}
374
374
 
375
375
  {/* Admin Dashboard - show for site admins (config or database flag) */}
376
- {(user?.isAdmin || config.admin?.emails?.some(
377
- (email: string) => email.toLowerCase() === user?.email?.toLowerCase()
378
- )) && (
376
+ {(user?.isAdmin || config.auth?.isAdmin) && (
379
377
  <Link
380
378
  to={appPath('/admin')}
381
379
  className="mb-1 flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-xs text-text-secondary hover:bg-background-secondary hover:text-text-primary active:bg-background-secondary"