Clawdbot汉化版从零开始:Clawdbot前端控制台二次开发+UI主题定制
1. 引言:为什么需要定制你的AI助手?
想象一下,你有一个24小时在线的AI助手,它能在微信里和你聊天,帮你写代码、查资料、做总结,而且完全免费,所有数据都保存在你自己的电脑上。这就是Clawdbot。
但默认的界面可能不够符合你的审美,或者你想给它增加一些独特的功能,比如一个企业微信的入口,让团队协作更顺畅。这时候,二次开发和UI定制就派上用场了。
本文将带你从零开始,手把手教你如何对Clawdbot的前端控制台进行二次开发,并定制一套属于你自己的UI主题。无论你是前端新手,还是有一定经验的开发者,都能跟着步骤一步步实现。
你将学到:
- 如何搭建Clawdbot前端开发环境
- 理解前端控制台的核心代码结构
- 如何修改界面样式和布局
- 如何增加新的功能模块(以企业微信入口为例)
- 如何打包和部署你的定制版本
2. 环境准备与项目结构解析
在开始动手之前,我们需要先准备好开发环境,并了解Clawdbot前端项目的整体结构。
2.1 开发环境搭建
首先,确保你的系统已经安装了必要的工具:
bash
# 检查Node.js版本(需要16.x或更高)
node --version
# 检查npm版本
npm --version
# 如果没有安装,先安装Node.js
# Ubuntu/Debian系统
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs
# CentOS/RHEL系统
curl -fsSL https://rpm.nodesource.com/setup_16.x | sudo bash -
sudo yum install -y nodejs
2.2 获取Clawdbot前端源码
Clawdbot的前端代码通常位于/root/clawdbot目录下,但我们需要找到具体的UI部分:
bash
# 进入Clawdbot目录
cd /root/clawdbot
# 查看目录结构
ls -la
# 通常前端代码在以下位置之一:
# - /root/clawdbot/ui
# - /root/clawdbot/web
# - /root/clawdbot/frontend
# 如果找不到,可以搜索相关文件
find . -name "package.json" -type f | grep -v node_modules
假设我们找到了前端代码在/root/clawdbot/ui目录:
bash
# 进入前端目录
cd /root/clawdbot/ui
# 安装依赖
npm install
# 启动开发服务器
npm run dev
如果看到类似下面的输出,说明开发环境启动成功:
VITE v4.4.9 ready in 320 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
2.3 项目结构解析
让我们看看典型的前端项目结构:
ui/
├── src/ # 源代码目录
│ ├── components/ # 组件目录
│ │ ├── Chat.vue # 聊天组件
│ │ ├── Sidebar.vue # 侧边栏
│ │ └── Settings.vue # 设置页面
│ ├── views/ # 页面视图
│ │ ├── Dashboard.vue # 仪表盘
│ │ └── Agents.vue # 助手管理
│ ├── assets/ # 静态资源
│ │ ├── css/ # 样式文件
│ │ └── images/ # 图片资源
│ ├── router/ # 路由配置
│ ├── store/ # 状态管理
│ └── main.js # 入口文件
├── public/ # 公共资源
├── package.json # 项目配置
├── vite.config.js # 构建配置
└── index.html # HTML模板
关键文件说明:
src/main.js:应用入口,初始化Vue应用src/router/index.js:定义页面路由src/assets/css/:全局样式文件package.json:项目依赖和脚本配置
3. 基础修改:定制你的UI主题
现在我们来开始实际的修改工作。我们将从最简单的颜色主题开始,逐步深入到布局调整。
3.1 修改全局样式
首先,找到项目的样式文件。通常位于src/assets/css/或src/styles/目录:
css
/* 在 src/assets/css/main.css 或类似文件中 */
/* 1. 修改主题颜色 */
:root {
/* 原版颜色 */
--primary-color: #646cff;
--primary-hover: #535bf2;
/* 改为你喜欢的颜色 */
--primary-color: #1890ff; /* 蓝色主题 */
--primary-hover: #40a9ff;
/* 或者绿色主题 */
/* --primary-color: #52c41a; */
/* --primary-hover: #73d13d; */
/* 背景色和文字色 */
--bg-color: #ffffff;
--text-color: #213547;
--border-color: #e4e7ed;
}
/* 2. 修改暗色主题 */
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #1a1a1a;
--text-color: rgba(255, 255, 255, 0.87);
--border-color: #434343;
}
}
/* 3. 修改按钮样式 */
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
border-color: var(--primary-hover);
}
/* 4. 修改输入框样式 */
.input-field {
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 8px 12px;
transition: border-color 0.3s;
}
.input-field:focus {
border-color: var(--primary-color);
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
3.2 修改布局结构
如果你想调整页面布局,比如把侧边栏从左边移到右边,或者改变聊天窗口的大小:
vue
<!-- 在 src/components/Layout.vue 或类似文件中 -->
<template>
<div class="layout-container">
<!-- 原版布局 -->
<!-- <Sidebar class="sidebar" />
<main class="main-content">
<router-view />
</main> -->
<!-- 修改后的布局:侧边栏在右侧 -->
<main class="main-content">
<router-view />
</main>
<Sidebar class="sidebar-right" />
</div>
</template>
<style scoped>
.layout-container {
display: flex;
height: 100vh;
}
/* 原版样式 */
.sidebar {
width: 240px;
border-right: 1px solid var(--border-color);
}
.main-content {
flex: 1;
overflow: auto;
}
/* 修改后的样式:侧边栏在右侧 */
.sidebar-right {
width: 240px;
border-left: 1px solid var(--border-color);
order: 2; /* 让侧边栏在最后显示(右侧) */
}
.main-content {
flex: 1;
order: 1; /* 主要内容在左侧 */
}
</style>
3.3 修改聊天界面
聊天界面是使用最频繁的部分,我们可以让它更符合我们的使用习惯:
vue
<!-- 在 src/components/Chat.vue 中 -->
<template>
<div class="chat-container">
<!-- 消息列表 -->
<div class="messages" ref="messagesRef">
<div
v-for="(message, index) in messages"
:key="index"
:class="['message', message.role]"
>
<!-- 用户消息 -->
<div v-if="message.role === 'user'" class="user-message">
<div class="avatar">👤</div>
<div class="bubble">{{ message.content }}</div>
</div>
<!-- AI消息 -->
<div v-else class="ai-message">
<div class="avatar">🤖</div>
<div class="bubble">
<!-- 支持Markdown渲染 -->
<div v-html="renderMarkdown(message.content)"></div>
<!-- 添加复制按钮 -->
<button
class="copy-btn"
@click="copyToClipboard(message.content)"
title="复制内容"
>
📋
</button>
</div>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="input-area">
<textarea
v-model="inputText"
@keydown.enter.exact.prevent="sendMessage"
placeholder="输入消息... (按Enter发送,Shift+Enter换行)"
rows="3"
></textarea>
<div class="input-actions">
<!-- 添加快捷指令按钮 -->
<button
v-for="cmd in quickCommands"
:key="cmd.text"
@click="insertCommand(cmd)"
class="quick-btn"
>
{{ cmd.label }}
</button>
<button @click="sendMessage" class="send-btn">
发送
</button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
inputText: '',
messages: [],
quickCommands: [
{ label: '写代码', text: '帮我写一个Python函数,功能是:' },
{ label: '总结', text: '请总结以下内容:' },
{ label: '翻译', text: '将以下内容翻译成英文:' },
{ label: '解释', text: '请解释一下这个概念:' }
]
}
},
methods: {
sendMessage() {
if (!this.inputText.trim()) return
// 添加用户消息
this.messages.push({
role: 'user',
content: this.inputText
})
// 发送到后端
this.sendToBackend(this.inputText)
// 清空输入
this.inputText = ''
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom()
})
},
insertCommand(cmd) {
this.inputText = cmd.text + this.inputText
this.$refs.textarea.focus()
},
copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
alert('已复制到剪贴板!')
})
},
renderMarkdown(content) {
// 简单的Markdown渲染逻辑
return content
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code>$1</code>')
.replace(/\n/g, '<br>')
},
scrollToBottom() {
const container = this.$refs.messagesRef
container.scrollTop = container.scrollHeight
}
}
}
</script>
<style scoped>
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
}
.messages {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.message {
margin-bottom: 16px;
}
.user-message, .ai-message {
display: flex;
align-items: flex-start;
gap: 12px;
}
.user-message {
flex-direction: row-reverse;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
background: var(--primary-color);
color: white;
}
.bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
position: relative;
}
.user-message .bubble {
background-color: var(--primary-color);
color: white;
border-bottom-right-radius: 4px;
}
.ai-message .bubble {
background-color: #f5f5f5;
color: var(--text-color);
border-bottom-left-radius: 4px;
}
.copy-btn {
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
cursor: pointer;
opacity: 0.3;
transition: opacity 0.3s;
}
.copy-btn:hover {
opacity: 1;
}
.input-area {
border-top: 1px solid var(--border-color);
padding: 16px;
}
textarea {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
resize: vertical;
font-family: inherit;
font-size: 14px;
}
.input-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.quick-btn {
padding: 6px 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: white;
cursor: pointer;
font-size: 12px;
}
.quick-btn:hover {
background: #f5f5f5;
}
.send-btn {
margin-left: auto;
padding: 8px 24px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.send-btn:hover {
background: var(--primary-hover);
}
</style>
4. 高级功能:添加企业微信入口
现在我们来实现一个实用的功能:在企业微信中集成Clawdbot。这样你的团队成员可以直接在企业微信里使用AI助手。
4.1 创建企业微信配置页面
首先,我们需要在设置页面中添加企业微信的配置选项:
vue
<!-- 新建 src/components/WeChatWork.vue -->
<template>
<div class="wechat-work-config">
<h3>企业微信配置</h3>
<div class="form-group">
<label>企业ID (CorpID)</label>
<input
v-model="config.corpId"
type="text"
placeholder="请输入企业ID"
/>
<small>在企业微信管理后台获取</small>
</div>
<div class="form-group">
<label>应用Secret</label>
<input
v-model="config.secret"
type="password"
placeholder="请输入应用Secret"
/>
<small>在企业微信应用管理页面获取</small>
</div>
<div class="form-group">
<label>应用AgentId</label>
<input
v-model="config.agentId"
type="text"
placeholder="请输入应用AgentId"
/>
</div>
<div class="form-group">
<label>接收消息URL</label>
<div class="url-display">
<code>{{ webhookUrl }}</code>
<button @click="copyUrl" class="copy-btn">复制</button>
</div>
<small>将此URL配置到企业微信应用的回调地址</small>
</div>
<div class="form-actions">
<button @click="testConnection" :disabled="testing" class="test-btn">
{{ testing ? '测试中...' : '测试连接' }}
</button>
<button @click="saveConfig" class="save-btn">保存配置</button>
</div>
<!-- 配置说明 -->
<div class="instructions">
<h4>配置步骤:</h4>
<ol>
<li>登录企业微信管理后台</li>
<li>进入"应用管理" → "自建应用"</li>
<li>创建新应用或选择现有应用</li>
<li>在"接收消息"设置中,启用API接收</li>
<li>将上面的URL填入"接收消息服务器配置"</li>
<li>填写Token和EncodingAESKey(随机生成)</li>
<li>保存后点击"测试连接"按钮验证</li>
</ol>
</div>
<!-- 状态显示 -->
<div v-if="status" class="status-message" :class="status.type">
{{ status.message }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
config: {
corpId: '',
secret: '',
agentId: '',
token: this.generateToken(),
encodingAESKey: this.generateAESKey()
},
testing: false,
status: null
}
},
computed: {
webhookUrl() {
// 获取当前服务器的地址
const baseUrl = window.location.origin
return `${baseUrl}/api/wechat-work/webhook`
}
},
mounted() {
this.loadConfig()
},
methods: {
generateToken() {
// 生成随机Token
return Array.from(crypto.getRandomValues(new Uint8Array(16)))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
},
generateAESKey() {
// 生成43位随机字符串(企业微信要求)
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
for (let i = 0; i < 43; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length))
}
return result
},
async loadConfig() {
try {
const response = await fetch('/api/config/wechat-work')
if (response.ok) {
const data = await response.json()
this.config = { ...this.config, ...data }
}
} catch (error) {
console.log('加载配置失败,使用默认值')
}
},
async saveConfig() {
try {
const response = await fetch('/api/config/wechat-work', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.config)
})
if (response.ok) {
this.showStatus('配置保存成功!', 'success')
// 重启服务使配置生效
await this.restartService()
} else {
this.showStatus('保存失败,请检查网络连接', 'error')
}
} catch (error) {
this.showStatus('保存失败:' + error.message, 'error')
}
},
async testConnection() {
this.testing = true
this.status = null
try {
const response = await fetch('/api/wechat-work/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
corpId: this.config.corpId,
secret: this.config.secret
})
})
const data = await response.json()
if (data.success) {
this.showStatus('连接测试成功!', 'success')
} else {
this.showStatus(`连接失败:${data.message}`, 'error')
}
} catch (error) {
this.showStatus('测试失败:' + error.message, 'error')
} finally {
this.testing = false
}
},
async restartService() {
try {
const response = await fetch('/api/service/restart', {
method: 'POST'
})
if (response.ok) {
this.showStatus('服务重启中,请稍候...', 'info')
// 等待服务重启
setTimeout(() => {
this.showStatus('服务重启完成!', 'success')
}, 3000)
}
} catch (error) {
console.error('重启服务失败:', error)
}
},
copyUrl() {
navigator.clipboard.writeText(this.webhookUrl).then(() => {
this.showStatus('URL已复制到剪贴板', 'success')
})
},
showStatus(message, type = 'info') {
this.status = { message, type }
// 3秒后自动清除状态
setTimeout(() => {
this.status = null
}, 3000)
}
}
}
</script>
<style scoped>
.wechat-work-config {
max-width: 600px;
margin: 0 auto;
padding: 24px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: var(--text-color);
}
.form-group input {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 14px;
}
.form-group small {
display: block;
margin-top: 4px;
color: #666;
font-size: 12px;
}
.url-display {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: #f5f5f5;
border-radius: 6px;
font-family: monospace;
font-size: 13px;
}
.url-display code {
flex: 1;
word-break: break-all;
}
.copy-btn {
padding: 4px 12px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.copy-btn:hover {
background: var(--primary-hover);
}
.form-actions {
display: flex;
gap: 12px;
margin: 24px 0;
}
.test-btn, .save-btn {
padding: 10px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
}
.test-btn {
background: #f5f5f5;
color: var(--text-color);
}
.test-btn:hover:not(:disabled) {
background: #e8e8e8;
}
.test-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.save-btn {
background: var(--primary-color);
color: white;
}
.save-btn:hover {
background: var(--primary-hover);
}
.instructions {
margin-top: 32px;
padding: 20px;
background: #f9f9f9;
border-radius: 8px;
border-left: 4px solid var(--primary-color);
}
.instructions h4 {
margin-top: 0;
margin-bottom: 12px;
}
.instructions ol {
margin: 0;
padding-left: 20px;
}
.instructions li {
margin-bottom: 8px;
line-height: 1.5;
}
.status-message {
margin-top: 20px;
padding: 12px 16px;
border-radius: 6px;
font-size: 14px;
}
.status-message.success {
background: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.status-message.error {
background: #fff2f0;
border: 1px solid #ffccc7;
color: #ff4d4f;
}
.status-message.info {
background: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
</style>
4.2 集成到设置页面
接下来,我们需要把这个组件添加到现有的设置页面中:
vue
<!-- 修改 src/views/Settings.vue -->
<template>
<div class="settings-page">
<h2>系统设置</h2>
<div class="settings-tabs">
<button
v-for="tab in tabs"
:key="tab.id"
:class="['tab-btn', { active: activeTab === tab.id }]"
@click="activeTab = tab.id"
>
{{ tab.label }}
</button>
</div>
<div class="tab-content">
<!-- 通用设置 -->
<div v-if="activeTab === 'general'" class="general-settings">
<!-- 原有的通用设置内容... -->
</div>
<!-- AI模型设置 -->
<div v-else-if="activeTab === 'model'" class="model-settings">
<!-- 原有的模型设置内容... -->
</div>
<!-- 企业微信设置 -->
<div v-else-if="activeTab === 'wechat-work'" class="wechat-work-settings">
<WeChatWork />
</div>
<!-- 其他设置... -->
</div>
</div>
</template>
<script>
import WeChatWork from '@/components/WeChatWork.vue'
export default {
components: {
WeChatWork
},
data() {
return {
activeTab: 'general',
tabs: [
{ id: 'general', label: '通用设置' },
{ id: 'model', label: 'AI模型' },
{ id: 'wechat-work', label: '企业微信' },
{ id: 'notifications', label: '通知设置' },
{ id: 'advanced', label: '高级设置' }
]
}
}
}
</script>
<style scoped>
.settings-page {
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.settings-tabs {
display: flex;
gap: 8px;
margin-bottom: 24px;
border-bottom: 1px solid var(--border-color);
padding-bottom: 4px;
}
.tab-btn {
padding: 10px 20px;
background: none;
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
font-size: 14px;
color: #666;
transition: all 0.3s;
}
.tab-btn:hover {
color: var(--primary-color);
}
.tab-btn.active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
font-weight: 500;
}
.tab-content {
background: white;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>
4.3 添加后端API支持
前端配置好了,我们还需要后端支持。创建一个简单的Node.js服务来处理企业微信的Webhook:
javascript
// 在 /root/clawdbot 目录下创建 wechat-work.js
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');
class WeChatWorkService {
constructor() {
this.config = this.loadConfig();
this.accessToken = null;
this.tokenExpiresAt = 0;
}
// 加载配置
loadConfig() {
try {
const configPath = '/root/.clawdbot/wechat-work.json';
if (fs.existsSync(configPath)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
}
} catch (error) {
console.error('加载企业微信配置失败:', error);
}
return {};
}
// 保存配置
saveConfig(config) {
try {
const configPath = '/root/.clawdbot/wechat-work.json';
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
this.config = config;
return true;
} catch (error) {
console.error('保存企业微信配置失败:', error);
return false;
}
}
// 获取Access Token
async getAccessToken() {
const now = Date.now();
// 检查token是否过期
if (this.accessToken && now < this.tokenExpiresAt) {
return this.accessToken;
}
try {
const { corpId, secret } = this.config;
if (!corpId || !secret) {
throw new Error('企业微信配置不完整');
}
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpId}&corpsecret=${secret}`;
const response = await axios.get(url);
if (response.data.errcode === 0) {
this.accessToken = response.data.access_token;
this.tokenExpiresAt = now + (response.data.expires_in - 300) * 1000; // 提前5分钟过期
return this.accessToken;
} else {
throw new Error(`获取token失败: ${response.data.errmsg}`);
}
} catch (error) {
console.error('获取企业微信Access Token失败:', error);
throw error;
}
}
// 验证消息签名
verifySignature(token, timestamp, nonce, signature) {
const arr = [token, timestamp, nonce].sort();
const str = arr.join('');
const sha1 = crypto.createHash('sha1');
sha1.update(str);
const hash = sha1.digest('hex');
return hash === signature;
}
// 处理企业微信消息
async handleMessage(message) {
try {
const { FromUserName, Content, MsgType } = message;
// 只处理文本消息
if (MsgType !== 'text') {
return '暂不支持此消息类型';
}
// 调用Clawdbot的AI接口
const aiResponse = await this.callClawdbot(Content, FromUserName);
// 返回回复内容
return {
ToUserName: FromUserName,
FromUserName: message.ToUserName,
CreateTime: Math.floor(Date.now() / 1000),
MsgType: 'text',
Content: aiResponse
};
} catch (error) {
console.error('处理企业微信消息失败:', error);
return '处理消息时出现错误';
}
}
// 调用Clawdbot AI
async callClawdbot(message, userId) {
try {
// 这里调用Clawdbot的本地API
const { exec } = require('child_process');
return new Promise((resolve, reject) => {
const command = `cd /root/clawdbot && node dist/index.js agent --agent main --message "${message.replace(/"/g, '\\"')}"`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.error('调用Clawdbot失败:', error);
reject(error);
return;
}
// 解析AI的回复
const response = stdout.trim();
resolve(response || '抱歉,我没有理解你的问题。');
});
});
} catch (error) {
console.error('调用AI失败:', error);
return 'AI服务暂时不可用,请稍后再试。';
}
}
// 发送消息到企业微信
async sendMessage(toUser, content) {
try {
const accessToken = await this.getAccessToken();
const url = `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${accessToken}`;
const message = {
touser: toUser,
msgtype: 'text',
agentid: this.config.agentId,
text: {
content: content
},
safe: 0
};
const response = await axios.post(url, message);
if (response.data.errcode === 0) {
console.log('消息发送成功:', toUser, content);
return true;
} else {
console.error('发送消息失败:', response.data);
return false;
}
} catch (error) {
console.error('发送企业微信消息失败:', error);
return false;
}
}
}
// 创建Express应用
const app = express();
const wechatWork = new WeChatWorkService();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 企业微信验证接口
app.get('/api/wechat-work/webhook', (req, res) => {
const { signature, timestamp, nonce, echostr } = req.query;
const { token } = wechatWork.config;
if (wechatWork.verifySignature(token, timestamp, nonce, signature)) {
res.send(echostr);
} else {
res.status(403).send('验证失败');
}
});
// 接收企业微信消息
app.post('/api/wechat-work/webhook', (req, res) => {
const { signature, timestamp, nonce } = req.query;
const { token } = wechatWork.config;
// 验证签名
if (!wechatWork.verifySignature(token, timestamp, nonce, signature)) {
res.status(403).send('验证失败');
return;
}
// 处理消息
const message = req.body;
// 如果是加密消息,需要解密
// 这里简化处理,假设是明文模式
wechatWork.handleMessage(message).then(reply => {
res.json(reply);
}).catch(error => {
console.error('处理消息错误:', error);
res.status(500).send('服务器错误');
});
});
// 保存配置
app.post('/api/config/wechat-work', (req, res) => {
const config = req.body;
if (wechatWork.saveConfig(config)) {
res.json({ success: true, message: '配置保存成功' });
} else {
res.status(500).json({ success: false, message: '保存配置失败' });
}
});
// 测试连接
app.post('/api/wechat-work/test', async (req, res) => {
const { corpId, secret } = req.body;
try {
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpId}&corpsecret=${secret}`;
const response = await axios.get(url);
if (response.data.errcode === 0) {
res.json({ success: true, message: '连接测试成功' });
} else {
res.json({ success: false, message: response.data.errmsg });
}
} catch (error) {
res.json({ success: false, message: error.message });
}
});
// 启动服务
const PORT = 3001;
app.listen(PORT, () => {
console.log(`企业微信服务运行在 http://localhost:${PORT}`);
});
module.exports = { WeChatWorkService };
4.4 修改Clawdbot主服务
我们需要修改Clawdbot的主服务,让它启动时也启动企业微信服务:
javascript
// 修改 /root/clawdbot/dist/index.js 或相应的启动文件
// 在适当的位置添加企业微信服务启动
const { WeChatWorkService } = require('./wechat-work');
// 启动企业微信服务
function startWeChatWorkService() {
try {
const wechatWork = new WeChatWorkService();
// 检查配置是否存在
const config = wechatWork.loadConfig();
if (config.corpId && config.secret && config.agentId) {
console.log('检测到企业微信配置,启动企业微信服务...');
// 这里可以添加更多的初始化逻辑
// 比如定时同步通讯录、处理消息队列等
console.log('企业微信服务启动完成');
} else {
console.log('未检测到完整的企业微信配置,服务未启动');
}
} catch (error) {
console.error('启动企业微信服务失败:', error);
}
}
// 在Clawdbot启动时调用
startWeChatWorkService();
5. 构建与部署定制版本
完成所有修改后,我们需要构建并部署我们的定制版本。
5.1 构建前端代码
bash
# 进入前端目录
cd /root/clawdbot/ui
# 安装依赖(如果还没安装)
npm install
# 构建生产版本
npm run build
# 构建完成后,静态文件会在 dist 目录下
ls -la dist/
5.2 配置Nginx代理
为了让前端能够访问后端API,我们需要配置Nginx:
nginx
# 在 /etc/nginx/sites-available/clawdbot 或类似位置
server {
listen 80;
server_name your-domain.com; # 替换为你的域名或IP
# 前端静态文件
location / {
root /root/clawdbot/ui/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
# 后端API代理
location /api/ {
proxy_pass http://localhost:3000; # Clawdbot后端端口
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 企业微信服务代理
location /wechat-work/ {
proxy_pass http://localhost:3001; # 企业微信服务端口
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
5.3 创建启动脚本
为了方便管理,我们可以创建一个启动脚本:
bash
#!/bin/bash
# /root/start-clawdbot-custom.sh
echo "启动定制版Clawdbot..."
# 1. 启动Clawdbot主服务
echo "启动Clawdbot主服务..."
cd /root/clawdbot
node dist/index.js gateway > /tmp/clawdbot-gateway.log 2>&1 &
# 2. 启动企业微信服务
echo "启动企业微信服务..."
node wechat-work.js > /tmp/wechat-work.log 2>&1 &
# 3. 启动Nginx(如果还没运行)
if ! pgrep -x "nginx" > /dev/null
then
echo "启动Nginx..."
nginx
fi
echo "所有服务启动完成!"
echo "前端访问: http://你的服务器IP"
echo "企业微信配置: http://你的服务器IP/#/settings/wechat-work"
echo ""
echo "查看日志:"
echo " Clawdbot: tail -f /tmp/clawdbot-gateway.log"
echo " 企业微信: tail -f /tmp/wechat-work.log"
给脚本添加执行权限:
bash
chmod +x /root/start-clawdbot-custom.sh
5.4 测试部署
bash
# 运行启动脚本
bash /root/start-clawdbot-custom.sh
# 检查服务状态
ps aux | grep -E "(node|nginx)"
# 查看日志
tail -f /tmp/clawdbot-gateway.log
tail -f /tmp/wechat-work.log
# 测试前端访问
curl http://localhost
6. 常见问题与解决方案
在二次开发和部署过程中,你可能会遇到一些问题。这里列出了一些常见问题及其解决方法。
6.1 前端构建失败
问题 :运行npm run build时出现错误
可能原因和解决方法:
bash
# 1. 依赖问题
# 删除node_modules重新安装
rm -rf node_modules package-lock.json
npm cache clean --force
npm install
# 2. 内存不足
# 增加Node.js内存限制
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build
# 3. 版本不兼容
# 检查package.json中的依赖版本
# 确保Node.js版本符合要求
node --version # 需要16.x或更高
6.2 企业微信连接失败
问题:企业微信配置保存后,测试连接失败
排查步骤:
bash
# 1. 检查配置是否正确
cat /root/.clawdbot/wechat-work.json
# 2. 检查网络连接
curl -v https://qyapi.weixin.qq.com
# 3. 检查服务是否运行
ps aux | grep wechat-work
# 4. 查看日志
tail -f /tmp/wechat-work.log
# 5. 手动测试API
curl "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=YOUR_CORPID&corpsecret=YOUR_SECRET"
常见错误:
- 40001:获取access_token时Secret错误
- 40014:不合法的access_token
- 41001:缺少access_token参数
- 42001:access_token已过期
6.3 样式不生效
问题:修改了CSS样式,但页面没有变化
解决方法:
bash
# 1. 清除浏览器缓存
# 或者使用无痕模式访问
# 2. 检查构建是否正确
# 重新构建前端
cd /root/clawdbot/ui
npm run build
# 3. 检查Nginx配置
# 确保指向正确的dist目录
nginx -t # 测试配置
nginx -s reload # 重新加载配置
# 4. 检查文件权限
chmod -R 755 /root/clawdbot/ui/dist
6.4 页面路由问题
问题:刷新页面后出现404错误
解决方法:
nginx
# 在Nginx配置中添加try_files
location / {
root /root/clawdbot/ui/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
6.5 性能优化建议
如果你的定制版本运行缓慢,可以尝试以下优化:
javascript
// 1. 代码分割
// 在vite.config.js或webpack配置中添加
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router', 'axios'],
wechat: ['./src/components/WeChatWork.vue']
}
}
}
}
});
// 2. 图片优化
// 使用WebP格式,压缩图片
// 安装图片处理插件
npm install -D vite-plugin-imagemin
// 3. 缓存策略
// 在Nginx配置中添加缓存
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
// 4. 启用Gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
7. 总结
通过本文的步骤,你已经成功完成了Clawdbot前端控制台的二次开发和UI主题定制,并增加了企业微信入口功能。让我们回顾一下关键点:
7.1 主要成果
- 环境搭建成功:建立了完整的开发环境,能够进行前端修改和构建
- UI主题定制完成:修改了颜色、布局、聊天界面,让界面更符合个人喜好
- 企业微信集成实现:增加了企业微信配置页面和后端服务,实现了在企业微信中使用AI助手
- 部署流程完善:创建了构建脚本和启动脚本,方便后续维护和更新
7.2 核心价值
- 个性化体验:不再使用千篇一律的界面,有了属于自己的AI助手外观
- 团队协作增强:通过企业微信集成,让团队成员都能方便地使用AI助手
- 完全自主控制:所有代码和数据都在自己手中,安全可控
- 扩展性强:掌握了二次开发的方法,未来可以轻松添加更多功能
7.3 后续建议
- 定期更新:关注Clawdbot原项目的更新,及时合并新功能
- 备份配置:定期备份你的定制配置和修改的代码
- 监控日志:设置日志监控,及时发现和解决问题
- 性能优化:随着使用量增加,考虑优化前端性能和后端响应速度
- 安全加固:确保企业微信等敏感配置的安全存储
7.4 快速命令参考
bash
# 开发相关
cd /root/clawdbot/ui # 进入前端目录
npm run dev # 启动开发服务器
npm run build # 构建生产版本
# 服务管理
bash /root/start-clawdbot-custom.sh # 启动所有服务
ps aux | grep node # 查看服务状态
tail -f /tmp/clawdbot-gateway.log # 查看主服务日志
tail -f /tmp/wechat-work.log # 查看企业微信日志
# 配置检查
cat /root/.clawdbot/wechat-work.json # 查看企业微信配置
nginx -t # 检查Nginx配置
nginx -s reload # 重载Nginx配置
二次开发是一个持续的过程,随着你对Clawdbot的深入使用,可能会发现更多可以优化的地方。记住,最重要的是保持代码的可维护性,做好版本管理,这样在未来升级或添加新功能时会更加顺利。
祝你使用愉快!如果你在定制过程中遇到任何问题,或者有新的创意想要实现,欢迎继续探索和尝试。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。