Spaces:
Running
Running
asemxin commited on
Commit ·
19bb594
1
Parent(s): 4dca48a
fix: image_daemon 接管飞书交互,禁用 OpenClaw 飞书插件
Browse files- Dockerfile +3 -2
- entrypoint.sh +1 -1
- image_daemon.py +75 -16
- test_api.py +21 -0
Dockerfile
CHANGED
|
@@ -15,8 +15,9 @@ RUN npm install -g openclaw@latest
|
|
| 15 |
RUN mkdir -p /root/.openclaw/workspace /root/.openclaw/extensions /root/.openclaw/credentials /root/.openclaw/skills
|
| 16 |
|
| 17 |
# 安装飞书插件
|
| 18 |
-
|
| 19 |
-
RUN
|
|
|
|
| 20 |
|
| 21 |
# 复制文件
|
| 22 |
COPY SOUL.md /root/.openclaw/workspace/SOUL.md
|
|
|
|
| 15 |
RUN mkdir -p /root/.openclaw/workspace /root/.openclaw/extensions /root/.openclaw/credentials /root/.openclaw/skills
|
| 16 |
|
| 17 |
# 安装飞书插件
|
| 18 |
+
# 安装飞书插件 (禁用以避免 WebSocket 竞争)
|
| 19 |
+
# RUN openclaw plugins install feishu-openclaw 2>/dev/null || true
|
| 20 |
+
# RUN cd /root/.openclaw/extensions/feishu-openclaw && npm install @sinclair/typebox 2>/dev/null || true
|
| 21 |
|
| 22 |
# 复制文件
|
| 23 |
COPY SOUL.md /root/.openclaw/workspace/SOUL.md
|
entrypoint.sh
CHANGED
|
@@ -42,7 +42,7 @@ cat > "$OPENCLAW_DIR/openclaw.json" << JSONEOF
|
|
| 42 |
},
|
| 43 |
"channels": {
|
| 44 |
"feishu": {
|
| 45 |
-
"enabled":
|
| 46 |
"appId": "${FEISHU_APP_ID}",
|
| 47 |
"appSecret": "${FEISHU_APP_SECRET}"
|
| 48 |
}
|
|
|
|
| 42 |
},
|
| 43 |
"channels": {
|
| 44 |
"feishu": {
|
| 45 |
+
"enabled": false,
|
| 46 |
"appId": "${FEISHU_APP_ID}",
|
| 47 |
"appSecret": "${FEISHU_APP_SECRET}"
|
| 48 |
}
|
image_daemon.py
CHANGED
|
@@ -10,6 +10,11 @@ FEISHU_BASE = "https://open.feishu.cn/open-apis"
|
|
| 10 |
APP_ID = os.environ.get("FEISHU_APP_ID", "")
|
| 11 |
APP_SECRET = os.environ.get("FEISHU_APP_SECRET", "")
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
# ---------- 日志 ----------
|
| 14 |
def log(msg):
|
| 15 |
ts = time.strftime("%H:%M:%S")
|
|
@@ -176,6 +181,55 @@ def handle_image_message(message_id, chat_id, image_key):
|
|
| 176 |
f.write(img_data)
|
| 177 |
log(f"⚠️ 图床全部失败,本地保存: {path}")
|
| 178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
# ---------- 事件处理 ----------
|
| 180 |
def on_message_receive(data):
|
| 181 |
"""im.message.receive_v1 事件回调"""
|
|
@@ -185,32 +239,37 @@ def on_message_receive(data):
|
|
| 185 |
sender = event.sender
|
| 186 |
|
| 187 |
msg_type = message.message_type
|
| 188 |
-
# 调试:打印所有收到的消息类型
|
| 189 |
-
log(f"📨 WebSocket 收到消息: type={msg_type}, msg_id={message.message_id}")
|
| 190 |
-
|
| 191 |
msg_id = message.message_id
|
| 192 |
chat_id = message.chat_id
|
| 193 |
sender_type = getattr(sender, 'sender_type', '') if sender else ''
|
| 194 |
|
| 195 |
-
#
|
| 196 |
if sender_type == "app":
|
| 197 |
return
|
| 198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
return
|
| 200 |
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
| 205 |
return
|
| 206 |
|
| 207 |
-
log(f"📨 实时收到图片消息: msg_id={msg_id[:16]}..., image_key={image_key[:20]}...")
|
| 208 |
-
|
| 209 |
-
# 在新线程中处理,避免阻塞事件循环
|
| 210 |
-
t = threading.Thread(target=handle_image_message, args=(msg_id, chat_id, image_key))
|
| 211 |
-
t.daemon = True
|
| 212 |
-
t.start()
|
| 213 |
-
|
| 214 |
except Exception as e:
|
| 215 |
log(f"❌ on_message_receive 异常: {type(e).__name__}: {e}")
|
| 216 |
import traceback
|
|
|
|
| 10 |
APP_ID = os.environ.get("FEISHU_APP_ID", "")
|
| 11 |
APP_SECRET = os.environ.get("FEISHU_APP_SECRET", "")
|
| 12 |
|
| 13 |
+
# LLM 配置
|
| 14 |
+
API_BASE_URL = os.environ.get("API_BASE_URL", "https://asem12345-cliproxyapi.hf.space/v1")
|
| 15 |
+
API_KEY = os.environ.get("API_KEY", "")
|
| 16 |
+
MODEL_NAME = os.environ.get("MODEL_NAME", "gemini-3-flash")
|
| 17 |
+
|
| 18 |
# ---------- 日志 ----------
|
| 19 |
def log(msg):
|
| 20 |
ts = time.strftime("%H:%M:%S")
|
|
|
|
| 181 |
f.write(img_data)
|
| 182 |
log(f"⚠️ 图床全部失败,本地保存: {path}")
|
| 183 |
|
| 184 |
+
# ---------- LLM 对话 ----------
|
| 185 |
+
def chat_with_llm(user_text):
|
| 186 |
+
"""调用 LLM API 获取回复"""
|
| 187 |
+
if not API_KEY:
|
| 188 |
+
log("❌ API_KEY 未设置,无法对话")
|
| 189 |
+
return "抱歉,我的大脑连接中断了 (API_KEY missing)"
|
| 190 |
+
|
| 191 |
+
try:
|
| 192 |
+
url = f"{API_BASE_URL}/chat/completions"
|
| 193 |
+
headers = {
|
| 194 |
+
"Authorization": f"Bearer {API_KEY}",
|
| 195 |
+
"Content-Type": "application/json"
|
| 196 |
+
}
|
| 197 |
+
payload = {
|
| 198 |
+
"model": MODEL_NAME,
|
| 199 |
+
"messages": [
|
| 200 |
+
{"role": "system", "content": "You are MoltBot, a helpful AI assistant."},
|
| 201 |
+
{"role": "user", "content": user_text}
|
| 202 |
+
],
|
| 203 |
+
"stream": False
|
| 204 |
+
}
|
| 205 |
+
log(f"🤖 调用 LLM: {user_text[:50]}...")
|
| 206 |
+
resp = requests.post(url, headers=headers, json=payload, timeout=60)
|
| 207 |
+
|
| 208 |
+
if resp.status_code == 200:
|
| 209 |
+
data = resp.json()
|
| 210 |
+
reply = data["choices"][0]["message"]["content"]
|
| 211 |
+
log(f"🤖 LLM 回复: {reply[:50]}...")
|
| 212 |
+
return reply
|
| 213 |
+
else:
|
| 214 |
+
log(f"❌ LLM 错误 {resp.status_code}: {resp.text}")
|
| 215 |
+
return f"思考时遇到错误 ({resp.status_code})"
|
| 216 |
+
except Exception as e:
|
| 217 |
+
log(f"❌ LLM 异常: {e}")
|
| 218 |
+
return f"大脑短路了: {e}"
|
| 219 |
+
|
| 220 |
+
# ---------- 处理文本消息 ----------
|
| 221 |
+
def handle_text_message(message_id, chat_id, text):
|
| 222 |
+
"""LLM -> 发送"""
|
| 223 |
+
token = get_token()
|
| 224 |
+
if not token:
|
| 225 |
+
return
|
| 226 |
+
|
| 227 |
+
# 简单防重:也许以后需要
|
| 228 |
+
# 这里直接调用 LLM
|
| 229 |
+
reply = chat_with_llm(text)
|
| 230 |
+
if reply:
|
| 231 |
+
send_text(token, chat_id, reply)
|
| 232 |
+
|
| 233 |
# ---------- 事件处理 ----------
|
| 234 |
def on_message_receive(data):
|
| 235 |
"""im.message.receive_v1 事件回调"""
|
|
|
|
| 239 |
sender = event.sender
|
| 240 |
|
| 241 |
msg_type = message.message_type
|
|
|
|
|
|
|
|
|
|
| 242 |
msg_id = message.message_id
|
| 243 |
chat_id = message.chat_id
|
| 244 |
sender_type = getattr(sender, 'sender_type', '') if sender else ''
|
| 245 |
|
| 246 |
+
# 过滤
|
| 247 |
if sender_type == "app":
|
| 248 |
return
|
| 249 |
+
|
| 250 |
+
content_json = json.loads(message.content)
|
| 251 |
+
|
| 252 |
+
# 调试
|
| 253 |
+
log(f"📨 WebSocket 收到消息: type={msg_type}, msg_id={msg_id}")
|
| 254 |
+
|
| 255 |
+
# 图片消息
|
| 256 |
+
if msg_type == "image":
|
| 257 |
+
image_key = content_json.get("image_key", "")
|
| 258 |
+
if image_key:
|
| 259 |
+
t = threading.Thread(target=handle_image_message, args=(msg_id, chat_id, image_key))
|
| 260 |
+
t.daemon = True
|
| 261 |
+
t.start()
|
| 262 |
return
|
| 263 |
|
| 264 |
+
# 文本消息
|
| 265 |
+
if msg_type == "text":
|
| 266 |
+
text = content_json.get("text", "")
|
| 267 |
+
if text:
|
| 268 |
+
t = threading.Thread(target=handle_text_message, args=(msg_id, chat_id, text))
|
| 269 |
+
t.daemon = True
|
| 270 |
+
t.start()
|
| 271 |
return
|
| 272 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
except Exception as e:
|
| 274 |
log(f"❌ on_message_receive 异常: {type(e).__name__}: {e}")
|
| 275 |
import traceback
|
test_api.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
try:
|
| 5 |
+
# 尝试 OpenAI 格式
|
| 6 |
+
resp = requests.post("http://127.0.0.1:18789/v1/chat/completions",
|
| 7 |
+
json={
|
| 8 |
+
"model": "default",
|
| 9 |
+
"messages": [{"role": "user", "content": "hello"}]
|
| 10 |
+
}, timeout=5)
|
| 11 |
+
print(f"POST /v1/chat/completions: {resp.status_code}")
|
| 12 |
+
print(resp.text[:200])
|
| 13 |
+
except Exception as e:
|
| 14 |
+
print(f"POST /v1/chat/completions error: {e}")
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
# 尝试 OpenClaw 原生格式 (如果有)
|
| 18 |
+
resp = requests.get("http://127.0.0.1:18789/health", timeout=5)
|
| 19 |
+
print(f"GET /health: {resp.status_code}")
|
| 20 |
+
except Exception as e:
|
| 21 |
+
print(f"GET /health error: {e}")
|