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

@lzy6755/ai-chat-opencode

v0.1.6

Published

Vue 2 AI chat panel (OpenCode SSE)

Readme

ai-chat-opencode

Vue 2 单文件组件:OpenCode 对话面板(源码包,由宿主 webpack 编译)。

安装

npm install @lzy6755/ai-chat-opencode --save

使用

方案1 直接导入组件

用户直接导入AiChatPanel组件,并传入配置参数即可使用。

参数列表

| 参数名 | 类型 | 默认值 | 是否必填 | 说明 | | -------------------- | ---------- | ------------------------------- | ---- | --------------------------------- | | visible | Boolean | false | 是 | 控制面板显示/隐藏,通常配合 :visible.sync 使用 | | opencodeUrl | String | https://opencodedev.pyamc.com | 是 | OpenCode 服务地址 | | opencodeDirectory | String | /work/crm-agent | 是 | 服务端会话目录参数 | | opencodeSystem | String | '' | 是 | 传给opencode进行身份校验的信息 | | source | String | crm | 是 | 传入系统名称作为本地存储 key 的来源隔离标识 | | userId | String | '' | 是 | 用于区分用户会话存储;不传会影响会话列表按用户隔离 | | opencodeAgent | String | build | 否 | 使用的 agent | | opencodeModelID | String | GLM-5 | 否 | 模型 ID | | opencodeProviderID | String | jdprovider | 否 | 模型提供商 ID | | title | String | 智能助手 | 否 | 顶部主标题 | | subtitle | String | 随时提问,一键生成建议 | 否 | 顶部副标题 | | placeholder | String | 请输入你想让 AI 帮你处理的问题... | 否 | 输入框占位文案 | | initialMessages | Array | [] | 否 | 初始消息列表 | | toast | Function | undefined | 否 | 自定义轻提示函数 | | confirm | Function | undefined | 否 | 自定义确认框函数,返回 Promise<boolean> | | maxSessionCount | Number | 10 | 否 | 本地最多保留会话数 |

示例

<template>
  <AiChatPanel
    :visible.sync="show"
    title="智能助手"
    subtitle="随时提问,一键生成建议"
    placeholder="请输入你想让 AI 帮你处理的问题..."
    :initial-messages="initialMessages"
    :opencode-url="opencodeUrl"
    opencode-directory="/work/crm-agent"
    :opencode-system="opencodeSystem"
    opencode-agent="build"
    opencode-model-i-d="GLM-5"
    opencode-provider-i-d="jdprovider"
    :toast="customToast"
    :confirm="customConfirm"
    source="crm"
    :user-id="userId"
    :max-session-count="10"
  />
</template>

<script>
import { AiChatPanel } from '@lzy6755/ai-chat-opencode'

export default {
  components: { AiChatPanel },
  data () {
    return {
      show: false,
      userId: 'u_10001',
      opencodeUrl: 'https://opencodedev.pyamc.com',
      opencodeSystem: `{
        "PY-CRM-TOKEN":"LOGIN_TOKEN_e71ef7c7-b982-40e5-85e1-8d34039256c1",
        "PY-CRM-DEVICE":"string"
      }`,
      initialMessages: [
        { role: 'assistant', content: '你好,我可以帮你生成跟进话术和工单建议。' }
      ]
    }
  },
  methods: {
    customToast ({ message, position = 'bottom' }) {
      // 可接你们自己的 UI 提示组件
      console.log('[toast]', position, message)
    },
    customConfirm ({ message, title }) {
      // 需要返回 Promise<boolean>
      return Promise.resolve(window.confirm(`${title ? `${title}\n` : ''}${message}`))
    }
  }
}
</script>

方案2 Headless 模式(v2):自建 UI

包内提供 **createAiChatController**:由本包维护会话列表、消息、流式状态与 OpenCode SSE;宿主只负责用自有组件渲染 ctrl 上的响应式数据并调用方法。

环境要求

  • Vue 2.5+。

引入

import { createAiChatController } from '@lzy6755/ai-chat-opencode'

最小示例

export default {
  data () {
    return {
      input: '',
      chat: createAiChatController({
        opencodeUrl: 'https://opencodedev.pyamc.com',
        opencodeDirectory: '/work/crm-agent',
        opencodeSystem: '',
        source: 'crm',
        userId: 'u_10001',
        confirm: async ({ message, title }) =>
          window.confirm(`${title ? title + '\n' : ''}${message}`)
      })
    }
  },
  async mounted () {
    await this.chat.init()
  },
  beforeDestroy () {
    this.chat.dispose()
  },
  methods: {
    async onSend () {
      const text = this.input.trim()
      if (!text) return
      try {
        await this.chat.sendMessage(text)
        this.input = ''
      } catch (e) {
        // 空消息、正在生成、建会话失败、prompt 请求失败等
      }
    }
  }
}

模板中把 chat 当作根状态对象绑定即可,例如:

<ul>
  <li v-for="s in chat.sessionList" :key="s.id" @click="chat.selectSession(s.id)">
    {{ chat.getSessionTitle(s) }}
    <span v-if="chat.isSessionGenerating(s.id)">生成中</span>
  </li>
</ul>
<div v-for="(m, i) in chat.messages" :key="i">
  <template v-if="m.role === 'assistant'">
    <div v-if="m.reasoning" v-html="chat.renderMarkdownHtml(m.reasoning)" />
    <div v-html="chat.renderMarkdownHtml(m.content)" />
  </template>
  <template v-else>{{ m.content }}</template>
</div>
<button type="button" :disabled="chat.isCurrentSessionLoading" @click="onSend">发送</button>
<button type="button" @click="chat.abort">停止</button>
<button type="button" @click="chat.startNewDraft">新草稿</button>

renderMarkdownHtml(text) 使用与本包面板一致的 Markdown 规则生成 HTML;若使用 v-html,请自行评估 XSS 风险(与自带组件相同策略:markdown-ithtml: false)。

生命周期

| 调用时机 | 方法 | 说明 | | -------- | --------- | --------- | | 页面/模块挂载后 | await chat.init() | 创建 HTTP 客户端、启动全局 SSE、进入本地「空草稿」视图并刷新会话列表 | | 页面卸载前 | chat.dispose() | 中止 SSE、清空内部 client,避免泄漏;再次使用可再调用 init() |

工厂选项 createAiChatController(options)

与组件 props 对齐的字段如下(不传则用默认值)。

| 选项 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | opencodeUrl | String | https://opencodedev.pyamc.com | OpenCode 服务根地址 | | opencodeDirectory | String | /work/crm-agent | 服务端 directory 参数 | | opencodeSystem | String | '' | 鉴权信息 | | opencodeAgent | String | build | prompt 所用 agent | | opencodeModelID | String | GLM-5 | 模型 ID | | opencodeProviderID | String | jdprovider | 提供商 ID | | source | String | crm | 本地存储命名空间(与会话列表、lastActive 隔离相关) | | userId | String | '' | 必填建议填写;会话列表按用户维度存 localStorage | | maxSessionCount | Number | 10 | 本地会话列表上限 | | initialMessages | Array | [] | 无远程会话或拉取历史为空时使用的初始消息快照 | | toast | Function | undefined | ({ message, position?, ... }) => void,部分错误会调用 | | confirm | Function | undefined | ({ message, title }) => Promise<boolean>deleteSession 前确认 | | onSessionIdle | Function | undefined | 收到 session.idle 后调用 ({ sessionId }) | | onError | Function | undefined | 创建会话失败、流式失败、init 异常等 | | onTitleSynced | Function | undefined | 首条消息后从服务端同步标题成功:({ sessionId, title }) |

对外状态(建议在模板中绑定,只读)

| 字段| 说明 | | ------------------------------- | -------------------------------- | | sessionList | { id, title }[],侧栏列表 | | activeSessionId | 当前查看的会话 id;仅本地草稿时为 null | | messages | 当前会话消息:rolecontentreasoning(纯文本)、messageIdtimestamp | | isCurrentSessionLoading | 当前会话是否在生成 | | hasCurrentSessionReplyContent | 当前轮是否已有可见流式内容(区分「仅有 loading 点」与「已出字」) | | activeGenerationBySession | { [sessionId]: boolean },各会话是否仍在生成(含切走后该会话在后台仍返回时);可只读绑定侧栏「生成中」等角标;由 SSE 与包内逻辑维护,只读 | | hasReplyContentBySession | { [sessionId]: boolean },各会话当前轮是否已出现可见流式正文或思考;可只读绑定「已出字」等角标;只读 | | streamPartTypesBySession | { [sessionId]: { [partId]: 类型 } },非当前会话的 part 类型缓存,供后台 SSE 过滤 text/reasoning 与「已出字」判断配套;只读|

切换会话、删除会话等请使用下方对外方法不要通过改写上述字段或 messages 代替流程

内部流式中间状态: 同一 ctrl 对象上还有 streamPartIdTypesassistantPartTextMap 等,由 SSE 驱动;不要在业务里手动赋值,否则易与流式不同步。

对外方法

|方法|说明 | | -------------- | ------------ | | init() | 初始化客户端与 SSE,并进入空入口视图| | dispose()| 释放 SSE 与 client | | sendMessage(text)sendMessage({ text, system?, agent?, model? }) | 发送消息;必要时会先创建远程会话;返回 Promise,校验失败或请求失败会 reject(并在内部更新消息/状态) | | abort() | 停止当前会话生成(对齐服务端 abort)| | selectSession(id) / switchSession(id) | 同一实现:切换会话并拉取历史 | | deleteSession(id) | 先走 confirm,再删远程与本地列表;若删的是当前会话则自动切换或空白视图 | | startNewDraft()| 等同组件侧栏「新建」:仅本地新草稿,清除 lastActive首个远程 session 在第一次成功 sendMessage 时创建 | | refreshSessionList()| 仅从 localStorage 刷新 sessionList| | reloadMessages() | 为当前 activeSessionId 重新请求历史| | getSessionTitle(session)| 展示用标题,缺省为「未命名会话」| | isSessionGenerating(sessionId?) | 是否正在生成;省略参数时看当前会话 | | renderMarkdownHtml(text)| 将任意 Markdown 字符串转为 HTML(如正文 m.content、思考 m.reasoning)|