Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion plugins/example-plugin/web/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ instance Studio renders — same behavior, same styles, no second copy in the
bundle. See `src/SharedUiPage.tsx`.

```ts
import { StudioDataView, useStudioDataViewState } from '@nemo/common';
import { AssistantChat, StudioDataView, useStudioDataViewState } from '@nemo/common';
```

- **Bare specifier only.** A deep `@nemo/common/src/...` import is not
Expand All @@ -92,6 +92,11 @@ import { StudioDataView, useStudioDataViewState } from '@nemo/common';
fine (they erase). A shared component that needs data takes it as a prop or
callback and the caller supplies it from `host.sdk`; `CreateSecretModal`'s
`onCreate` and `fetchAllPages`' page fetcher are the pattern.
- **`AssistantChat` is shared.** A plugin can point it at an authenticated,
OpenAI-compatible `baseURL`; Studio supplies its current access token and
chat runtime. Use `messageContentProps.markdownLinkComponent` when a plugin
owns trusted, in-app citation targets. The plugin should still own the panel,
prompts, endpoint, and citation behavior specific to its feature.
Comment thread
marcusds marked this conversation as resolved.
- **Types come from source**, via `paths` in `tsconfig.json`; `@nemo/common` is
unpublished, so there is nothing to install. `src/env.d.ts` declares the `*.css`
side-effect imports those sources carry.
Expand Down
23 changes: 23 additions & 0 deletions web/packages/common/src/components/AssistantChat/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,29 @@ describe('AssistantChat', () => {
interactionTimeoutMs
);

it('allows a caller to render trusted Markdown links in messages', async () => {
mocks.createChatCompletion.mockResolvedValueOnce(
createCompletion('[Trace source](#zoomer-node=summary-1)')
);
renderAssistantChat(
<AssistantChat
model="test-model"
workspace="default"
messageContentProps={{
markdownLinkComponent: ({ href, children }) => <a href={href}>{children}</a>,
}}
/>
);

await userEvent.type(screen.getByRole('textbox', { name: /Task prompt/i }), 'Show evidence');
await userEvent.click(screen.getByRole('button', { name: /Submit/i }));

expect(await screen.findByRole('link', { name: 'Trace source' })).toHaveAttribute(
'href',
'#zoomer-node=summary-1'
);
});

it('renders base64 images returned by an image model stream', async () => {
const imageUrl = 'data:image/png;base64,iVBORw0KGgo=';
const stream = {
Expand Down
2 changes: 2 additions & 0 deletions web/packages/common/src/components/AssistantChat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const AssistantChat: FC<AssistantChatProps> = ({
stopCount,
slotComposerStart,
emptyState,
messageContentProps,
composerOverride,
enableImageAttachments = true,
}) => {
Expand Down Expand Up @@ -76,6 +77,7 @@ export const AssistantChat: FC<AssistantChatProps> = ({
composerMode={composerMode}
slotComposerStart={slotComposerStart}
emptyState={emptyState}
messageContentProps={messageContentProps}
composerOverride={composerOverride}
enableImageAttachments={imageAttachmentsEnabled}
/>
Expand Down
2 changes: 2 additions & 0 deletions web/packages/common/src/components/AssistantChat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ export interface AssistantChatProps {
slotHeading?: string;
slotSubheading?: string;
};
/** Overrides used when rendering Markdown inside chat messages. */
messageContentProps?: AssistantChatMessageContentProps;
composerOverride?: ReactNode;
/**
* @default true
Expand Down
5 changes: 5 additions & 0 deletions web/packages/common/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
// Removals are breaking. Explicit exports, not `export *`.

export { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
export { AssistantChat } from '@nemo/common/src/components/AssistantChat';
export type {
AssistantChatMessageContentProps,
AssistantChatProps,
} from '@nemo/common/src/components/AssistantChat/types';
export { AccordionSection } from '@nemo/common/src/components/AccordionSection';
// CancelJobButton is deliberately NOT exported
export { ConfirmationModal } from '@nemo/common/src/components/ConfirmationModal';
Expand Down