@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-it,html: 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 | 当前会话消息:role、content、reasoning(纯文本)、messageId、timestamp |
| isCurrentSessionLoading | 当前会话是否在生成 |
| hasCurrentSessionReplyContent | 当前轮是否已有可见流式内容(区分「仅有 loading 点」与「已出字」) |
| activeGenerationBySession | { [sessionId]: boolean },各会话是否仍在生成(含切走后该会话在后台仍返回时);可只读绑定侧栏「生成中」等角标;由 SSE 与包内逻辑维护,只读 |
| hasReplyContentBySession | { [sessionId]: boolean },各会话当前轮是否已出现可见流式正文或思考;可只读绑定「已出字」等角标;只读 |
| streamPartTypesBySession | { [sessionId]: { [partId]: 类型 } },非当前会话的 part 类型缓存,供后台 SSE 过滤 text/reasoning 与「已出字」判断配套;只读|
切换会话、删除会话等请使用下方对外方法, 不要通过改写上述字段或 messages 代替流程。
内部流式中间状态: 同一 ctrl 对象上还有 streamPartIdTypes、assistantPartTextMap 等,由 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)|
