Skip to content

Add reusable OzwellChat component - #368

Open
aditya-damerla128 wants to merge 5 commits into
mieweb:mainfrom
aditya-damerla128:feat/ozwell-chat-component
Open

Add reusable OzwellChat component#368
aditya-damerla128 wants to merge 5 commits into
mieweb:mainfrom
aditya-damerla128:feat/ozwell-chat-component

Conversation

@aditya-damerla128

@aditya-damerla128 aditya-damerla128 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Closes #363

Summary

Adds the Ozwell widget directly to @mieweb/ui, following the same UI-library approach as the Q chat component.

  • Adds the reusable, UI-only OzwellChat component and public exports.
  • Ports the current widget's visible chat states: welcome, pending and streaming messages, Markdown, thinking modes, tool results, fallback warning, queued messages, message navigation, and model selection.
  • Adds Storybook documentation, state exploration, and a local interactive mock conversation.
  • Fixes AIChat automatic scrolling so it scrolls only its own message container instead of moving the surrounding Docs page.

The Ozwell API adapter remains responsible for requests, streaming parsing, authentication, model discovery, tools, and iframe/window behavior.

Checks

  • pnpm typecheck
  • pnpm lint
  • git diff --check origin/main...HEAD
image

Copilot AI lite review requested due to automatic review settings August 11, 2026 21:07
@aditya-damerla128
aditya-damerla128 marked this pull request as ready for review August 11, 2026 21:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new reusable, UI-only OzwellChat widget shell to @mieweb/ui, plus Storybook demos, and adjusts AIChat auto-scrolling to scroll only within its own message container (avoiding Storybook/Docs page scrolling).

Changes:

  • Introduces OzwellChat (thinking controls, message navigation, warning toast, model selector integration) built on top of AIChat.
  • Adds comprehensive Storybook stories including an interactive local mock conversation and state explorer.
  • Updates AIChat auto-scroll implementation to use the internal messages container ref.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/components/AI/OzwellChatView.tsx New OzwellChat UI shell component; thinking/menu/model selector/warning/footer composition.
src/components/AI/OzwellChat.stories.tsx Storybook documentation, state explorer, and interactive playground for OzwellChat.
src/components/AI/index.ts Public exports for OzwellChat and associated types.
src/components/AI/AIChat.tsx Adjusts auto-scroll behavior to scroll only the messages container.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/components/AI/OzwellChatView.tsx
Comment thread src/components/AI/OzwellChatView.tsx
Comment thread src/components/AI/OzwellChatView.tsx Outdated
Copilot AI review requested due to automatic review settings August 11, 2026 22:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/components/AI/OzwellChatView.tsx:125

  • OzwellChat is exported from a file named OzwellChatView.tsx. This *View suffix isn’t used elsewhere in src/components/AI and makes it harder to find the component by name (and to grep/auto-import reliably). Consider renaming the file to OzwellChat.tsx and updating the barrel/story imports accordingly.
export function OzwellChat({
  messages,
  isGenerating = false,
  inputPlaceholder = 'Ask a question...',
  onSendMessage,

src/components/AI/OzwellChatView.tsx:119

  • applyThinkingMode contains non-trivial transformation/filtering logic (e.g., hiding thinking blocks, auto-collapsing based on streaming status, and dropping empty messages). There aren’t any unit tests for this behavior yet, which makes regressions in chat rendering likely as the adapter evolves. Adding a focused OzwellChatView.test.tsx (or extracting this function to a tested helper) would improve confidence.
function applyThinkingMode(
  messages: AIMessage[],
  mode: OzwellThinkingMode
): AIMessage[] {
  return messages
    .map((message) => {
      const hasTextContent = message.content.some(
        (block) => block.type === 'text' && Boolean(block.text)
      );
      const content = message.content
        .filter((block) => mode !== 'never' || block.type !== 'thinking')
        .map((block) => {
          if (block.type !== 'thinking') return block;
          return {
            ...block,
            collapsed:
              mode === 'collapsed' ||
              (mode === 'auto' &&
                (message.status !== 'streaming' || hasTextContent)),
          };
        });

      return { ...message, content };
    })
    .filter(
      (message) =>
        message.status === 'streaming' ||
        message.content.length > 0 ||
        message.role === 'tool'
    );
}

Copilot AI review requested due to automatic review settings August 12, 2026 21:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/components/AI/OzwellChatView.tsx:119

  • applyThinkingMode introduces non-trivial behavior (hiding/collapsing thinking blocks and filtering empty messages) but the PR adds no unit tests to lock down these rules. Since this is a UI-library component, a small RTL/Vitest test suite would help prevent regressions when message types/statuses evolve.
function applyThinkingMode(
  messages: AIMessage[],
  mode: OzwellThinkingMode
): AIMessage[] {
  return messages
    .map((message) => {
      const hasTextContent = message.content.some(
        (block) => block.type === 'text' && Boolean(block.text)
      );
      const content = message.content
        .filter((block) => mode !== 'never' || block.type !== 'thinking')
        .map((block) => {
          if (block.type !== 'thinking') return block;
          return {
            ...block,
            collapsed:
              mode === 'collapsed' ||
              (mode === 'auto' &&
                (message.status !== 'streaming' || hasTextContent)),
          };
        });

      return { ...message, content };
    })
    .filter(
      (message) =>
        message.status === 'streaming' ||
        message.content.length > 0 ||
        message.role === 'tool'
    );
}

src/components/AI/OzwellChatView.tsx:362

  • inputPlaceholder is documented as a host-controlled placeholder, but when the model selector is shown the component always forces 'Ask a question...', ignoring the prop value. This makes it impossible for consumers to customize placeholder text whenever models are enabled.
        inputPlaceholder={
          showModelSelector ? 'Ask a question...' : inputPlaceholder
        }

@aditya-damerla128
aditya-damerla128 force-pushed the feat/ozwell-chat-component branch from 577b6f0 to ca00952 Compare August 13, 2026 13:51
Copilot AI review requested due to automatic review settings August 13, 2026 13:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/components/AI/AIChat.tsx:337

  • Auto-scroll sets scrollTop to scrollHeight, which relies on browser clamping (the maximum scrollTop is scrollHeight - clientHeight). Setting the computed max explicitly is more correct and avoids edge cases when scrollHeight is smaller than the viewport.
  // Auto-scroll to bottom on new messages
  React.useEffect(() => {
    const container = messagesContainerRef.current;
    if (container) container.scrollTop = container.scrollHeight;
  }, [messages]);

@garrity-miepub

garrity-miepub commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Two style issues to fix before merge — screenshots below are from this branch in Storybook (State Explorer and Tool Result + Warning stories):

1. Hardcoded palette

OzwellChatView.tsx uses literal colors throughout (bg-[#f7f9fc] shell, slate-* text/borders, amber-* warning strip, raw rgba in the flare keyframes), bypassing the brand token system. The component won't re-theme under any brand or dark mode — the Storybook brand/theme switchers have no effect on it.
ozwellchat-welcome
ozwellchat-tool-result-warning

Please swap for semantic tokens (bg-surface, text-foreground, border-border, warning/primary scales).

2. Inline <style> keyframes

The injected @keyframes ozwell-message-flare duplicates per mounted instance and breaks under strict CSP (style-src) — relevant for embedded hosts. Move it to theme.extend.keyframes/animation in src/tailwind-preset.ts so it ships with the design system CSS (and the flare color can come from a token instead of rgba(15, 116, 156, …)).

Copilot AI review requested due to automatic review settings August 14, 2026 15:18
@aditya-damerla128

Copy link
Copy Markdown
Contributor Author

Addressed in 8faf0a9:\n\n- Replaced fixed Ozwell colors with semantic theme tokens.\n- Moved the message-flare keyframes and animation to src/tailwind-preset.ts, using the primary token instead of an inline style.\n\nLocal Storybook build passes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/components/AI/OzwellChatView.tsx:280

  • OzwellChat always passes a renderTextContent function to AIChat (via renderMessageTextContent). When the host does not supply renderTextContent, this wrapper returns a raw string, which causes AIChat to take the custom-renderer path and lose its default whitespace-pre-wrap handling (newlines will collapse). Preserve the default behavior when no host renderer is provided.
  const renderMessageTextContent: AIRenderTextContent = (text, context) => {
    if (context.messageId === QUEUED_MESSAGE_ID && isEditingQueuedMessage) {
      return (
        <textarea
          aria-label="Edit queued message"
          ref={queuedMessageEditorRef}
          className="border-border bg-card text-foreground focus-visible:ring-ring block min-h-20 w-full resize-y rounded border px-2 py-1.5 text-sm outline-none focus-visible:ring-2"
          value={queuedMessageDraft}
          onChange={(event) => setQueuedMessageDraft(event.target.value)}
          onKeyDown={(event) => {
            if (event.key === 'Escape') setIsEditingQueuedMessage(false);
            if (event.key === 'Enter' && !event.shiftKey) {
              event.preventDefault();
              saveQueuedMessage();
            }
          }}
        />
      );
    }

    return renderTextContent?.(text, context) ?? text;
  };

Copilot AI review requested due to automatic review settings August 14, 2026 15:35
@aditya-damerla128
aditya-damerla128 force-pushed the feat/ozwell-chat-component branch from 8faf0a9 to 1573a89 Compare August 14, 2026 15:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/components/AI/OzwellChatView.tsx:492

  • OzwellChatProps exposes inputPlaceholder, but when the model selector is shown the component ignores the prop and hardcodes 'Ask a question...'. This makes the prop ineffective for the common “models enabled” state and is surprising for API consumers trying to customize placeholder copy.
        inputPlaceholder={
          showModelSelector ? 'Ask a question...' : inputPlaceholder
        }
        onSendMessage={onSendMessage}

Copilot AI review requested due to automatic review settings August 14, 2026 17:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 14, 2026 19:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/components/AI/OzwellChatView.tsx:66

  • QUEUED_MESSAGE_ID is a fixed string. If a host ever provides a real message with the same id, React keys will collide and the queued-message special-casing (edit footer / textarea renderer) could apply to the wrong message. Consider using a more namespaced internal id to minimize collision risk.
const QUEUED_MESSAGE_ID = 'ozwell-queued-message';

src/components/AI/OzwellChatView.tsx:496

  • inputPlaceholder is ignored whenever showModelSelector is true (it always forces 'Ask a question...'). This makes the inputPlaceholder prop ineffective for hosts that show the model selector.
        inputPlaceholder={
          showModelSelector ? 'Ask a question...' : inputPlaceholder
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Ozwell chat component

3 participants