@drewswiredin/backstage-plugin-assistants 0.1.0
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.
- package/LICENSE +201 -0
- package/README.md +113 -0
- package/dist/alpha.d.ts +64 -0
- package/dist/alpha.esm.js +35 -0
- package/dist/alpha.esm.js.map +1 -0
- package/dist/api/AssistantsApi.esm.js +55 -0
- package/dist/api/AssistantsApi.esm.js.map +1 -0
- package/dist/collapsible/AssistantDetailDialog.esm.js +209 -0
- package/dist/collapsible/AssistantDetailDialog.esm.js.map +1 -0
- package/dist/collapsible/AssistantsList.esm.js +119 -0
- package/dist/collapsible/AssistantsList.esm.js.map +1 -0
- package/dist/collapsible/CollapsiblePage.esm.js +724 -0
- package/dist/collapsible/CollapsiblePage.esm.js.map +1 -0
- package/dist/collapsible/ConversationsPanel.esm.js +198 -0
- package/dist/collapsible/ConversationsPanel.esm.js.map +1 -0
- package/dist/collapsible/FullHeightRegion.esm.js +54 -0
- package/dist/collapsible/FullHeightRegion.esm.js.map +1 -0
- package/dist/collapsible/SidePane.esm.js +89 -0
- package/dist/collapsible/SidePane.esm.js.map +1 -0
- package/dist/collapsible/surface/AssistantAvatar.esm.js +98 -0
- package/dist/collapsible/surface/AssistantAvatar.esm.js.map +1 -0
- package/dist/collapsible/surface/BackstageLogo.esm.js +24 -0
- package/dist/collapsible/surface/BackstageLogo.esm.js.map +1 -0
- package/dist/collapsible/surface/ConversationSurface.esm.js +268 -0
- package/dist/collapsible/surface/ConversationSurface.esm.js.map +1 -0
- package/dist/collapsible/surface/MarkdownText.esm.js +24 -0
- package/dist/collapsible/surface/MarkdownText.esm.js.map +1 -0
- package/dist/collapsible/surface/MermaidDiagram.esm.js +245 -0
- package/dist/collapsible/surface/MermaidDiagram.esm.js.map +1 -0
- package/dist/collapsible/surface/parts.esm.js +216 -0
- package/dist/collapsible/surface/parts.esm.js.map +1 -0
- package/dist/collapsible/surface/styles/assistant-ui-markdown.css.esm.js +5 -0
- package/dist/collapsible/surface/styles/assistant-ui-markdown.css.esm.js.map +1 -0
- package/dist/collapsible/surface/styles/assistant-ui.css.esm.js +5 -0
- package/dist/collapsible/surface/styles/assistant-ui.css.esm.js.map +1 -0
- package/dist/collapsible/unreadStore.esm.js +79 -0
- package/dist/collapsible/unreadStore.esm.js.map +1 -0
- package/dist/collapsible/useConversations.esm.js +171 -0
- package/dist/collapsible/useConversations.esm.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.esm.js +3 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +107 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollapsiblePage.esm.js","sources":["../../src/collapsible/CollapsiblePage.tsx"],"sourcesContent":["// Vendored react-ui Thread CSS. Imported at the TOP of this page entry so it\n// bundles into this page's lazy chunk: react-ui ships `sideEffects: false`, so\n// a bare `@assistant-ui/react-ui` CSS import gets tree-shaken and the Thread\n// renders unstyled. These local copies are not subject to that.\nimport './surface/styles/assistant-ui.css';\nimport './surface/styles/assistant-ui-markdown.css';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport useAsync from 'react-use/lib/useAsync';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Content, Progress, ResponseErrorPanel } from '@backstage/core-components';\nimport { makeStyles } from '@material-ui/core/styles';\nimport {\n Badge,\n FormControl,\n IconButton,\n ListItemIcon,\n ListSubheader,\n MenuItem,\n Select,\n Tooltip,\n Typography,\n} from '@material-ui/core';\nimport AddIcon from '@material-ui/icons/Add';\nimport ChatBubbleOutlineIcon from '@material-ui/icons/ChatBubbleOutline';\nimport CheckIcon from '@material-ui/icons/Check';\nimport ChevronRightIcon from '@material-ui/icons/ChevronRight';\nimport StarIcon from '@material-ui/icons/Star';\nimport { AssistantRuntimeProvider, useThread } from '@assistant-ui/react';\nimport { useChatRuntime } from '@assistant-ui/react-ai-sdk';\nimport { DefaultChatTransport } from 'ai';\nimport type { UIMessage } from 'ai';\nimport type {\n AssistantSummary,\n ModelId,\n ModelOption,\n StatusResponse,\n} from '@drewswiredin/backstage-plugin-assistants-common';\nimport { assistantsApiRef } from '../api';\nimport { ConversationSurface } from './surface';\nimport { AssistantAvatar } from './surface/AssistantAvatar';\nimport { SidePane } from './SidePane';\nimport { FullHeightRegion } from './FullHeightRegion';\nimport {\n persistConversationMessages,\n useConversations,\n} from './useConversations';\nimport {\n clearUnread,\n hasUnread,\n isConversationUnread,\n markUnread,\n useUnreadVersion,\n} from './unreadStore';\n\nconst SIDEPANE_COLLAPSED_KEY = 'ai-chat-sidepane-collapsed';\n\nfunction loadSidePaneCollapsed() {\n try {\n return localStorage.getItem(SIDEPANE_COLLAPSED_KEY) === 'true';\n } catch {\n return false;\n }\n}\n\nconst useStyles = makeStyles(theme => ({\n // Flex-column fill inside the measured FullHeightRegion (mirrors the native\n // page): lets the shell own the remaining height without a nested <Page>.\n content: {\n flex: 1,\n minHeight: 0,\n display: 'flex',\n flexDirection: 'column',\n },\n shell: {\n display: 'flex',\n flex: 1,\n minHeight: 0,\n overflow: 'hidden',\n backgroundColor: theme.palette.background.default,\n // No top gutter so the card tucks flush under the page header (reclaims the\n // gap there); 8px right/bottom keep it enclosed on those sides, 0 left since\n // it abuts the sidebar. Composer breathing room lives on the Thread's own\n // footer (see ConversationSurface) — same bg, no seam.\n paddingTop: 0,\n paddingRight: theme.spacing(1),\n paddingBottom: theme.spacing(1),\n paddingLeft: 0,\n gap: theme.spacing(1),\n },\n sidePane: {\n width: 320,\n flexShrink: 0,\n minHeight: 0,\n overflowY: 'auto',\n overflowX: 'hidden',\n },\n sidePaneRail: {\n width: 56,\n flexShrink: 0,\n minHeight: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n paddingBottom: theme.spacing(0.5),\n borderRight: `1px solid ${theme.palette.divider}`,\n overflow: 'hidden',\n },\n sidePaneRailHeader: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n minHeight: 44,\n flexShrink: 0,\n borderBottom: `1px solid ${theme.palette.divider}`,\n },\n sidePaneRailAssistants: {\n flexShrink: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n gap: theme.spacing(0.5),\n paddingTop: theme.spacing(0.75),\n },\n sidePaneRailDivider: {\n flexShrink: 0,\n width: 24,\n borderTop: `1px solid ${theme.palette.divider}`,\n margin: theme.spacing(0.75, 0),\n },\n sidePaneRailControls: {\n flexShrink: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n gap: theme.spacing(0.5),\n paddingBottom: theme.spacing(1),\n },\n sidePaneRailChats: {\n flex: 1,\n minHeight: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n gap: theme.spacing(0.5),\n overflowY: 'auto',\n overflowX: 'hidden',\n },\n sidePaneRailButton: {\n color: theme.palette.text.secondary,\n },\n sidePaneRailButtonActive: {\n color: theme.palette.primary.main,\n backgroundColor: theme.palette.action.selected,\n '&:hover': {\n backgroundColor: theme.palette.action.hover,\n },\n },\n threadPane: {\n display: 'flex',\n flex: 1,\n flexDirection: 'column',\n minWidth: 0,\n backgroundColor: theme.palette.background.paper,\n border: `1px solid ${theme.palette.divider}`,\n borderRadius: theme.shape.borderRadius,\n boxShadow: theme.shadows[1],\n overflow: 'hidden',\n },\n // Slim, solid header.\n threadHeader: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: theme.spacing(2),\n minHeight: 36,\n padding: theme.spacing(0, 2),\n backgroundColor: theme.palette.background.paper,\n borderBottom: `1px solid ${theme.palette.divider}`,\n },\n threadIdentity: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n minWidth: 0,\n flex: 1,\n },\n assistantName: {\n fontWeight: 600,\n color: theme.palette.text.primary,\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n threadTitle: {\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n color: theme.palette.text.secondary,\n },\n modelSelect: {\n fontSize: theme.typography.caption.fontSize,\n color: theme.palette.text.secondary,\n borderRadius: 999,\n transition: theme.transitions.create('background-color'),\n '&:hover': {\n backgroundColor: theme.palette.action.hover,\n },\n '& .MuiSelect-select': {\n display: 'flex',\n alignItems: 'center',\n borderRadius: 999,\n paddingTop: theme.spacing(0.5),\n paddingBottom: theme.spacing(0.5),\n paddingLeft: theme.spacing(1.25),\n paddingRight: theme.spacing(3),\n '&:focus': {\n backgroundColor: 'transparent',\n borderRadius: 999,\n },\n },\n '& .MuiSelect-icon': {\n color: theme.palette.text.secondary,\n right: theme.spacing(0.5),\n },\n },\n // Trigger label: vendor in muted text, model name emphasized.\n modelTriggerVendor: {\n color: theme.palette.text.hint,\n marginRight: theme.spacing(0.5),\n },\n modelGroupLabel: {\n lineHeight: 2,\n fontSize: theme.typography.caption.fontSize,\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n color: theme.palette.text.secondary,\n backgroundColor: theme.palette.background.paper,\n },\n modelItem: {\n paddingTop: theme.spacing(0.75),\n paddingBottom: theme.spacing(0.75),\n },\n modelItemCheck: {\n minWidth: theme.spacing(3.5),\n color: theme.palette.primary.main,\n },\n modelDefaultStar: {\n fontSize: '1rem',\n color: theme.palette.warning.main,\n marginLeft: theme.spacing(1),\n },\n threadBody: {\n flex: 1,\n minHeight: 0,\n display: 'flex',\n flexDirection: 'column',\n // NOTE: no paddingBottom here — the Thread fills this box with its own\n // --aui-background; padding would expose the card's paper bg and create a\n // two-tone seam. Composer breathing room lives on .aui-thread-viewport-footer\n // (same bg) in ConversationSurface.\n },\n // Background thread: kept mounted (stream alive) but out of view/layout.\n threadPaneHidden: {\n display: 'none',\n },\n}));\n\n/**\n * Bridges the active thread runtime's running state up to the live-thread\n * manager. Rendered inside {@link ChatThread}'s `AssistantRuntimeProvider`.\n */\nfunction RunningReporter({\n onRunningChange,\n}: {\n onRunningChange?: (running: boolean) => void;\n}) {\n const isRunning = useThread(t => t.isRunning);\n useEffect(() => {\n onRunningChange?.(isRunning);\n }, [isRunning, onRunningChange]);\n return null;\n}\n\n/**\n * Split a model id into a display vendor + name. Our models route through one\n * provider (e.g. `openrouter`) but the meaningful family is the vendor prefix in\n * the model name (`google/gemini-2.5-flash` -> {vendor: \"google\", name:\n * \"gemini-2.5-flash\"}). Falls back to the provider / raw id when there's no `/`.\n */\nfunction splitModel(\n id: ModelId,\n pool: ModelOption[],\n): { vendor: string; name: string } {\n const opt = pool.find(m => m.id === id);\n const raw = opt?.model ?? id;\n const slash = raw.indexOf('/');\n if (slash !== -1) {\n return { vendor: raw.slice(0, slash), name: raw.slice(slash + 1) };\n }\n return { vendor: opt?.provider ?? 'models', name: raw };\n}\n\n/** The short model name (after the vendor prefix), for the selector trigger. */\nfunction modelLabel(id: ModelId, pool: ModelOption[]): string {\n return splitModel(id, pool).name;\n}\n\ninterface ChatThreadProps {\n /** Backend base URL (`.../api/assistants`); the transport posts to `/chat`. */\n baseUrl: string;\n /** Authenticated fetch from the assistants API client. */\n authFetch: typeof fetch;\n /** Assistant the turn is sent to. */\n assistantId: string;\n /** Selected `provider:model` id. */\n modelId: ModelId;\n /** Header title (the active conversation's title). */\n title: string;\n /** The active assistant's display name (shown in the header identity). */\n assistantName: string;\n initialMessages?: UIMessage[];\n onFinish?: (messages: UIMessage[]) => void;\n /** Composer placeholder from the assistant's `ui`. */\n composerPlaceholder?: string;\n /** Starter prompts from the assistant's `ui`. */\n suggestions?: Array<{ title: string; prompt: string }>;\n /** The assistant's avatar color (tints the assistant message + welcome). */\n assistantColor?: string;\n /** Thread header content rendered to the right of the title. */\n headerRight?: React.ReactNode;\n /**\n * Render but visually hide the thread (kept mounted so a background stream\n * keeps running). The live-thread manager shows exactly one thread at a time.\n */\n hidden?: boolean;\n /** Notified when this thread starts/stops streaming (drives keep-alive). */\n onRunningChange?: (running: boolean) => void;\n}\n\n/**\n * A single chat thread: owns its `useChatRuntime` (AI SDK\n * `DefaultChatTransport`) and renders the {@link ConversationSurface}. Mounted\n * with `key={activeId}` by the page so a fresh runtime is created per\n * conversation, seeded with that conversation's `initialMessages`.\n */\nfunction ChatThread({\n baseUrl,\n authFetch,\n assistantId,\n modelId,\n title,\n assistantName,\n initialMessages,\n onFinish,\n composerPlaceholder,\n suggestions,\n assistantColor,\n headerRight,\n hidden,\n onRunningChange,\n}: ChatThreadProps) {\n const classes = useStyles();\n\n // Keep the assistant/model selection current without remounting the runtime:\n // the transport reads them from a ref via the function-form `body`.\n const selectionRef = useRef({ assistantId, modelId });\n selectionRef.current = { assistantId, modelId };\n\n const transport = useMemo(\n () =>\n new DefaultChatTransport({\n api: `${baseUrl}/chat`,\n fetch: authFetch,\n body: () => ({\n assistantId: selectionRef.current.assistantId,\n modelId: selectionRef.current.modelId,\n }),\n }),\n [authFetch, baseUrl],\n );\n\n // Keep onFinish ref stable so useChatRuntime doesn't remount mid-stream.\n const onFinishRef = useRef(onFinish);\n onFinishRef.current = onFinish;\n\n const stableOnFinish = useCallback(({ messages }: { messages: UIMessage[] }) => {\n onFinishRef.current?.(messages);\n }, []);\n\n const runtime = useChatRuntime({\n transport,\n messages: initialMessages,\n onFinish: stableOnFinish,\n });\n\n return (\n <AssistantRuntimeProvider runtime={runtime}>\n <RunningReporter onRunningChange={onRunningChange} />\n <main\n className={\n hidden ? `${classes.threadPane} ${classes.threadPaneHidden}` : classes.threadPane\n }\n aria-label=\"AI chat thread\"\n aria-hidden={hidden}\n >\n <div className={classes.threadHeader}>\n <div className={classes.threadIdentity}>\n <AssistantAvatar color={assistantColor} size={22} />\n <Typography variant=\"subtitle2\" className={classes.assistantName}>\n {assistantName}\n </Typography>\n {title && (\n <Typography\n variant=\"body2\"\n className={classes.threadTitle}\n title={title}\n >\n · {title}\n </Typography>\n )}\n </div>\n {headerRight}\n </div>\n <div className={classes.threadBody}>\n <ConversationSurface\n composerPlaceholder={composerPlaceholder}\n suggestions={suggestions}\n assistantColor={assistantColor}\n />\n </div>\n </main>\n </AssistantRuntimeProvider>\n );\n}\n\n/**\n * Everything the live-thread manager needs to mount a {@link ChatThread}\n * independently of which assistant is currently on screen. Captured (snapshot)\n * when a conversation first becomes active, so a thread can keep streaming in the\n * background after the user switches conversation or assistant.\n */\ninterface ThreadDescriptor {\n convId: string;\n agentId: string;\n assistantTitle: string;\n assistantColor?: string;\n modelId: ModelId;\n initialMessages?: UIMessage[];\n composerPlaceholder?: string;\n suggestions?: Array<{ title: string; prompt: string }>;\n}\n\n/**\n * Keeps a {@link ChatThread} mounted while it is the active conversation OR still\n * streaming, so navigating away doesn't abort an in-flight reply. Returns the set\n * of descriptors to render (active + any still-running background threads) and a\n * callback to report each thread's running state.\n *\n * Reconciliation runs in an effect (not during render) so the discarded render\n * that React performs when `useConversations` re-seeds on an assistant switch\n * can't capture a half-updated descriptor.\n */\nfunction useLiveThreads(active: ThreadDescriptor | null) {\n const [mounted, setMounted] = useState<ThreadDescriptor[]>([]);\n const [running, setRunning] = useState<ReadonlySet<string>>(\n () => new Set<string>(),\n );\n const activeRef = useRef(active);\n activeRef.current = active;\n\n useEffect(() => {\n const a = activeRef.current;\n setMounted(prev => {\n const byId = new Map(prev.map(d => [d.convId, d] as const));\n // Snapshot the active descriptor the first time it mounts; never overwrite\n // an already-mounted (live) thread.\n if (a && !byId.has(a.convId)) {\n byId.set(a.convId, a);\n }\n const keep = new Set<string>(running);\n if (a) {\n keep.add(a.convId);\n }\n const next: ThreadDescriptor[] = [];\n for (const d of prev) {\n if (keep.has(d.convId)) {\n next.push(byId.get(d.convId) ?? d);\n keep.delete(d.convId);\n }\n }\n for (const id of keep) {\n const d = byId.get(id);\n if (d) {\n next.push(d);\n }\n }\n const unchanged =\n next.length === prev.length &&\n next.every((d, i) => d.convId === prev[i].convId);\n return unchanged ? prev : next;\n });\n }, [active?.convId, running]);\n\n const setThreadRunning = useCallback((convId: string, isRunning: boolean) => {\n setRunning(prev => {\n const has = prev.has(convId);\n if (isRunning === has) {\n return prev;\n }\n const next = new Set(prev);\n if (isRunning) {\n next.add(convId);\n } else {\n next.delete(convId);\n }\n return next;\n });\n }, []);\n\n // Always render the active thread immediately, even before the effect folds it\n // into `mounted` (avoids a one-frame empty pane on open / switch).\n const threads =\n active && !mounted.some(d => d.convId === active.convId)\n ? [...mounted, active]\n : mounted;\n\n return { threads, setThreadRunning };\n}\n\ninterface CollapsibleChatProps {\n status: StatusResponse;\n assistant: AssistantSummary;\n}\n\n/**\n * The conversation experience for the resolved assistant: the collapsible left\n * rail, the per-assistant conversation set, the model picker, and the live\n * threads. NOT remounted on assistant switch — `useConversations` re-seeds from\n * the new namespace, and the {@link useLiveThreads} manager keeps background\n * threads streaming across the switch.\n */\nfunction CollapsibleChat({ status, assistant }: CollapsibleChatProps) {\n const classes = useStyles();\n const api = useApi(assistantsApiRef);\n useUnreadVersion(); // re-render the rail's unread dots on change\n\n // Switching assistant drives `?assistant=<id>`; the page re-resolves and\n // remounts this component (keyed by assistant.id) onto that assistant's\n // siloed conversation set.\n const [, setSearchParams] = useSearchParams();\n const handleSelectAssistant = useCallback(\n (id: string) => {\n if (id !== assistant.id) {\n setSearchParams({ assistant: id });\n }\n },\n [assistant.id, setSearchParams],\n );\n\n const baseUrl = useAsync(() => api.getBaseUrl(), [api]);\n\n // Model picker: limited to the assistant's allowlist (else the global pool),\n // defaulting to the assistant's default (else the global default).\n const allowedModels = useMemo<ModelId[]>(\n () => assistant.models ?? status.models.map(m => m.id),\n [assistant.models, status.models],\n );\n // The model marked with a default star in the menu.\n const defaultModel = assistant.defaultModel ?? status.defaultModel;\n // Group allowed models by vendor (the prefix in the model name) for the menu.\n const modelGroups = useMemo<Array<[string, ModelId[]]>>(() => {\n const byVendor = new Map<string, ModelId[]>();\n for (const id of allowedModels) {\n const { vendor } = splitModel(id, status.models);\n const list = byVendor.get(vendor);\n if (list) {\n list.push(id);\n } else {\n byVendor.set(vendor, [id]);\n }\n }\n return [...byVendor.entries()];\n }, [allowedModels, status.models]);\n const convState = useConversations(assistant.id);\n\n // Per-conversation model memory. Seed from the active conversation's saved\n // model, validated against this assistant's allowlist; fall back to the\n // assistant default when absent or no longer available.\n const resolveModel = useCallback(\n (stored: ModelId | undefined) =>\n stored && allowedModels.includes(stored) ? stored : defaultModel,\n [allowedModels, defaultModel],\n );\n const [modelId, setModelId] = useState<ModelId>(() =>\n resolveModel(convState.activeConversation?.model),\n );\n // Adopt the active conversation's saved model when switching conversations.\n // Keyed on activeId only (not the conversation object, whose identity changes\n // on every streamed message) so an in-flight chat can't clobber the pick.\n useEffect(() => {\n setModelId(resolveModel(convState.activeConversation?.model));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [convState.activeId]);\n // Persist the user's pick onto the active conversation.\n const handleModelChange = useCallback(\n (next: ModelId) => {\n setModelId(next);\n if (convState.activeId) {\n convState.setConversationModel(convState.activeId, next);\n }\n },\n [convState],\n );\n\n const [sidePaneCollapsed, setSidePaneCollapsed] = useState(\n loadSidePaneCollapsed,\n );\n useEffect(() => {\n try {\n localStorage.setItem(SIDEPANE_COLLAPSED_KEY, String(sidePaneCollapsed));\n } catch {\n // storage unavailable\n }\n }, [sidePaneCollapsed]);\n\n const handleNew = useCallback(() => {\n convState.createConversation();\n }, [convState]);\n\n // A turn finished — possibly in a background thread (different conversation or\n // even a different assistant than the one on screen).\n const handleThreadFinish = useCallback(\n (descriptor: ThreadDescriptor, messages: UIMessage[]) => {\n const sameAgent = descriptor.agentId === assistant.id;\n const viewing = sameAgent && descriptor.convId === convState.activeId;\n\n // Persist: reactively for the on-screen assistant, directly to storage for\n // any other assistant (whose conversation state isn't mounted here).\n if (sameAgent) {\n convState.updateMessages(descriptor.convId, messages);\n } else {\n persistConversationMessages(\n descriptor.agentId,\n descriptor.convId,\n messages,\n );\n }\n\n // Unread dot if the reply landed somewhere the user isn't looking.\n if (!viewing) {\n markUnread(descriptor.agentId, descriptor.convId);\n }\n\n // Best-effort title for brand-new chats of the on-screen assistant (its\n // conversation state is mounted, so the rename is reactive).\n if (sameAgent) {\n const conv = convState.conversations.find(\n c => c.id === descriptor.convId,\n );\n const shouldTitle =\n conv?.title === 'New Chat' &&\n messages.some(m => m.role === 'user') &&\n messages.some(m => m.role === 'assistant');\n if (shouldTitle) {\n api\n .getTitle({\n assistantId: descriptor.agentId,\n modelId: descriptor.modelId,\n messages,\n })\n .then(generated => {\n if (generated && generated !== 'New Chat') {\n convState.renameConversation(descriptor.convId, generated);\n }\n })\n .catch(() => {\n // Title generation is best-effort only.\n });\n }\n }\n },\n [api, assistant.id, convState],\n );\n\n // Auto-create a conversation on first load if none active.\n useEffect(() => {\n if (!convState.activeId) {\n convState.createConversation();\n }\n // Only on mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Viewing a conversation clears its unread dot (and does so on assistant\n // switch, since the active conversation changes with it).\n useEffect(() => {\n if (convState.activeId) {\n clearUnread(assistant.id, convState.activeId);\n }\n }, [assistant.id, convState.activeId]);\n\n // The active conversation, as a descriptor, plus any background threads still\n // streaming. The manager keeps them mounted across conversation/assistant\n // switches so their replies finish and persist.\n const activeDescriptor: ThreadDescriptor | null = convState.activeId\n ? {\n convId: convState.activeId,\n agentId: assistant.id,\n assistantTitle: assistant.title,\n assistantColor: assistant.color,\n modelId,\n initialMessages: convState.activeConversation?.messages,\n composerPlaceholder: assistant.ui?.composer?.placeholder,\n suggestions: assistant.ui?.suggestions,\n }\n : null;\n const { threads, setThreadRunning } = useLiveThreads(activeDescriptor);\n\n const modelPicker = (\n <FormControl>\n <Select\n value={modelId}\n onChange={e => handleModelChange(e.target.value as ModelId)}\n disableUnderline\n className={classes.modelSelect}\n inputProps={{ 'aria-label': 'Model' }}\n renderValue={value => modelLabel(value as ModelId, status.models)}\n MenuProps={{\n anchorOrigin: { vertical: 'bottom', horizontal: 'right' },\n transformOrigin: { vertical: 'top', horizontal: 'right' },\n getContentAnchorEl: null,\n }}\n >\n {modelGroups.flatMap(([vendor, ids]) => [\n <ListSubheader\n key={`group-${vendor}`}\n disableSticky\n className={classes.modelGroupLabel}\n >\n {vendor}\n </ListSubheader>,\n ...ids.map(id => (\n <MenuItem key={id} value={id} className={classes.modelItem}>\n <ListItemIcon className={classes.modelItemCheck}>\n {id === modelId ? <CheckIcon fontSize=\"small\" /> : null}\n </ListItemIcon>\n <span style={{ flexGrow: 1 }}>\n {modelLabel(id, status.models)}\n </span>\n {id === defaultModel && (\n <Tooltip title=\"Assistant default\">\n <StarIcon\n className={classes.modelDefaultStar}\n aria-label=\"Assistant default\"\n />\n </Tooltip>\n )}\n </MenuItem>\n )),\n ])}\n </Select>\n </FormControl>\n );\n\n if (baseUrl.loading) {\n return <Progress />;\n }\n if (baseUrl.error || !baseUrl.value) {\n return (\n <ResponseErrorPanel\n error={baseUrl.error ?? new Error('Failed to resolve backend URL')}\n />\n );\n }\n\n return (\n <FullHeightRegion>\n <Content noPadding className={classes.content}>\n <div className={classes.shell}>\n {sidePaneCollapsed ? (\n <aside\n className={classes.sidePaneRail}\n aria-label=\"AI chat sidebar collapsed\"\n >\n <div className={classes.sidePaneRailHeader}>\n <Tooltip title=\"Expand\" placement=\"right\">\n <IconButton\n size=\"small\"\n className={classes.sidePaneRailButton}\n aria-label=\"Expand AI chat sidebar\"\n onClick={() => setSidePaneCollapsed(false)}\n >\n <ChevronRightIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </div>\n <nav\n className={classes.sidePaneRailAssistants}\n aria-label=\"Assistants\"\n >\n {status.assistants.map(a => (\n <Tooltip key={a.id} title={a.title} placement=\"right\">\n <IconButton\n size=\"small\"\n className={`${classes.sidePaneRailButton} ${\n a.id === assistant.id\n ? classes.sidePaneRailButtonActive\n : ''\n }`}\n aria-label={a.title}\n onClick={() => handleSelectAssistant(a.id)}\n >\n <Badge\n color=\"error\"\n variant=\"dot\"\n overlap=\"circular\"\n invisible={!hasUnread(a.id)}\n >\n <AssistantAvatar color={a.color} size={24} />\n </Badge>\n </IconButton>\n </Tooltip>\n ))}\n </nav>\n <div className={classes.sidePaneRailDivider} />\n <div className={classes.sidePaneRailControls}>\n <Tooltip title=\"New Chat\" placement=\"right\">\n <IconButton\n size=\"small\"\n className={classes.sidePaneRailButton}\n aria-label=\"New Chat\"\n onClick={handleNew}\n >\n <AddIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </div>\n <nav\n className={classes.sidePaneRailChats}\n aria-label=\"AI chat conversations\"\n >\n {convState.conversations.map(conversation => (\n <Tooltip\n key={conversation.id}\n title={conversation.title}\n placement=\"right\"\n >\n <IconButton\n size=\"small\"\n className={`${classes.sidePaneRailButton} ${\n conversation.id === convState.activeId\n ? classes.sidePaneRailButtonActive\n : ''\n }`}\n aria-label={conversation.title}\n onClick={() => convState.selectConversation(conversation.id)}\n >\n <Badge\n color=\"error\"\n variant=\"dot\"\n overlap=\"circular\"\n invisible={\n conversation.id === convState.activeId ||\n !isConversationUnread(assistant.id, conversation.id)\n }\n >\n <ChatBubbleOutlineIcon fontSize=\"small\" />\n </Badge>\n </IconButton>\n </Tooltip>\n ))}\n </nav>\n </aside>\n ) : (\n <aside className={classes.sidePane} aria-label=\"AI chat sidepane\">\n <SidePane\n assistants={status.assistants}\n activeAssistantId={assistant.id}\n onSelectAssistant={handleSelectAssistant}\n conversations={convState.conversations}\n activeId={convState.activeId}\n onNew={handleNew}\n onSelect={convState.selectConversation}\n onRename={convState.renameConversation}\n onPin={convState.pinConversation}\n onDelete={convState.deleteConversation}\n onCollapse={() => setSidePaneCollapsed(true)}\n />\n </aside>\n )}\n {threads.map(d => {\n const isActive =\n d.agentId === assistant.id && d.convId === convState.activeId;\n return (\n <ChatThread\n key={d.convId}\n hidden={!isActive}\n baseUrl={baseUrl.value}\n authFetch={api.fetch}\n assistantId={d.agentId}\n modelId={isActive ? modelId : d.modelId}\n title={isActive ? convState.activeConversation?.title ?? '' : ''}\n assistantName={isActive ? assistant.title : d.assistantTitle}\n initialMessages={d.initialMessages}\n onFinish={messages => handleThreadFinish(d, messages)}\n onRunningChange={running => setThreadRunning(d.convId, running)}\n composerPlaceholder={d.composerPlaceholder}\n suggestions={d.suggestions}\n assistantColor={isActive ? assistant.color : d.assistantColor}\n headerRight={isActive ? modelPicker : undefined}\n />\n );\n })}\n </div>\n </Content>\n </FullHeightRegion>\n );\n}\n\n/**\n * Implementation 1's collapsible AI chat page, faithfully reproduced as a\n * fully self-contained Backstage page module.\n *\n * Reads the target assistant from `?assistant=<id>` (the assistant rail lives\n * in the Backstage nav), loads `/status`, and renders the collapsible\n * conversation experience for the chosen assistant (or the first accessible\n * one). Backend wiring goes through `assistantsApiRef` — never `config`.\n *\n * @public\n */\nexport function CollapsiblePage() {\n const api = useApi(assistantsApiRef);\n const [searchParams] = useSearchParams();\n const requestedAssistant = searchParams.get('assistant');\n\n const status = useAsync(() => api.getStatus(), [api]);\n\n // No <Page> wrapper: the new frontend system already renders this extension\n // inside the app's page chrome (header/breadcrumb). A nested <Page> adds its\n // own min-height and pushes the layout past the viewport (outer scrollbar).\n // The chat fills the viewport via FullHeightRegion inside CollapsibleChat.\n if (status.loading) {\n return <Progress />;\n }\n if (status.error) {\n return <ResponseErrorPanel error={status.error} />;\n }\n if (!status.value) {\n return null;\n }\n const assistants = status.value.assistants;\n if (assistants.length === 0) {\n return (\n <ResponseErrorPanel\n error={new Error('No assistants are available to you.')}\n />\n );\n }\n const assistant =\n assistants.find(a => a.id === requestedAssistant) ?? assistants[0];\n // Intentionally NOT keyed by assistant.id: the page persists across assistant\n // switches so background chat threads keep streaming. useConversations\n // re-seeds itself from the new assistant's namespace.\n return <CollapsibleChat status={status.value} assistant={assistant} />;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { makeStyles } from '@material-ui/core/styles';
|
|
4
|
+
import { Typography, List, ListItem, ListItemIcon, Badge, TextField, Tooltip, ListItemText, ListItemSecondaryAction, IconButton, Menu, MenuItem } from '@material-ui/core';
|
|
5
|
+
import BookmarkIcon from '@material-ui/icons/Bookmark';
|
|
6
|
+
import ChatBubbleOutlineIcon from '@material-ui/icons/ChatBubbleOutline';
|
|
7
|
+
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
|
8
|
+
import { useUnreadVersion, isConversationUnread } from './unreadStore.esm.js';
|
|
9
|
+
|
|
10
|
+
const useStyles = makeStyles((theme) => ({
|
|
11
|
+
root: {
|
|
12
|
+
display: "flex",
|
|
13
|
+
flexDirection: "column",
|
|
14
|
+
padding: theme.spacing(0.5, 1.5, 1),
|
|
15
|
+
gap: theme.spacing(1)
|
|
16
|
+
},
|
|
17
|
+
activeItem: {
|
|
18
|
+
borderRadius: theme.shape.borderRadius,
|
|
19
|
+
backgroundColor: theme.palette.action.selected
|
|
20
|
+
},
|
|
21
|
+
listItem: {
|
|
22
|
+
borderRadius: theme.shape.borderRadius,
|
|
23
|
+
"&:hover": {
|
|
24
|
+
backgroundColor: theme.palette.action.hover
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
// Reveal the row's ⋮ only on hover so the title gets the full width otherwise.
|
|
28
|
+
// Must live on the container <li> (ContainerProps): the secondary action is a
|
|
29
|
+
// sibling of the ListItem root, so a :hover on the row can't reach it.
|
|
30
|
+
container: {
|
|
31
|
+
"&:hover $action": {
|
|
32
|
+
opacity: 1,
|
|
33
|
+
pointerEvents: "auto"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
action: {
|
|
37
|
+
opacity: 0,
|
|
38
|
+
pointerEvents: "none",
|
|
39
|
+
transition: theme.transitions.create("opacity")
|
|
40
|
+
},
|
|
41
|
+
// Keep the ⋮ visible while its menu is open (even if the row isn't hovered).
|
|
42
|
+
actionVisible: {
|
|
43
|
+
opacity: 1,
|
|
44
|
+
pointerEvents: "auto"
|
|
45
|
+
},
|
|
46
|
+
pinnedIcon: {
|
|
47
|
+
color: theme.palette.warning.main,
|
|
48
|
+
fontSize: "0.875rem",
|
|
49
|
+
marginRight: theme.spacing(0.5)
|
|
50
|
+
},
|
|
51
|
+
renameInput: {
|
|
52
|
+
"& input": {
|
|
53
|
+
fontSize: "0.875rem",
|
|
54
|
+
padding: theme.spacing(0.5, 1)
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
emptyState: {
|
|
58
|
+
padding: theme.spacing(3, 2),
|
|
59
|
+
textAlign: "center"
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
function ConversationsPanel({
|
|
63
|
+
assistantId,
|
|
64
|
+
conversations,
|
|
65
|
+
activeId,
|
|
66
|
+
onSelect,
|
|
67
|
+
onRename,
|
|
68
|
+
onPin,
|
|
69
|
+
onDelete
|
|
70
|
+
}) {
|
|
71
|
+
const classes = useStyles();
|
|
72
|
+
useUnreadVersion();
|
|
73
|
+
const [menuAnchor, setMenuAnchor] = useState(null);
|
|
74
|
+
const [renamingId, setRenamingId] = useState(null);
|
|
75
|
+
const [renameValue, setRenameValue] = useState("");
|
|
76
|
+
const handleMenuOpen = (e, id) => {
|
|
77
|
+
e.stopPropagation();
|
|
78
|
+
setMenuAnchor({ el: e.currentTarget, id });
|
|
79
|
+
};
|
|
80
|
+
const handleMenuClose = () => setMenuAnchor(null);
|
|
81
|
+
const handleRenameStart = (conv) => {
|
|
82
|
+
setRenamingId(conv.id);
|
|
83
|
+
setRenameValue(conv.title);
|
|
84
|
+
handleMenuClose();
|
|
85
|
+
};
|
|
86
|
+
const handleRenameSubmit = () => {
|
|
87
|
+
if (renamingId && renameValue.trim()) {
|
|
88
|
+
onRename(renamingId, renameValue.trim());
|
|
89
|
+
}
|
|
90
|
+
setRenamingId(null);
|
|
91
|
+
};
|
|
92
|
+
return /* @__PURE__ */ jsxs("div", { className: classes.root, children: [
|
|
93
|
+
conversations.length === 0 ? /* @__PURE__ */ jsx("div", { className: classes.emptyState, children: /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "textSecondary", children: "No conversations yet" }) }) : /* @__PURE__ */ jsx(List, { disablePadding: true, dense: true, children: conversations.map((conv) => /* @__PURE__ */ jsxs(
|
|
94
|
+
ListItem,
|
|
95
|
+
{
|
|
96
|
+
button: true,
|
|
97
|
+
className: conv.id === activeId ? classes.activeItem : classes.listItem,
|
|
98
|
+
ContainerProps: { className: classes.container },
|
|
99
|
+
onClick: () => onSelect(conv.id),
|
|
100
|
+
children: [
|
|
101
|
+
/* @__PURE__ */ jsx(ListItemIcon, { style: { minWidth: 32 }, children: /* @__PURE__ */ jsx(
|
|
102
|
+
Badge,
|
|
103
|
+
{
|
|
104
|
+
color: "error",
|
|
105
|
+
variant: "dot",
|
|
106
|
+
overlap: "circular",
|
|
107
|
+
invisible: conv.id === activeId || !isConversationUnread(assistantId, conv.id),
|
|
108
|
+
children: /* @__PURE__ */ jsx(ChatBubbleOutlineIcon, { fontSize: "small" })
|
|
109
|
+
}
|
|
110
|
+
) }),
|
|
111
|
+
renamingId === conv.id ? /* @__PURE__ */ jsx(
|
|
112
|
+
TextField,
|
|
113
|
+
{
|
|
114
|
+
className: classes.renameInput,
|
|
115
|
+
value: renameValue,
|
|
116
|
+
onChange: (e) => setRenameValue(e.target.value),
|
|
117
|
+
onBlur: handleRenameSubmit,
|
|
118
|
+
onKeyDown: (e) => {
|
|
119
|
+
if (e.key === "Enter") handleRenameSubmit();
|
|
120
|
+
if (e.key === "Escape") setRenamingId(null);
|
|
121
|
+
},
|
|
122
|
+
autoFocus: true,
|
|
123
|
+
fullWidth: true,
|
|
124
|
+
size: "small",
|
|
125
|
+
variant: "standard"
|
|
126
|
+
}
|
|
127
|
+
) : /* @__PURE__ */ jsx(Tooltip, { title: conv.title, placement: "right", arrow: true, children: /* @__PURE__ */ jsx(
|
|
128
|
+
ListItemText,
|
|
129
|
+
{
|
|
130
|
+
primary: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
131
|
+
conv.pinned && /* @__PURE__ */ jsx(BookmarkIcon, { className: classes.pinnedIcon }),
|
|
132
|
+
conv.title
|
|
133
|
+
] }),
|
|
134
|
+
primaryTypographyProps: { variant: "body2", noWrap: true }
|
|
135
|
+
}
|
|
136
|
+
) }),
|
|
137
|
+
/* @__PURE__ */ jsx(
|
|
138
|
+
ListItemSecondaryAction,
|
|
139
|
+
{
|
|
140
|
+
className: `${classes.action}${menuAnchor?.id === conv.id ? ` ${classes.actionVisible}` : ""}`,
|
|
141
|
+
children: /* @__PURE__ */ jsx(
|
|
142
|
+
IconButton,
|
|
143
|
+
{
|
|
144
|
+
edge: "end",
|
|
145
|
+
size: "small",
|
|
146
|
+
"aria-label": "Conversation options",
|
|
147
|
+
onClick: (e) => handleMenuOpen(e, conv.id),
|
|
148
|
+
children: /* @__PURE__ */ jsx(MoreVertIcon, { fontSize: "small" })
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
conv.id
|
|
156
|
+
)) }),
|
|
157
|
+
/* @__PURE__ */ jsx(
|
|
158
|
+
Menu,
|
|
159
|
+
{
|
|
160
|
+
anchorEl: menuAnchor?.el,
|
|
161
|
+
open: Boolean(menuAnchor),
|
|
162
|
+
onClose: handleMenuClose,
|
|
163
|
+
children: menuAnchor && (() => {
|
|
164
|
+
const conv = conversations.find((c) => c.id === menuAnchor.id);
|
|
165
|
+
if (!conv) return null;
|
|
166
|
+
return [
|
|
167
|
+
/* @__PURE__ */ jsx(MenuItem, { onClick: () => handleRenameStart(conv), children: "Rename" }, "rename"),
|
|
168
|
+
/* @__PURE__ */ jsx(
|
|
169
|
+
MenuItem,
|
|
170
|
+
{
|
|
171
|
+
onClick: () => {
|
|
172
|
+
onPin(conv.id);
|
|
173
|
+
handleMenuClose();
|
|
174
|
+
},
|
|
175
|
+
children: conv.pinned ? "Unpin" : "Pin"
|
|
176
|
+
},
|
|
177
|
+
"pin"
|
|
178
|
+
),
|
|
179
|
+
/* @__PURE__ */ jsx(
|
|
180
|
+
MenuItem,
|
|
181
|
+
{
|
|
182
|
+
onClick: () => {
|
|
183
|
+
onDelete(conv.id);
|
|
184
|
+
handleMenuClose();
|
|
185
|
+
},
|
|
186
|
+
children: "Delete"
|
|
187
|
+
},
|
|
188
|
+
"delete"
|
|
189
|
+
)
|
|
190
|
+
];
|
|
191
|
+
})()
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
] });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { ConversationsPanel };
|
|
198
|
+
//# sourceMappingURL=ConversationsPanel.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConversationsPanel.esm.js","sources":["../../src/collapsible/ConversationsPanel.tsx"],"sourcesContent":["import { MouseEvent, useState } from 'react';\nimport { makeStyles } from '@material-ui/core/styles';\nimport {\n Badge,\n IconButton,\n List,\n ListItem,\n ListItemIcon,\n ListItemSecondaryAction,\n ListItemText,\n Menu,\n MenuItem,\n TextField,\n Tooltip,\n Typography,\n} from '@material-ui/core';\nimport BookmarkIcon from '@material-ui/icons/Bookmark';\nimport ChatBubbleOutlineIcon from '@material-ui/icons/ChatBubbleOutline';\nimport MoreVertIcon from '@material-ui/icons/MoreVert';\nimport type { Conversation } from './useConversations';\nimport { isConversationUnread, useUnreadVersion } from './unreadStore';\n\nconst useStyles = makeStyles(theme => ({\n root: {\n display: 'flex',\n flexDirection: 'column',\n padding: theme.spacing(0.5, 1.5, 1),\n gap: theme.spacing(1),\n },\n activeItem: {\n borderRadius: theme.shape.borderRadius,\n backgroundColor: theme.palette.action.selected,\n },\n listItem: {\n borderRadius: theme.shape.borderRadius,\n '&:hover': {\n backgroundColor: theme.palette.action.hover,\n },\n },\n // Reveal the row's ⋮ only on hover so the title gets the full width otherwise.\n // Must live on the container <li> (ContainerProps): the secondary action is a\n // sibling of the ListItem root, so a :hover on the row can't reach it.\n container: {\n '&:hover $action': {\n opacity: 1,\n pointerEvents: 'auto',\n },\n },\n action: {\n opacity: 0,\n pointerEvents: 'none',\n transition: theme.transitions.create('opacity'),\n },\n // Keep the ⋮ visible while its menu is open (even if the row isn't hovered).\n actionVisible: {\n opacity: 1,\n pointerEvents: 'auto',\n },\n pinnedIcon: {\n color: theme.palette.warning.main,\n fontSize: '0.875rem',\n marginRight: theme.spacing(0.5),\n },\n renameInput: {\n '& input': {\n fontSize: '0.875rem',\n padding: theme.spacing(0.5, 1),\n },\n },\n emptyState: {\n padding: theme.spacing(3, 2),\n textAlign: 'center',\n },\n}));\n\n/**\n * Props for {@link ConversationsPanel}.\n *\n * @public\n */\nexport interface ConversationsPanelProps {\n /** The assistant these conversations belong to (for unread lookups). */\n assistantId: string;\n conversations: Conversation[];\n activeId: string | null;\n onSelect: (id: string | null) => void;\n onRename: (id: string, title: string) => void;\n onPin: (id: string) => void;\n onDelete: (id: string) => void;\n}\n\n/**\n * The expanded sidebar's conversation list (ported from Implementation 1): a\n * \"New Chat\" button, the conversation rows (active highlight, pinned bookmark\n * icon, inline rename), and a per-row overflow (⋮) menu with Rename / Pin /\n * Delete.\n *\n * @public\n */\nexport function ConversationsPanel({\n assistantId,\n conversations,\n activeId,\n onSelect,\n onRename,\n onPin,\n onDelete,\n}: ConversationsPanelProps) {\n const classes = useStyles();\n useUnreadVersion(); // re-render when unread state changes\n const [menuAnchor, setMenuAnchor] = useState<{\n el: HTMLElement;\n id: string;\n } | null>(null);\n const [renamingId, setRenamingId] = useState<string | null>(null);\n const [renameValue, setRenameValue] = useState('');\n\n const handleMenuOpen = (e: MouseEvent<HTMLElement>, id: string) => {\n e.stopPropagation();\n setMenuAnchor({ el: e.currentTarget, id });\n };\n\n const handleMenuClose = () => setMenuAnchor(null);\n\n const handleRenameStart = (conv: Conversation) => {\n setRenamingId(conv.id);\n setRenameValue(conv.title);\n handleMenuClose();\n };\n\n const handleRenameSubmit = () => {\n if (renamingId && renameValue.trim()) {\n onRename(renamingId, renameValue.trim());\n }\n setRenamingId(null);\n };\n\n return (\n <div className={classes.root}>\n {conversations.length === 0 ? (\n <div className={classes.emptyState}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n No conversations yet\n </Typography>\n </div>\n ) : (\n <List disablePadding dense>\n {conversations.map(conv => (\n <ListItem\n key={conv.id}\n button\n className={\n conv.id === activeId ? classes.activeItem : classes.listItem\n }\n ContainerProps={{ className: classes.container }}\n onClick={() => onSelect(conv.id)}\n >\n <ListItemIcon style={{ minWidth: 32 }}>\n <Badge\n color=\"error\"\n variant=\"dot\"\n overlap=\"circular\"\n invisible={\n conv.id === activeId ||\n !isConversationUnread(assistantId, conv.id)\n }\n >\n <ChatBubbleOutlineIcon fontSize=\"small\" />\n </Badge>\n </ListItemIcon>\n {renamingId === conv.id ? (\n <TextField\n className={classes.renameInput}\n value={renameValue}\n onChange={e => setRenameValue(e.target.value)}\n onBlur={handleRenameSubmit}\n onKeyDown={e => {\n if (e.key === 'Enter') handleRenameSubmit();\n if (e.key === 'Escape') setRenamingId(null);\n }}\n autoFocus\n fullWidth\n size=\"small\"\n variant=\"standard\"\n />\n ) : (\n <Tooltip title={conv.title} placement=\"right\" arrow>\n <ListItemText\n primary={\n <>\n {conv.pinned && (\n <BookmarkIcon className={classes.pinnedIcon} />\n )}\n {conv.title}\n </>\n }\n primaryTypographyProps={{ variant: 'body2', noWrap: true }}\n />\n </Tooltip>\n )}\n <ListItemSecondaryAction\n className={`${classes.action}${\n menuAnchor?.id === conv.id ? ` ${classes.actionVisible}` : ''\n }`}\n >\n <IconButton\n edge=\"end\"\n size=\"small\"\n aria-label=\"Conversation options\"\n onClick={e => handleMenuOpen(e, conv.id)}\n >\n <MoreVertIcon fontSize=\"small\" />\n </IconButton>\n </ListItemSecondaryAction>\n </ListItem>\n ))}\n </List>\n )}\n\n <Menu\n anchorEl={menuAnchor?.el}\n open={Boolean(menuAnchor)}\n onClose={handleMenuClose}\n >\n {menuAnchor &&\n (() => {\n const conv = conversations.find(c => c.id === menuAnchor.id);\n if (!conv) return null;\n return [\n <MenuItem key=\"rename\" onClick={() => handleRenameStart(conv)}>\n Rename\n </MenuItem>,\n <MenuItem\n key=\"pin\"\n onClick={() => {\n onPin(conv.id);\n handleMenuClose();\n }}\n >\n {conv.pinned ? 'Unpin' : 'Pin'}\n </MenuItem>,\n <MenuItem\n key=\"delete\"\n onClick={() => {\n onDelete(conv.id);\n handleMenuClose();\n }}\n >\n Delete\n </MenuItem>,\n ];\n })()}\n </Menu>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useRef, useLayoutEffect } from 'react';
|
|
3
|
+
import { makeStyles } from '@material-ui/core/styles';
|
|
4
|
+
|
|
5
|
+
const useStyles = makeStyles(
|
|
6
|
+
{
|
|
7
|
+
root: {
|
|
8
|
+
display: "flex",
|
|
9
|
+
flexDirection: "column",
|
|
10
|
+
minHeight: 0,
|
|
11
|
+
overflow: "hidden"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{ name: "AssistantsCollapsibleFullHeightRegion" }
|
|
15
|
+
);
|
|
16
|
+
function FullHeightRegion(props) {
|
|
17
|
+
const classes = useStyles();
|
|
18
|
+
const ref = useRef(null);
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
const el = ref.current;
|
|
21
|
+
if (!el || typeof window === "undefined") {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
const apply = () => {
|
|
25
|
+
const top = el.getBoundingClientRect().top;
|
|
26
|
+
el.style.height = `${Math.max(0, window.innerHeight - top)}px`;
|
|
27
|
+
};
|
|
28
|
+
apply();
|
|
29
|
+
window.addEventListener("resize", apply);
|
|
30
|
+
let observer;
|
|
31
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
32
|
+
observer = new ResizeObserver(() => apply());
|
|
33
|
+
const header = el.previousElementSibling;
|
|
34
|
+
if (header) {
|
|
35
|
+
observer.observe(header);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return () => {
|
|
39
|
+
window.removeEventListener("resize", apply);
|
|
40
|
+
observer?.disconnect();
|
|
41
|
+
};
|
|
42
|
+
}, []);
|
|
43
|
+
return /* @__PURE__ */ jsx(
|
|
44
|
+
"div",
|
|
45
|
+
{
|
|
46
|
+
ref,
|
|
47
|
+
className: props.className ? `${classes.root} ${props.className}` : classes.root,
|
|
48
|
+
children: props.children
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { FullHeightRegion };
|
|
54
|
+
//# sourceMappingURL=FullHeightRegion.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FullHeightRegion.esm.js","sources":["../../src/collapsible/FullHeightRegion.tsx"],"sourcesContent":["/**\n * A self-bounding, full-height page region for the chat surface.\n *\n * The page renders inside Backstage's `Content` area, which is `width: 100%`\n * with AUTO height (not a viewport-bounded flex column). A naive `height: 100%`\n * therefore doesn't bound to the viewport and the page overflows below the fold\n * (the composer gets pushed off-screen).\n *\n * Instead of trusting the CSS height chain, we MEASURE this element's own top\n * offset and fill from there to the viewport bottom (`innerHeight - rect.top`).\n * That's correct regardless of header height or sidebar, with no magic pixel\n * constants. The element is a flex column that clips, so the chat's own\n * `Viewport` owns the only scroll and the composer pins.\n */\nimport { ReactNode, useLayoutEffect, useRef } from 'react';\nimport { makeStyles } from '@material-ui/core/styles';\n\nconst useStyles = makeStyles(\n {\n root: {\n display: 'flex',\n flexDirection: 'column',\n minHeight: 0,\n overflow: 'hidden',\n },\n },\n { name: 'AssistantsCollapsibleFullHeightRegion' },\n);\n\n/** @public */\nexport function FullHeightRegion(props: {\n className?: string;\n children: ReactNode;\n}) {\n const classes = useStyles();\n const ref = useRef<HTMLDivElement>(null);\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || typeof window === 'undefined') {\n return undefined;\n }\n\n const apply = () => {\n const top = el.getBoundingClientRect().top;\n el.style.height = `${Math.max(0, window.innerHeight - top)}px`;\n };\n\n apply();\n window.addEventListener('resize', apply);\n\n // The page header (our previous sibling in the Content flow) determines our\n // top offset; react to its size changes too. Falls back to resize-only.\n let observer: ResizeObserver | undefined;\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(() => apply());\n const header = el.previousElementSibling;\n if (header) {\n observer.observe(header);\n }\n }\n\n return () => {\n window.removeEventListener('resize', apply);\n observer?.disconnect();\n };\n }, []);\n\n return (\n <div\n ref={ref}\n className={\n props.className ? `${classes.root} ${props.className}` : classes.root\n }\n >\n {props.children}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAiBA,MAAM,SAAA,GAAY,UAAA;AAAA,EAChB;AAAA,IACE,IAAA,EAAM;AAAA,MACJ,OAAA,EAAS,MAAA;AAAA,MACT,aAAA,EAAe,QAAA;AAAA,MACf,SAAA,EAAW,CAAA;AAAA,MACX,QAAA,EAAU;AAAA;AACZ,GACF;AAAA,EACA,EAAE,MAAM,uCAAA;AACV,CAAA;AAGO,SAAS,iBAAiB,KAAA,EAG9B;AACD,EAAA,MAAM,UAAU,SAAA,EAAU;AAC1B,EAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AAEvC,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,IAAA,IAAI,CAAC,EAAA,IAAM,OAAO,MAAA,KAAW,WAAA,EAAa;AACxC,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,QAAQ,MAAM;AAClB,MAAA,MAAM,GAAA,GAAM,EAAA,CAAG,qBAAA,EAAsB,CAAE,GAAA;AACvC,MAAA,EAAA,CAAG,KAAA,CAAM,SAAS,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,EAAG,MAAA,CAAO,WAAA,GAAc,GAAG,CAAC,CAAA,EAAA,CAAA;AAAA,IAC5D,CAAA;AAEA,IAAA,KAAA,EAAM;AACN,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAU,KAAK,CAAA;AAIvC,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AACzC,MAAA,QAAA,GAAW,IAAI,cAAA,CAAe,MAAM,KAAA,EAAO,CAAA;AAC3C,MAAA,MAAM,SAAS,EAAA,CAAG,sBAAA;AAClB,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,QAAA,CAAS,QAAQ,MAAM,CAAA;AAAA,MACzB;AAAA,IACF;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,KAAK,CAAA;AAC1C,MAAA,QAAA,EAAU,UAAA,EAAW;AAAA,IACvB,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EACE,KAAA,CAAM,SAAA,GAAY,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA,CAAA,EAAI,KAAA,CAAM,SAAS,CAAA,CAAA,GAAK,OAAA,CAAQ,IAAA;AAAA,MAGlE,QAAA,EAAA,KAAA,CAAM;AAAA;AAAA,GACT;AAEJ;;;;"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { makeStyles } from '@material-ui/core/styles';
|
|
3
|
+
import { Tooltip, IconButton, Typography } from '@material-ui/core';
|
|
4
|
+
import AddIcon from '@material-ui/icons/Add';
|
|
5
|
+
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
|
|
6
|
+
import { AssistantsList } from './AssistantsList.esm.js';
|
|
7
|
+
import { ConversationsPanel } from './ConversationsPanel.esm.js';
|
|
8
|
+
|
|
9
|
+
const useStyles = makeStyles((theme) => ({
|
|
10
|
+
header: {
|
|
11
|
+
display: "flex",
|
|
12
|
+
alignItems: "center",
|
|
13
|
+
justifyContent: "flex-end",
|
|
14
|
+
minHeight: 44,
|
|
15
|
+
padding: theme.spacing(0, 1),
|
|
16
|
+
borderBottom: `1px solid ${theme.palette.divider}`
|
|
17
|
+
},
|
|
18
|
+
sectionHeader: {
|
|
19
|
+
display: "flex",
|
|
20
|
+
alignItems: "center",
|
|
21
|
+
justifyContent: "space-between",
|
|
22
|
+
padding: theme.spacing(1.5, 0.5, 0, 2)
|
|
23
|
+
},
|
|
24
|
+
sectionLabel: {
|
|
25
|
+
color: theme.palette.text.secondary,
|
|
26
|
+
fontWeight: 600,
|
|
27
|
+
textTransform: "uppercase",
|
|
28
|
+
letterSpacing: 0.5
|
|
29
|
+
},
|
|
30
|
+
divider: {
|
|
31
|
+
margin: theme.spacing(1, 1.5, 0),
|
|
32
|
+
borderTop: `1px solid ${theme.palette.divider}`
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
function SidePane(props) {
|
|
36
|
+
const {
|
|
37
|
+
assistants,
|
|
38
|
+
activeAssistantId,
|
|
39
|
+
onSelectAssistant,
|
|
40
|
+
conversations,
|
|
41
|
+
activeId,
|
|
42
|
+
onNew,
|
|
43
|
+
onSelect,
|
|
44
|
+
onRename,
|
|
45
|
+
onPin,
|
|
46
|
+
onDelete,
|
|
47
|
+
onCollapse
|
|
48
|
+
} = props;
|
|
49
|
+
const classes = useStyles();
|
|
50
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
51
|
+
/* @__PURE__ */ jsx("div", { className: classes.header, children: /* @__PURE__ */ jsx(Tooltip, { title: "Collapse", placement: "bottom", children: /* @__PURE__ */ jsx(
|
|
52
|
+
IconButton,
|
|
53
|
+
{
|
|
54
|
+
size: "small",
|
|
55
|
+
"aria-label": "Collapse AI chat sidebar",
|
|
56
|
+
onClick: onCollapse,
|
|
57
|
+
children: /* @__PURE__ */ jsx(ChevronLeftIcon, { fontSize: "small" })
|
|
58
|
+
}
|
|
59
|
+
) }) }),
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
AssistantsList,
|
|
62
|
+
{
|
|
63
|
+
assistants,
|
|
64
|
+
activeId: activeAssistantId,
|
|
65
|
+
onSelect: onSelectAssistant
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsx("div", { className: classes.divider }),
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: classes.sectionHeader, children: [
|
|
70
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: classes.sectionLabel, children: "Conversations" }),
|
|
71
|
+
/* @__PURE__ */ jsx(Tooltip, { title: "New chat", placement: "bottom", children: /* @__PURE__ */ jsx(IconButton, { size: "small", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx(AddIcon, { fontSize: "small" }) }) })
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ jsx(
|
|
74
|
+
ConversationsPanel,
|
|
75
|
+
{
|
|
76
|
+
assistantId: activeAssistantId,
|
|
77
|
+
conversations,
|
|
78
|
+
activeId,
|
|
79
|
+
onSelect,
|
|
80
|
+
onRename,
|
|
81
|
+
onPin,
|
|
82
|
+
onDelete
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
] });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { SidePane };
|
|
89
|
+
//# sourceMappingURL=SidePane.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SidePane.esm.js","sources":["../../src/collapsible/SidePane.tsx"],"sourcesContent":["import { makeStyles } from '@material-ui/core/styles';\nimport { IconButton, Tooltip, Typography } from '@material-ui/core';\nimport AddIcon from '@material-ui/icons/Add';\nimport ChevronLeftIcon from '@material-ui/icons/ChevronLeft';\nimport { AssistantSummary } from '@drewswiredin/backstage-plugin-assistants-common';\nimport { AssistantsList } from './AssistantsList';\nimport { ConversationsPanel } from './ConversationsPanel';\nimport type { Conversation } from './useConversations';\n\nconst useStyles = makeStyles(theme => ({\n header: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n minHeight: 44,\n padding: theme.spacing(0, 1),\n borderBottom: `1px solid ${theme.palette.divider}`,\n },\n sectionHeader: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: theme.spacing(1.5, 0.5, 0, 2),\n },\n sectionLabel: {\n color: theme.palette.text.secondary,\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n },\n divider: {\n margin: theme.spacing(1, 1.5, 0),\n borderTop: `1px solid ${theme.palette.divider}`,\n },\n}));\n\n/**\n * Props for {@link SidePane}.\n *\n * @public\n */\nexport interface SidePaneProps {\n /** All assistants the caller may access (top tier — the switcher). */\n assistants: AssistantSummary[];\n /** The currently active assistant id. */\n activeAssistantId: string;\n /** Switch assistant (drives `?assistant=<id>`). */\n onSelectAssistant: (id: string) => void;\n conversations: Conversation[];\n activeId: string | null;\n onNew: () => void;\n onSelect: (id: string | null) => void;\n onRename: (id: string, title: string) => void;\n onPin: (id: string) => void;\n onDelete: (id: string) => void;\n onCollapse: () => void;\n}\n\n/**\n * The expanded (320px) left sidebar: a collapse header, the\n * {@link AssistantsList} switcher on top, then the conversation list (with a\n * \"new chat\" + next to the section label). Adapted from Implementation 1's side\n * pane (the tools list moved into the assistant detail dialog).\n *\n * @public\n */\nexport function SidePane(props: SidePaneProps) {\n const {\n assistants,\n activeAssistantId,\n onSelectAssistant,\n conversations,\n activeId,\n onNew,\n onSelect,\n onRename,\n onPin,\n onDelete,\n onCollapse,\n } = props;\n const classes = useStyles();\n\n return (\n <div>\n <div className={classes.header}>\n <Tooltip title=\"Collapse\" placement=\"bottom\">\n <IconButton\n size=\"small\"\n aria-label=\"Collapse AI chat sidebar\"\n onClick={onCollapse}\n >\n <ChevronLeftIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </div>\n\n <AssistantsList\n assistants={assistants}\n activeId={activeAssistantId}\n onSelect={onSelectAssistant}\n />\n\n <div className={classes.divider} />\n\n <div className={classes.sectionHeader}>\n <Typography variant=\"caption\" className={classes.sectionLabel}>\n Conversations\n </Typography>\n <Tooltip title=\"New chat\" placement=\"bottom\">\n <IconButton size=\"small\" aria-label=\"New chat\" onClick={onNew}>\n <AddIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </div>\n <ConversationsPanel\n assistantId={activeAssistantId}\n conversations={conversations}\n activeId={activeId}\n onSelect={onSelect}\n onRename={onRename}\n onPin={onPin}\n onDelete={onDelete}\n />\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useTheme, makeStyles } from '@material-ui/core/styles';
|
|
3
|
+
import { BackstageLogo } from './BackstageLogo.esm.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_AVATAR_COLOR = "#7df3e1";
|
|
6
|
+
const clamp = (n, min, max) => Math.min(Math.max(n, min), max);
|
|
7
|
+
function hexToRgb(hex) {
|
|
8
|
+
let h = hex.trim().replace(/^#/, "");
|
|
9
|
+
if (h.length === 3) {
|
|
10
|
+
h = h.split("").map((c) => c + c).join("");
|
|
11
|
+
}
|
|
12
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const num = parseInt(h, 16);
|
|
16
|
+
return [num >> 16 & 255, num >> 8 & 255, num & 255];
|
|
17
|
+
}
|
|
18
|
+
function rgbToHsl(r, g, b) {
|
|
19
|
+
const [rn, gn, bn] = [r / 255, g / 255, b / 255];
|
|
20
|
+
const max = Math.max(rn, gn, bn);
|
|
21
|
+
const min = Math.min(rn, gn, bn);
|
|
22
|
+
const d = max - min;
|
|
23
|
+
let h = 0;
|
|
24
|
+
if (d !== 0) {
|
|
25
|
+
if (max === rn) {
|
|
26
|
+
h = (gn - bn) / d % 6;
|
|
27
|
+
} else if (max === gn) {
|
|
28
|
+
h = (bn - rn) / d + 2;
|
|
29
|
+
} else {
|
|
30
|
+
h = (rn - gn) / d + 4;
|
|
31
|
+
}
|
|
32
|
+
h = h * 60;
|
|
33
|
+
if (h < 0) {
|
|
34
|
+
h += 360;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const l = (max + min) / 2;
|
|
38
|
+
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
|
39
|
+
return [h, s, l];
|
|
40
|
+
}
|
|
41
|
+
function hslToHex(h, s, l) {
|
|
42
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
43
|
+
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
44
|
+
const m = l - c / 2;
|
|
45
|
+
let rgb;
|
|
46
|
+
if (h < 60) rgb = [c, x, 0];
|
|
47
|
+
else if (h < 120) rgb = [x, c, 0];
|
|
48
|
+
else if (h < 180) rgb = [0, c, x];
|
|
49
|
+
else if (h < 240) rgb = [0, x, c];
|
|
50
|
+
else if (h < 300) rgb = [x, 0, c];
|
|
51
|
+
else rgb = [c, 0, x];
|
|
52
|
+
const channel = (v) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
|
|
53
|
+
return `#${channel(rgb[0])}${channel(rgb[1])}${channel(rgb[2])}`;
|
|
54
|
+
}
|
|
55
|
+
function resolveAssistantColor(color, themeType) {
|
|
56
|
+
const base = color ?? DEFAULT_AVATAR_COLOR;
|
|
57
|
+
const rgb = hexToRgb(base);
|
|
58
|
+
if (!rgb) {
|
|
59
|
+
return base;
|
|
60
|
+
}
|
|
61
|
+
const [h, s, l] = rgbToHsl(rgb[0], rgb[1], rgb[2]);
|
|
62
|
+
const adjustedL = themeType === "dark" ? clamp(l, 0.6, 0.82) : clamp(l, 0.3, 0.46);
|
|
63
|
+
return hslToHex(h, s, adjustedL);
|
|
64
|
+
}
|
|
65
|
+
const useStyles = makeStyles({
|
|
66
|
+
// Transparent — the tinted logo "floats", no disc fill.
|
|
67
|
+
disc: {
|
|
68
|
+
display: "inline-flex",
|
|
69
|
+
alignItems: "center",
|
|
70
|
+
justifyContent: "center",
|
|
71
|
+
flexShrink: 0
|
|
72
|
+
},
|
|
73
|
+
logo: {
|
|
74
|
+
height: "100%",
|
|
75
|
+
width: "auto"
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
function AssistantAvatar({
|
|
79
|
+
color,
|
|
80
|
+
size = 28,
|
|
81
|
+
className
|
|
82
|
+
}) {
|
|
83
|
+
const classes = useStyles();
|
|
84
|
+
const theme = useTheme();
|
|
85
|
+
const resolved = resolveAssistantColor(color, theme.palette.type);
|
|
86
|
+
return /* @__PURE__ */ jsx(
|
|
87
|
+
"span",
|
|
88
|
+
{
|
|
89
|
+
"aria-hidden": true,
|
|
90
|
+
className: className ? `${classes.disc} ${className}` : classes.disc,
|
|
91
|
+
style: { width: size, height: size, color: resolved },
|
|
92
|
+
children: /* @__PURE__ */ jsx(BackstageLogo, { className: classes.logo })
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { AssistantAvatar, DEFAULT_AVATAR_COLOR, resolveAssistantColor };
|
|
98
|
+
//# sourceMappingURL=AssistantAvatar.esm.js.map
|