Clawdbot汉化版从零开始:Clawdbot前端控制台二次开发+UI主题定制

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 主要成果

  1. 环境搭建成功:建立了完整的开发环境,能够进行前端修改和构建
  2. UI主题定制完成:修改了颜色、布局、聊天界面,让界面更符合个人喜好
  3. 企业微信集成实现:增加了企业微信配置页面和后端服务,实现了在企业微信中使用AI助手
  4. 部署流程完善:创建了构建脚本和启动脚本,方便后续维护和更新

7.2 核心价值

  • 个性化体验:不再使用千篇一律的界面,有了属于自己的AI助手外观
  • 团队协作增强:通过企业微信集成,让团队成员都能方便地使用AI助手
  • 完全自主控制:所有代码和数据都在自己手中,安全可控
  • 扩展性强:掌握了二次开发的方法,未来可以轻松添加更多功能

7.3 后续建议

  1. 定期更新:关注Clawdbot原项目的更新,及时合并新功能
  2. 备份配置:定期备份你的定制配置和修改的代码
  3. 监控日志:设置日志监控,及时发现和解决问题
  4. 性能优化:随着使用量增加,考虑优化前端性能和后端响应速度
  5. 安全加固:确保企业微信等敏感配置的安全存储

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星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

相关推荐
杭州华望MBSE1 天前
AI应用园地(1)| AI驱动需求工程升级—条目化、模型化、追溯化的三位一体实践
大数据·人工智能·mbse·sysml·ai助手
金融Tech趋势派1 天前
企业微信私域实现高效增长的3步策略:精准获客+粘性留存+高效转化
大数据·人工智能·企业微信
河北小博博1 天前
OpenClaw 接入飞书 / 钉钉 / 企业微信:从 HTTP Webhook 到 WebSocket 长连接
钉钉·飞书·企业微信
金融Tech趋势派1 天前
企业微信SCRM哪个好?2026年企业微信客户管理工具服务商选型测评与金融汽车零售等行业实战指导
金融·汽车·企业微信
@Ma1 天前
企业微信外部群机器人接入 AI:调用API接口自动回复 实战
人工智能·机器人·企业微信
tianxiaxue12 天前
企业微信 SCRM 自动打标签功能使用教程
企业微信
梦想的旅途22 天前
企业微信外部群主动调用:RPA 接口与官方 API 的技术边界
网络·mysql·自动化·企业微信·rpa
linyanRPA2 天前
影刀RPA实操指南_电商订单自动对账与差异标记
效率工具·python脚本·ai助手·rpa自动化·爬虫自动化·店群自动化·店群自动化运营
@Ma2 天前
Python 实现企业微信外部群主动消息发送及成功接入后如何避坑,避免风控封号
开发语言·python·企业微信