npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gzmagyari/copilot-chat-widget

v1.0.19

Published

Reusable Vue 3 chat widget component for AI agent conversations with SSE streaming support

Readme

Copilot Chat Widget

A fully independent, reusable chat widget component for AI agent conversations with Server-Sent Events (SSE) streaming support.

Vue 2 & Vue 3 Compatible - Works with both Vue 2.6+ or Vue 3.4+ and Vuetify 2.0+ or Vuetify 3.6+. Uses Options API syntax for maximum compatibility.

Features

Zero Dependencies - No Vuex/Pinia required, fully self-contained ✅ SSE Streaming - Real-time streaming with Server-Sent Events ✅ Thread Management - Create, load, and switch between conversation threads ✅ Message Editing - Edit previous messages and re-run conversations ✅ Tool Call Visualization - Display AI tool/function calls with request/response details ✅ Reasoning Display - Support for o1/o3 style reasoning summaries ✅ Configurable - API base URL via props for cross-origin support ✅ Vuetify UI - Beautiful Material Design interface

Installation

From NPM

npm install @gzmagyari/copilot-chat-widget

From GitHub Packages

npm install @gzmagyari/copilot-chat-widget --registry=https://npm.pkg.github.com

Peer Dependencies

For Vue 3 projects:

{
  "vue": "^3.4.0",
  "vuetify": "^3.6.0"
}

For Vue 2 projects:

{
  "vue": "^2.6.0",
  "vuetify": "^2.0.0"
}

Usage

Basic Example

<template>
  <div>
    <v-btn @click="showChat = true">Open Chat</v-btn>

    <ChatWidget v-model="showChat" :agent-id="123" api-base-url="" />
  </div>
</template>

<script>
import { ChatWidget } from "@gzmagyari/copilot-chat-widget";

export default {
  components: { ChatWidget },
  data() {
    return {
      showChat: false
    };
  }
};
</script>

With Custom API URL

<template>
  <ChatWidget
    v-model="showChat"
    :agent-id="agentId"
    api-base-url="https://api.example.com"
  />
</template>

<script>
import { ChatWidget } from "@gzmagyari/copilot-chat-widget";

export default {
  components: { ChatWidget },
  data() {
    return {
      showChat: true,
      agentId: 456
    };
  }
};
</script>

With Variables (e.g., API Keys)

<template>
  <ChatWidget
    v-model="showChat"
    :agent-id="agentId"
    api-base-url="https://api.example.com"
    :variables="chatVariables"
  />
</template>

<script>
import { ChatWidget } from "@gzmagyari/copilot-chat-widget";

export default {
  components: { ChatWidget },
  data() {
    return {
      showChat: true,
      agentId: 456,
      chatVariables: {
        APIKey: "your-api-key-here",
        UserSettings: { theme: "dark" }
      }
    };
  }
};
</script>

With Thread Persistence (URL Parameters)

<template>
  <ChatWidget
    v-model="showChat"
    :agent-id="agentId"
    :variables="chatVariables"
    :initial-thread-id="threadId"
    @thread-changed="handleThreadChanged"
  />
</template>

<script>
import { ChatWidget } from "@gzmagyari/copilot-chat-widget";

export default {
  components: { ChatWidget },
  data() {
    return {
      showChat: true,
      agentId: 456,
      chatVariables: { APIKey: "key-123" },
      // Read thread ID from URL on mount
      threadId:
        new URLSearchParams(window.location.search).get("thread") || null
    };
  },
  methods: {
    handleThreadChanged(newThreadId) {
      // Update URL parameter when thread changes
      const url = new URL(window.location.href);
      url.searchParams.set("thread", newThreadId);
      window.history.pushState({}, "", url);
      this.threadId = newThreadId;
    }
  }
};
</script>

Secure External Usage (Production with Authentication)

<template>
  <ChatWidget
    v-model="showChat"
    :agent-id="agentId"
    api-base-url="https://api.example.com"
    :variables="chatVariables"
    :initial-thread-id="threadId"
    :authentication-key="userAuthKey"
    @thread-changed="handleThreadChanged"
  />
</template>

<script>
import { ChatWidget } from "@gzmagyari/copilot-chat-widget";

export default {
  components: { ChatWidget },
  data() {
    return {
      showChat: true,
      agentId: 456,
      chatVariables: {
        APIKey: "user-specific-api-key-from-backend"
      },
      threadId: localStorage.getItem("chatThreadId") || null,
      // Generate and store unique auth key per user
      userAuthKey: this.getOrCreateAuthKey()
    };
  },
  methods: {
    getOrCreateAuthKey() {
      // Check if user already has an auth key
      let key = localStorage.getItem("chatAuthKey");
      if (!key) {
        // Generate UUID v4
        key = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
          /[xy]/g,
          function (c) {
            const r = (Math.random() * 16) | 0;
            const v = c == "x" ? r : (r & 0x3) | 0x8;
            return v.toString(16);
          }
        );
        localStorage.setItem("chatAuthKey", key);
      }
      return key;
    },
    handleThreadChanged(newThreadId) {
      // Store thread ID for persistence
      localStorage.setItem("chatThreadId", newThreadId);
      this.threadId = newThreadId;
    }
  }
};
</script>

Security Benefits:

  • ✅ Each user gets a unique authenticationKey (stored in localStorage)
  • ✅ Threads are linked to this key - only the creator can access them
  • ✅ Thread IDs can't be hijacked - auth key is required for all operations
  • ✅ Variables (like APIKey) are protected since the thread itself is secured
  • ✅ Thread list only shows threads belonging to this user

Using Both Components

<template>
  <div>
    <!-- Full chat panel with thread management -->
    <ChatWidget v-model="showPanel" :agent-id="agentId" />

    <!-- Or just the chat interface component -->
    <OpenAIChatWidget
      :messages="messages"
      :streaming-content="streamingContent"
      :can-send="true"
      @send-message="handleSend"
    />
  </div>
</template>

<script>
import { ChatWidget, OpenAIChatWidget } from "@gzmagyari/copilot-chat-widget";

export default {
  components: { ChatWidget, OpenAIChatWidget }
  // ...
};
</script>

Props

ChatWidget

| Prop | Type | Required | Default | Description | | ------------------- | ---------------- | -------- | ------- | --------------------------------------------------------- | | modelValue | Boolean | No | true | v-model for panel visibility | | agentId | String\|Number | Yes | - | Agent ID for API calls | | apiBaseUrl | String | No | '' | Base URL for API endpoints (empty = same origin) | | variables | Object | No | {} | Variables to pass to the agent (e.g., API keys, settings) | | initialThreadId | String\|Number | No | null | Thread ID to load on mount (for persistence) | | authenticationKey | String | No | null | Unique key for secure thread access (e.g., UUID per user) |

OpenAIChatWidget

| Prop | Type | Required | Default | Description | | ---------------------- | --------- | -------- | --------------------- | ------------------------------ | | messages | Array | Yes | - | Array of message objects | | streamingContent | String | No | '' | Content being streamed | | streamingToolCalls | Array | No | [] | Tool calls being streamed | | canSend | Boolean | No | false | Whether user can send messages | | isWaitingForResponse | Boolean | No | false | Show loading state | | isThinking | Boolean | No | false | Show thinking indicator | | placeholder | String | No | 'Type a message...' | Input placeholder text |

Events

ChatWidget

| Event | Payload | Description | | ------------------- | --------- | ----------------------------------------------------------------------- | | update:modelValue | Boolean | Panel visibility changed (v-model) | | thread-changed | Number | Thread selection changed (new thread created or existing thread loaded) |

OpenAIChatWidget

| Event | Payload | Description | | ---------------- | ---------------------- | ---------------------- | | send-message | String | User sent a message | | edit-message | {messageId, content} | User edited a message | | stop-streaming | - | User stopped streaming |

API Endpoints Required

Your backend must implement these endpoints:

Threads

  • GET /api/threads?agent_id={agentId} - List threads
  • POST /api/agents/{agentId}/threads - Create thread
  • GET /api/threads/{threadId} - Get thread details
  • POST /api/threads/{threadId}/abort - Abort streaming

Messages

  • GET /api/threads/{threadId}/messages - Get messages
  • POST /api/threads/{threadId}/messages - Send message
  • PATCH /api/threads/{threadId}/messages/{messageId} - Edit message
  • DELETE /api/threads/{threadId}/messages/after/{messageId} - Delete after message
  • POST /api/threads/{threadId}/cleanup-incomplete - Cleanup incomplete messages

Streaming

  • GET /api/threads/{threadId}/run/stream - SSE streaming endpoint

SSE Event Format

The streaming endpoint should send these events:

event: thinking.start
data: {}

event: content.delta
data: {"token": "Hello"}

event: tool_call.delta
data: {"id": "call_123", "name": "get_weather", "arguments_delta": "{"}

event: tool_call.complete
data: {"id": "call_123"}

event: api.request
data: {"tool_call_id": "call_123", "url": "https://api.weather.com"}

event: api.response
data: {"tool_call_id": "call_123", "status": 200, "body_snippet": {...}}

event: tool_status
data: {"tool_call_id": "call_123", "status": "completed", "status_message": "Success"}

event: assistant.message.final
data: {}

event: stream.aborted
data: {}

Message Format

Messages should follow this structure:

{
  id: 123,
  role: "user" | "assistant" | "tool",
  content: "Message text",          // For user/assistant text
  content_json: {                    // For tool calls/responses
    tool_calls: [{
      id: "call_123",
      type: "function",
      function: {
        name: "get_weather",
        arguments: "{\"city\": \"NYC\"}",
        request: { /* HTTP request */ },
        response: { /* HTTP response */ }
      }
    }]
  },
  tool_call_id: "call_123",         // For tool role messages
  isToolCall: true                   // Helper flag
}

Development

Local Development

If you're working on this component locally within the CopilotClone repo:

// Use relative import
import ChatWidget from "./components/ChatWidget/ChatWidget.vue";

Publishing Updates

# Navigate to the component directory
cd frontend/src/components/ChatWidget

# Bump version
npm version patch  # or minor, or major

# Publish to NPM
npm publish --access public

# Or publish to GitHub Packages
npm publish --registry=https://npm.pkg.github.com

Using in Other Projects

After publishing, install in your other Vue projects:

npm install @gzmagyari/copilot-chat-widget

Customization with Slots

The chat widget supports extensive customization through Vue slots. You can override the default rendering for any message type or indicator.

Available Slots

Message Display Slots

| Slot Name | Scope Props | Description | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------ | | message-user | { message, content } | Customize user message display | | message-assistant | { message, content } | Customize assistant text message display | | message-tool-call | { message?, toolCall, summary, openDetails, isStreaming? } | Customize tool call display (includes function name and arguments) | | message-tool-response | { message?, toolCall?, response, summary, openDetails } | Customize tool response display (includes HTTP response data) | | message-tool-status | { status, toolCall } | Customize tool status updates during execution |

Indicator Slots

| Slot Name | Scope Props | Description | | --------------------- | -------------- | -------------------------------------- | | indicator-thinking | {} | Customize the "thinking" indicator | | indicator-waiting | {} | Customize the waiting/typing animation | | indicator-executing | { toolCall } | Customize the tool execution indicator | | streaming-content | { content } | Customize streaming content display |

Customization Examples

Custom Tool Response Display

Display code diffs or structured data based on the tool that was called:

<template>
  <ChatWidget v-model="showChat" :agent-id="agentId" :variables="chatVariables">
    <!-- Custom tool response rendering -->
    <template
      #message-tool-response="{ toolCall, response, summary, openDetails }"
    >
      <!-- Show code diff for code editing tools -->
      <div v-if="toolCall.function.name === 'edit_code'" class="code-diff-card">
        <CodeDiffViewer
          :old-code="response.original"
          :new-code="response.modified"
          :language="response.language"
        />
        <v-btn small text @click="openDetails">View Raw JSON</v-btn>
      </div>

      <!-- Show order card for order creation -->
      <div
        v-else-if="toolCall.function.name === 'create_order'"
        class="order-card"
      >
        <OrderSummaryCard :order="response.order" />
        <v-chip small color="success">Order #{{ response.order.id }}</v-chip>
      </div>

      <!-- Show image for image generation tools -->
      <div v-else-if="toolCall.function.name === 'generate_image'">
        <v-img :src="response.image_url" max-width="400" class="rounded" />
        <v-btn small text @click="openDetails">View Details</v-btn>
      </div>

      <!-- Fallback to default display -->
      <div v-else class="tool-summary" @click="openDetails">
        <span>Tool response: {{ summary }}</span>
        <span class="click-hint">Click for details</span>
      </div>
    </template>
  </ChatWidget>
</template>

<script>
import { ChatWidget } from "@gzmagyari/copilot-chat-widget";
import CodeDiffViewer from "./CodeDiffViewer.vue";
import OrderSummaryCard from "./OrderSummaryCard.vue";

export default {
  components: { ChatWidget, CodeDiffViewer, OrderSummaryCard },
  data() {
    return {
      showChat: true,
      agentId: 123,
      chatVariables: {}
    };
  }
};
</script>

Custom Tool Call Display

Display tool calls with custom icons and formatting:

<template>
  <ChatWidget :agent-id="agentId">
    <template #message-tool-call="{ toolCall, openDetails, isStreaming }">
      <div class="custom-tool-call" @click="openDetails">
        <v-icon :color="getToolIcon(toolCall.function.name).color">
          {{ getToolIcon(toolCall.function.name).icon }}
        </v-icon>
        <span class="tool-name">{{ toolCall.function.name }}</span>
        <v-chip v-if="isStreaming" x-small color="orange">streaming</v-chip>
        <span class="click-hint">Click for details</span>
      </div>
    </template>
  </ChatWidget>
</template>

<script>
export default {
  methods: {
    getToolIcon(toolName) {
      const icons = {
        search_web: { icon: "mdi-magnify", color: "blue" },
        edit_code: { icon: "mdi-code-braces", color: "purple" },
        create_order: { icon: "mdi-cart", color: "green" },
        send_email: { icon: "mdi-email", color: "red" }
      };
      return icons[toolName] || { icon: "mdi-tools", color: "grey" };
    }
  }
};
</script>

Custom Message Display

Customize how user or assistant messages are rendered:

<template>
  <ChatWidget :agent-id="agentId">
    <!-- Custom user message with avatar -->
    <template #message-user="{ message, content }">
      <div class="custom-user-msg">
        <v-avatar size="32" class="mr-2">
          <img :src="userAvatar" />
        </v-avatar>
        <div class="message-bubble user-bubble">
          {{ content }}
        </div>
      </div>
    </template>

    <!-- Custom assistant message with markdown and avatar -->
    <template #message-assistant="{ content }">
      <div class="custom-assistant-msg">
        <v-avatar size="32" class="mr-2" color="primary">
          <v-icon dark>mdi-robot</v-icon>
        </v-avatar>
        <div
          class="message-bubble assistant-bubble"
          v-html="renderMarkdown(content)"
        ></div>
      </div>
    </template>
  </ChatWidget>
</template>

Custom Indicators

Replace loading indicators with custom animations:

<template>
  <ChatWidget :agent-id="agentId">
    <!-- Custom thinking indicator -->
    <template #indicator-thinking>
      <div class="custom-thinking">
        <v-progress-circular indeterminate color="purple" size="20" />
        <span class="ml-2">AI is thinking deeply...</span>
      </div>
    </template>

    <!-- Custom waiting indicator -->
    <template #indicator-waiting>
      <div class="custom-waiting">
        <v-icon color="blue" class="rotating">mdi-loading</v-icon>
        <span class="ml-2">Processing your request...</span>
      </div>
    </template>
  </ChatWidget>
</template>

<style scoped>
.rotating {
  animation: rotate 1s linear infinite;
}
@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
</style>

Accessing Tool Response Data

The response prop in the message-tool-response slot contains the full JSON response from the tool:

<template #message-tool-response="{ toolCall, response }">
  <div>
    <!-- Access HTTP status -->
    <div v-if="response.status_code">Status: {{ response.status_code }}</div>

    <!-- Access response body -->
    <div v-if="response.data">
      <pre>{{ JSON.stringify(response.data, null, 2) }}</pre>
    </div>

    <!-- Conditionally render based on tool name -->
    <component
      :is="getComponentForTool(toolCall.function.name)"
      :data="response"
    />
  </div>
</template>

Slot Forwarding

Slots defined on ChatWidget are automatically forwarded to the internal OpenAIChatWidget component, so you can customize the display without worrying about component hierarchy.

<!-- Both of these work the same way -->
<ChatWidget :agent-id="123">
  <template #message-tool-response="{ response }">
    <!-- Custom rendering -->
  </template>
</ChatWidget>

<OpenAIChatWidget :messages="messages">
  <template #message-tool-response="{ response }">
    <!-- Custom rendering -->
  </template>
</OpenAIChatWidget>

Styling

The component uses Vuetify components. Make sure Vuetify is properly configured in your project:

// main.js
import { createApp } from "vue";
import { createVuetify } from "vuetify";
import "vuetify/styles";

const vuetify = createVuetify();
const app = createApp(App);

app.use(vuetify);
app.mount("#app");

Browser Support

  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • Requires native fetch() and EventSource support
  • Vue 3.4+ and Vuetify 3.6+

License

MIT

Contributing

This component is part of the CopilotClone project.

To contribute:

  1. Fork the main repository
  2. Make changes in frontend/src/components/ChatWidget/
  3. Test locally in the CopilotClone project
  4. Submit a pull request

Support

For issues and questions:

  • GitHub Issues: https://github.com/gzmagyari/CopilotClone/issues
  • Repository: https://github.com/gzmagyari/CopilotClone

Changelog

1.1.0 (Slot Customization)

  • ✅ Added customizable slots for all message types and indicators
  • ✅ Support for custom tool response rendering (code diffs, cards, images, etc.)
  • ✅ Custom tool call display with access to full data
  • ✅ Custom loading indicators (thinking, waiting, executing)
  • ✅ Slot forwarding from ChatWidget to OpenAIChatWidget
  • ✅ Full Vue 2 and Vue 3 compatibility maintained

1.0.0 (Initial Release)

  • ✅ Independent component with zero Vuex dependency
  • ✅ SSE streaming support
  • ✅ Thread management
  • ✅ Message editing
  • ✅ Tool call visualization
  • ✅ Configurable API base URL