mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-03 02:27:09 +08:00
Compare commits
2 Commits
feat-web-c
...
feat-docs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6db22827f2 | ||
|
|
d891312032 |
47
app.py
47
app.py
@@ -118,51 +118,22 @@ class ChannelManager:
|
||||
Stop channel(s). If channel_name is given, stop only that channel;
|
||||
otherwise stop all channels.
|
||||
"""
|
||||
# Pop under lock, then stop outside lock to avoid deadlock
|
||||
with self._lock:
|
||||
names = [channel_name] if channel_name else list(self._channels.keys())
|
||||
to_stop = []
|
||||
for name in names:
|
||||
ch = self._channels.pop(name, None)
|
||||
th = self._threads.pop(name, None)
|
||||
to_stop.append((name, ch, th))
|
||||
self._threads.pop(name, None)
|
||||
if ch is None:
|
||||
continue
|
||||
logger.info(f"[ChannelManager] Stopping channel '{name}'...")
|
||||
try:
|
||||
if hasattr(ch, 'stop'):
|
||||
ch.stop()
|
||||
except Exception as e:
|
||||
logger.warning(f"[ChannelManager] Error during channel '{name}' stop: {e}")
|
||||
if channel_name and self._primary_channel is self._channels.get(channel_name):
|
||||
self._primary_channel = None
|
||||
|
||||
for name, ch, th in to_stop:
|
||||
if ch is None:
|
||||
logger.warning(f"[ChannelManager] Channel '{name}' not found in managed channels")
|
||||
if th and th.is_alive():
|
||||
self._interrupt_thread(th, name)
|
||||
continue
|
||||
logger.info(f"[ChannelManager] Stopping channel '{name}'...")
|
||||
try:
|
||||
if hasattr(ch, 'stop'):
|
||||
ch.stop()
|
||||
except Exception as e:
|
||||
logger.warning(f"[ChannelManager] Error during channel '{name}' stop: {e}")
|
||||
if th and th.is_alive():
|
||||
self._interrupt_thread(th, name)
|
||||
|
||||
@staticmethod
|
||||
def _interrupt_thread(th: threading.Thread, name: str):
|
||||
"""Raise SystemExit in target thread to break blocking loops like start_forever."""
|
||||
import ctypes
|
||||
try:
|
||||
tid = th.ident
|
||||
if tid is None:
|
||||
return
|
||||
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
|
||||
ctypes.c_ulong(tid), ctypes.py_object(SystemExit)
|
||||
)
|
||||
if res == 1:
|
||||
logger.info(f"[ChannelManager] Interrupted thread for channel '{name}'")
|
||||
elif res > 1:
|
||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_ulong(tid), None)
|
||||
logger.warning(f"[ChannelManager] Failed to interrupt thread for channel '{name}'")
|
||||
except Exception as e:
|
||||
logger.warning(f"[ChannelManager] Thread interrupt error for '{name}': {e}")
|
||||
|
||||
def restart(self, new_channel_name: str):
|
||||
"""
|
||||
Restart a single channel with a new channel type.
|
||||
|
||||
@@ -65,67 +65,30 @@ class AgentLLMModel(LLMModel):
|
||||
LLM Model adapter that uses COW's existing bot infrastructure
|
||||
"""
|
||||
|
||||
_MODEL_BOT_TYPE_MAP = {
|
||||
"wenxin": const.BAIDU, "wenxin-4": const.BAIDU,
|
||||
"xunfei": const.XUNFEI, const.QWEN: const.QWEN,
|
||||
const.MODELSCOPE: const.MODELSCOPE,
|
||||
}
|
||||
_MODEL_PREFIX_MAP = [
|
||||
("qwen", const.QWEN_DASHSCOPE), ("qwq", const.QWEN_DASHSCOPE), ("qvq", const.QWEN_DASHSCOPE),
|
||||
("gemini", const.GEMINI), ("glm", const.ZHIPU_AI), ("claude", const.CLAUDEAPI),
|
||||
("moonshot", const.MOONSHOT), ("kimi", const.MOONSHOT),
|
||||
("doubao", const.DOUBAO),
|
||||
]
|
||||
|
||||
def __init__(self, bridge: Bridge, bot_type: str = "chat"):
|
||||
# Get model name directly from config
|
||||
from config import conf
|
||||
super().__init__(model=conf().get("model", const.GPT_41))
|
||||
model_name = conf().get("model", const.GPT_41)
|
||||
super().__init__(model=model_name)
|
||||
self.bridge = bridge
|
||||
self.bot_type = bot_type
|
||||
self._bot = None
|
||||
self._bot_model = None
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
from config import conf
|
||||
return conf().get("model", const.GPT_41)
|
||||
|
||||
@model.setter
|
||||
def model(self, value):
|
||||
pass
|
||||
|
||||
def _resolve_bot_type(self, model_name: str) -> str:
|
||||
"""Resolve bot type from model name, matching Bridge.__init__ logic."""
|
||||
from config import conf
|
||||
if conf().get("use_linkai", False) and conf().get("linkai_api_key"):
|
||||
return const.LINKAI
|
||||
if not model_name or not isinstance(model_name, str):
|
||||
return const.CHATGPT
|
||||
if model_name in self._MODEL_BOT_TYPE_MAP:
|
||||
return self._MODEL_BOT_TYPE_MAP[model_name]
|
||||
if model_name.lower().startswith("minimax") or model_name in ["abab6.5-chat"]:
|
||||
return const.MiniMax
|
||||
if model_name in [const.QWEN_TURBO, const.QWEN_PLUS, const.QWEN_MAX]:
|
||||
return const.QWEN_DASHSCOPE
|
||||
if model_name in [const.MOONSHOT, "moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"]:
|
||||
return const.MOONSHOT
|
||||
if model_name in [const.DEEPSEEK_CHAT, const.DEEPSEEK_REASONER]:
|
||||
return const.CHATGPT
|
||||
for prefix, btype in self._MODEL_PREFIX_MAP:
|
||||
if model_name.startswith(prefix):
|
||||
return btype
|
||||
return const.CHATGPT
|
||||
|
||||
self._use_linkai = conf().get("use_linkai", False) and conf().get("linkai_api_key")
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
"""Lazy load the bot, re-create when model changes"""
|
||||
from models.bot_factory import create_bot
|
||||
cur_model = self.model
|
||||
if self._bot is None or self._bot_model != cur_model:
|
||||
bot_type = self._resolve_bot_type(cur_model)
|
||||
self._bot = create_bot(bot_type)
|
||||
self._bot = add_openai_compatible_support(self._bot)
|
||||
self._bot_model = cur_model
|
||||
"""Lazy load the bot and enhance it with tool calling if needed"""
|
||||
if self._bot is None:
|
||||
# If use_linkai is enabled, use LinkAI bot directly
|
||||
if self._use_linkai:
|
||||
self._bot = self.bridge.find_chat_bot(const.LINKAI)
|
||||
else:
|
||||
self._bot = self.bridge.get_bot(self.bot_type)
|
||||
# Automatically add tool calling support if not present
|
||||
self._bot = add_openai_compatible_support(self._bot)
|
||||
|
||||
# Log bot info
|
||||
bot_name = type(self._bot).__name__
|
||||
return self._bot
|
||||
|
||||
def call(self, request: LLMRequest):
|
||||
|
||||
@@ -145,7 +145,7 @@ class AgentInitializer:
|
||||
# after a restart. The full max_turns budget is reserved for the
|
||||
# live conversation that follows.
|
||||
max_turns = conf().get("agent_max_context_turns", 30)
|
||||
restore_turns = max(4, max_turns // 5)
|
||||
restore_turns = min(6, max(1, max_turns // 3))
|
||||
saved = store.load_messages(session_id, max_turns=restore_turns)
|
||||
if saved:
|
||||
with agent.messages_lock:
|
||||
|
||||
@@ -101,8 +101,6 @@ class DingTalkChanel(ChatChannel, dingtalk_stream.ChatbotHandler):
|
||||
# 历史消息id暂存,用于幂等控制
|
||||
self.receivedMsgs = ExpiredDict(conf().get("expires_in_seconds", 3600))
|
||||
self._stream_client = None
|
||||
self._running = False
|
||||
self._event_loop = None
|
||||
logger.debug("[DingTalk] client_id={}, client_secret={} ".format(
|
||||
self.dingtalk_client_id, self.dingtalk_client_secret))
|
||||
# 无需群校验和前缀
|
||||
@@ -116,54 +114,21 @@ class DingTalkChanel(ChatChannel, dingtalk_stream.ChatbotHandler):
|
||||
self._robot_code = None
|
||||
|
||||
def startup(self):
|
||||
import asyncio
|
||||
self.dingtalk_client_id = conf().get('dingtalk_client_id')
|
||||
self.dingtalk_client_secret = conf().get('dingtalk_client_secret')
|
||||
self._running = True
|
||||
credential = dingtalk_stream.Credential(self.dingtalk_client_id, self.dingtalk_client_secret)
|
||||
client = dingtalk_stream.DingTalkStreamClient(credential)
|
||||
self._stream_client = client
|
||||
client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self)
|
||||
logger.info("[DingTalk] ✅ Stream client initialized, ready to receive messages")
|
||||
_first_connect = True
|
||||
while self._running:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
self._event_loop = loop
|
||||
try:
|
||||
if not _first_connect:
|
||||
logger.info("[DingTalk] Reconnecting...")
|
||||
_first_connect = False
|
||||
loop.run_until_complete(client.start())
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
logger.info("[DingTalk] Startup loop received stop signal, exiting")
|
||||
break
|
||||
except Exception as e:
|
||||
if not self._running:
|
||||
break
|
||||
logger.warning(f"[DingTalk] Stream connection error: {e}, reconnecting in 3s...")
|
||||
time.sleep(3)
|
||||
finally:
|
||||
self._event_loop = None
|
||||
try:
|
||||
loop.close()
|
||||
except Exception:
|
||||
pass
|
||||
logger.info("[DingTalk] Startup loop exited")
|
||||
logger.info("[DingTalk] ✅ Stream connected, ready to receive messages")
|
||||
client.start_forever()
|
||||
|
||||
def stop(self):
|
||||
import asyncio
|
||||
logger.info("[DingTalk] stop() called, setting _running=False")
|
||||
self._running = False
|
||||
loop = self._event_loop
|
||||
if loop and not loop.is_closed():
|
||||
if self._stream_client:
|
||||
try:
|
||||
loop.call_soon_threadsafe(loop.stop)
|
||||
logger.info("[DingTalk] Sent stop signal to event loop")
|
||||
self._stream_client.stop()
|
||||
logger.info("[DingTalk] Stream client stopped")
|
||||
except Exception as e:
|
||||
logger.warning(f"[DingTalk] Error stopping event loop: {e}")
|
||||
self._stream_client = None
|
||||
logger.info("[DingTalk] stop() completed")
|
||||
logger.warning(f"[DingTalk] Error stopping stream client: {e}")
|
||||
self._stream_client = None
|
||||
|
||||
def get_access_token(self):
|
||||
"""
|
||||
@@ -500,21 +465,23 @@ class DingTalkChanel(ChatChannel, dingtalk_stream.ChatbotHandler):
|
||||
async def process(self, callback: dingtalk_stream.CallbackMessage):
|
||||
try:
|
||||
incoming_message = dingtalk_stream.ChatbotMessage.from_dict(callback.data)
|
||||
|
||||
|
||||
# 缓存 robot_code,用于后续图片下载
|
||||
if hasattr(incoming_message, 'robot_code'):
|
||||
self._robot_code_cache = incoming_message.robot_code
|
||||
|
||||
# Filter out stale messages from before channel startup (offline backlog)
|
||||
create_at = getattr(incoming_message, 'create_at', None)
|
||||
if create_at:
|
||||
msg_age_s = time.time() - int(create_at) / 1000
|
||||
if msg_age_s > 60:
|
||||
logger.warning(f"[DingTalk] stale msg filtered (age={msg_age_s:.0f}s), "
|
||||
f"msg_id={getattr(incoming_message, 'message_id', 'N/A')}")
|
||||
return AckMessage.STATUS_OK, 'OK'
|
||||
|
||||
image_download_handler = self
|
||||
|
||||
# Debug: 打印完整的 event 数据
|
||||
logger.debug(f"[DingTalk] ===== Incoming Message Debug =====")
|
||||
logger.debug(f"[DingTalk] callback.data keys: {callback.data.keys() if hasattr(callback.data, 'keys') else 'N/A'}")
|
||||
logger.debug(f"[DingTalk] incoming_message attributes: {dir(incoming_message)}")
|
||||
logger.debug(f"[DingTalk] robot_code: {getattr(incoming_message, 'robot_code', 'N/A')}")
|
||||
logger.debug(f"[DingTalk] chatbot_corp_id: {getattr(incoming_message, 'chatbot_corp_id', 'N/A')}")
|
||||
logger.debug(f"[DingTalk] chatbot_user_id: {getattr(incoming_message, 'chatbot_user_id', 'N/A')}")
|
||||
logger.debug(f"[DingTalk] conversation_id: {getattr(incoming_message, 'conversation_id', 'N/A')}")
|
||||
logger.debug(f"[DingTalk] Raw callback.data: {callback.data}")
|
||||
logger.debug(f"[DingTalk] =====================================")
|
||||
|
||||
image_download_handler = self # 传入方法所在的类实例
|
||||
dingtalk_msg = DingTalkMessage(incoming_message, image_download_handler)
|
||||
|
||||
if dingtalk_msg.is_group:
|
||||
@@ -523,7 +490,8 @@ class DingTalkChanel(ChatChannel, dingtalk_stream.ChatbotHandler):
|
||||
self.handle_single(dingtalk_msg)
|
||||
return AckMessage.STATUS_OK, 'OK'
|
||||
except Exception as e:
|
||||
logger.error(f"[DingTalk] process error: {e}", exc_info=True)
|
||||
logger.error(f"[DingTalk] process error: {e}")
|
||||
logger.exception(e) # 打印完整堆栈跟踪
|
||||
return AckMessage.STATUS_SYSTEM_EXCEPTION, 'ERROR'
|
||||
|
||||
@time_checker
|
||||
|
||||
@@ -61,8 +61,6 @@ class FeiShuChanel(ChatChannel):
|
||||
# 历史消息id暂存,用于幂等控制
|
||||
self.receivedMsgs = ExpiredDict(60 * 60 * 7.1)
|
||||
self._http_server = None
|
||||
self._ws_client = None
|
||||
self._ws_thread = None
|
||||
logger.debug("[FeiShu] app_id={}, app_secret={}, verification_token={}, event_mode={}".format(
|
||||
self.feishu_app_id, self.feishu_app_secret, self.feishu_token, self.feishu_event_mode))
|
||||
# 无需群校验和前缀
|
||||
@@ -75,37 +73,12 @@ class FeiShuChanel(ChatChannel):
|
||||
raise Exception("lark_oapi not installed")
|
||||
|
||||
def startup(self):
|
||||
self.feishu_app_id = conf().get('feishu_app_id')
|
||||
self.feishu_app_secret = conf().get('feishu_app_secret')
|
||||
self.feishu_token = conf().get('feishu_token')
|
||||
self.feishu_event_mode = conf().get('feishu_event_mode', 'websocket')
|
||||
if self.feishu_event_mode == 'websocket':
|
||||
self._startup_websocket()
|
||||
else:
|
||||
self._startup_webhook()
|
||||
|
||||
def stop(self):
|
||||
import ctypes
|
||||
logger.info("[FeiShu] stop() called")
|
||||
ws_client = self._ws_client
|
||||
self._ws_client = None
|
||||
ws_thread = self._ws_thread
|
||||
self._ws_thread = None
|
||||
# Interrupt the ws thread first so its blocking start() unblocks
|
||||
if ws_thread and ws_thread.is_alive():
|
||||
try:
|
||||
tid = ws_thread.ident
|
||||
if tid:
|
||||
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
|
||||
ctypes.c_ulong(tid), ctypes.py_object(SystemExit)
|
||||
)
|
||||
if res == 1:
|
||||
logger.info("[FeiShu] Interrupted ws thread via ctypes")
|
||||
elif res > 1:
|
||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_ulong(tid), None)
|
||||
except Exception as e:
|
||||
logger.warning(f"[FeiShu] Error interrupting ws thread: {e}")
|
||||
# lark.ws.Client has no stop() method; thread interruption above is sufficient
|
||||
if self._http_server:
|
||||
try:
|
||||
self._http_server.stop()
|
||||
@@ -113,7 +86,6 @@ class FeiShuChanel(ChatChannel):
|
||||
except Exception as e:
|
||||
logger.warning(f"[FeiShu] Error stopping HTTP server: {e}")
|
||||
self._http_server = None
|
||||
logger.info("[FeiShu] stop() completed")
|
||||
|
||||
def _startup_webhook(self):
|
||||
"""启动HTTP服务器接收事件(webhook模式)"""
|
||||
@@ -157,26 +129,29 @@ class FeiShuChanel(ChatChannel):
|
||||
.register_p2_im_message_receive_v1(handle_message_event) \
|
||||
.build()
|
||||
|
||||
# 尝试连接,如果遇到SSL错误则自动禁用证书验证
|
||||
def start_client_with_retry():
|
||||
"""Run ws client in this thread with its own event loop to avoid conflicts."""
|
||||
import asyncio
|
||||
"""启动websocket客户端,自动处理SSL证书错误"""
|
||||
# 全局禁用SSL证书验证(在导入lark_oapi之前设置)
|
||||
import ssl as ssl_module
|
||||
|
||||
# 保存原始的SSL上下文创建方法
|
||||
original_create_default_context = ssl_module.create_default_context
|
||||
|
||||
def create_unverified_context(*args, **kwargs):
|
||||
"""创建一个不验证证书的SSL上下文"""
|
||||
context = original_create_default_context(*args, **kwargs)
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
return context
|
||||
|
||||
# Give this thread its own event loop so lark SDK can call run_until_complete
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
# 尝试正常连接,如果失败则禁用SSL验证
|
||||
for attempt in range(2):
|
||||
try:
|
||||
if attempt == 1:
|
||||
logger.warning("[FeiShu] Retrying with SSL verification disabled...")
|
||||
# 第二次尝试:禁用SSL验证
|
||||
logger.warning("[FeiShu] SSL certificate verification disabled due to certificate error. "
|
||||
"This may happen when using corporate proxy or self-signed certificates.")
|
||||
ssl_module.create_default_context = create_unverified_context
|
||||
ssl_module._create_unverified_context = create_unverified_context
|
||||
|
||||
@@ -184,36 +159,39 @@ class FeiShuChanel(ChatChannel):
|
||||
self.feishu_app_id,
|
||||
self.feishu_app_secret,
|
||||
event_handler=event_handler,
|
||||
log_level=lark.LogLevel.WARNING
|
||||
log_level=lark.LogLevel.DEBUG if conf().get("debug") else lark.LogLevel.WARNING
|
||||
)
|
||||
self._ws_client = ws_client
|
||||
|
||||
logger.debug("[FeiShu] Websocket client starting...")
|
||||
ws_client.start()
|
||||
# 如果成功启动,跳出循环
|
||||
break
|
||||
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
logger.info("[FeiShu] Websocket thread received stop signal")
|
||||
break
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
is_ssl_error = ("CERTIFICATE_VERIFY_FAILED" in error_msg
|
||||
or "certificate verify failed" in error_msg.lower())
|
||||
if is_ssl_error and attempt == 0:
|
||||
logger.warning(f"[FeiShu] SSL error: {error_msg}, retrying...")
|
||||
continue
|
||||
logger.error(f"[FeiShu] Websocket client error: {e}", exc_info=True)
|
||||
ssl_module.create_default_context = original_create_default_context
|
||||
break
|
||||
try:
|
||||
loop.close()
|
||||
except Exception:
|
||||
pass
|
||||
logger.info("[FeiShu] Websocket thread exited")
|
||||
# 检查是否是SSL证书验证错误
|
||||
is_ssl_error = "CERTIFICATE_VERIFY_FAILED" in error_msg or "certificate verify failed" in error_msg.lower()
|
||||
|
||||
if is_ssl_error and attempt == 0:
|
||||
# 第一次遇到SSL错误,记录日志并继续循环(下次会禁用验证)
|
||||
logger.warning(f"[FeiShu] SSL certificate verification failed: {error_msg}")
|
||||
logger.info("[FeiShu] Retrying connection with SSL verification disabled...")
|
||||
continue
|
||||
else:
|
||||
# 其他错误或禁用验证后仍失败,抛出异常
|
||||
logger.error(f"[FeiShu] Websocket client error: {e}", exc_info=True)
|
||||
# 恢复原始方法
|
||||
ssl_module.create_default_context = original_create_default_context
|
||||
raise
|
||||
|
||||
# 注意:不恢复原始方法,因为ws_client.start()会持续运行
|
||||
|
||||
# 在新线程中启动客户端,避免阻塞主线程
|
||||
ws_thread = threading.Thread(target=start_client_with_retry, daemon=True)
|
||||
self._ws_thread = ws_thread
|
||||
ws_thread.start()
|
||||
logger.info("[FeiShu] ✅ Websocket thread started, ready to receive messages")
|
||||
|
||||
# 保持主线程运行
|
||||
logger.info("[FeiShu] ✅ Websocket connected, ready to receive messages")
|
||||
ws_thread.join()
|
||||
|
||||
def _handle_message_event(self, event: dict):
|
||||
@@ -234,15 +212,6 @@ class FeiShuChanel(ChatChannel):
|
||||
return
|
||||
self.receivedMsgs[msg_id] = True
|
||||
|
||||
# Filter out stale messages from before channel startup (offline backlog)
|
||||
import time as _time
|
||||
create_time_ms = msg.get("create_time")
|
||||
if create_time_ms:
|
||||
msg_age_s = _time.time() - int(create_time_ms) / 1000
|
||||
if msg_age_s > 60:
|
||||
logger.warning(f"[FeiShu] stale msg filtered (age={msg_age_s:.0f}s), msg_id={msg_id}")
|
||||
return
|
||||
|
||||
is_group = False
|
||||
chat_type = msg.get("chat_type")
|
||||
|
||||
|
||||
@@ -294,122 +294,68 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-6">
|
||||
|
||||
<!-- Model Config Card -->
|
||||
<div class="bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6">
|
||||
<div class="flex items-center gap-3 mb-5">
|
||||
<div class="placeholder-card bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-9 h-9 rounded-lg bg-primary-50 dark:bg-primary-900/30 flex items-center justify-center">
|
||||
<i class="fas fa-microchip text-primary-500 text-sm"></i>
|
||||
</div>
|
||||
<h3 class="font-semibold text-slate-800 dark:text-slate-100" data-i18n="config_model">Model Configuration</h3>
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<!-- Provider -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5" data-i18n="config_provider">Provider</label>
|
||||
<div id="cfg-provider" class="cfg-dropdown" tabindex="0">
|
||||
<div class="cfg-dropdown-selected">
|
||||
<span class="cfg-dropdown-text">--</span>
|
||||
<i class="fas fa-chevron-down cfg-dropdown-arrow"></i>
|
||||
</div>
|
||||
<div class="cfg-dropdown-menu"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Model -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5" data-i18n="config_model_name">Model</label>
|
||||
<div id="cfg-model-select" class="cfg-dropdown" tabindex="0">
|
||||
<div class="cfg-dropdown-selected">
|
||||
<span class="cfg-dropdown-text">--</span>
|
||||
<i class="fas fa-chevron-down cfg-dropdown-arrow"></i>
|
||||
</div>
|
||||
<div class="cfg-dropdown-menu"></div>
|
||||
</div>
|
||||
<div id="cfg-model-custom-wrap" class="mt-2 hidden">
|
||||
<input id="cfg-model-custom" type="text"
|
||||
class="w-full px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600
|
||||
bg-slate-50 dark:bg-white/5 text-sm text-slate-800 dark:text-slate-100
|
||||
focus:outline-none focus:border-primary-500 font-mono transition-colors"
|
||||
data-i18n-placeholder="config_custom_model_hint" placeholder="Enter custom model name">
|
||||
</div>
|
||||
</div>
|
||||
<!-- API Key -->
|
||||
<div id="cfg-api-key-wrap">
|
||||
<label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5">API Key</label>
|
||||
<div class="relative">
|
||||
<input id="cfg-api-key" type="text" autocomplete="off" data-1p-ignore data-lpignore="true"
|
||||
class="w-full px-3 py-2 pr-10 rounded-lg border border-slate-200 dark:border-slate-600
|
||||
bg-slate-50 dark:bg-white/5 text-sm text-slate-800 dark:text-slate-100
|
||||
focus:outline-none focus:border-primary-500 font-mono transition-colors cfg-key-masked"
|
||||
placeholder="sk-...">
|
||||
<button type="button" id="cfg-api-key-toggle"
|
||||
class="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600
|
||||
dark:hover:text-slate-300 cursor-pointer transition-colors p-1"
|
||||
onclick="toggleApiKeyVisibility()">
|
||||
<i class="fas fa-eye text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- API Base -->
|
||||
<div id="cfg-api-base-wrap" class="hidden">
|
||||
<label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5">API Base</label>
|
||||
<input id="cfg-api-base" type="text"
|
||||
class="w-full px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600
|
||||
bg-slate-50 dark:bg-white/5 text-sm text-slate-800 dark:text-slate-100
|
||||
focus:outline-none focus:border-primary-500 font-mono transition-colors"
|
||||
placeholder="https://...">
|
||||
</div>
|
||||
<!-- Save Model Button -->
|
||||
<div class="flex items-center justify-end gap-3 pt-1">
|
||||
<span id="cfg-model-status" class="text-xs text-primary-500 opacity-0 transition-opacity duration-300"></span>
|
||||
<button id="cfg-model-save"
|
||||
class="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white text-sm font-medium
|
||||
cursor-pointer transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onclick="saveModelConfig()" data-i18n="config_save">Save</button>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-4 p-3 rounded-lg bg-slate-50 dark:bg-white/5">
|
||||
<span class="text-sm font-medium text-slate-500 dark:text-slate-400 w-32 flex-shrink-0">Model</span>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 flex-1 font-mono" id="cfg-model">--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Agent Config Card -->
|
||||
<div class="bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6">
|
||||
<div class="flex items-center gap-3 mb-5">
|
||||
<div class="placeholder-card bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-9 h-9 rounded-lg bg-emerald-50 dark:bg-emerald-900/30 flex items-center justify-center">
|
||||
<i class="fas fa-robot text-emerald-500 text-sm"></i>
|
||||
</div>
|
||||
<h3 class="font-semibold text-slate-800 dark:text-slate-100" data-i18n="config_agent">Agent Configuration</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5" data-i18n="config_max_tokens">Max Context Tokens</label>
|
||||
<input id="cfg-max-tokens" type="number" min="1000" max="200000" step="1000"
|
||||
class="w-full px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600
|
||||
bg-slate-50 dark:bg-white/5 text-sm text-slate-800 dark:text-slate-100
|
||||
focus:outline-none focus:border-primary-500 font-mono transition-colors">
|
||||
<div class="flex items-center gap-4 p-3 rounded-lg bg-slate-50 dark:bg-white/5">
|
||||
<span class="text-sm font-medium text-slate-500 dark:text-slate-400 w-32 flex-shrink-0" data-i18n="config_agent_enabled">Agent Mode</span>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 flex-1" id="cfg-agent">--</span>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5" data-i18n="config_max_turns">Max Context Turns</label>
|
||||
<input id="cfg-max-turns" type="number" min="1" max="100" step="1"
|
||||
class="w-full px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600
|
||||
bg-slate-50 dark:bg-white/5 text-sm text-slate-800 dark:text-slate-100
|
||||
focus:outline-none focus:border-primary-500 font-mono transition-colors">
|
||||
<div class="flex items-center gap-4 p-3 rounded-lg bg-slate-50 dark:bg-white/5">
|
||||
<span class="text-sm font-medium text-slate-500 dark:text-slate-400 w-32 flex-shrink-0" data-i18n="config_max_tokens">Max Tokens</span>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 flex-1 font-mono" id="cfg-max-tokens">--</span>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5" data-i18n="config_max_steps">Max Steps</label>
|
||||
<input id="cfg-max-steps" type="number" min="1" max="50" step="1"
|
||||
class="w-full px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600
|
||||
bg-slate-50 dark:bg-white/5 text-sm text-slate-800 dark:text-slate-100
|
||||
focus:outline-none focus:border-primary-500 font-mono transition-colors">
|
||||
<div class="flex items-center gap-4 p-3 rounded-lg bg-slate-50 dark:bg-white/5">
|
||||
<span class="text-sm font-medium text-slate-500 dark:text-slate-400 w-32 flex-shrink-0" data-i18n="config_max_turns">Max Turns</span>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 flex-1 font-mono" id="cfg-max-turns">--</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-3 pt-1">
|
||||
<span id="cfg-agent-status" class="text-xs text-primary-500 opacity-0 transition-opacity duration-300"></span>
|
||||
<button id="cfg-agent-save"
|
||||
class="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white text-sm font-medium
|
||||
cursor-pointer transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onclick="saveAgentConfig()" data-i18n="config_save">Save</button>
|
||||
<div class="flex items-center gap-4 p-3 rounded-lg bg-slate-50 dark:bg-white/5">
|
||||
<span class="text-sm font-medium text-slate-500 dark:text-slate-400 w-32 flex-shrink-0" data-i18n="config_max_steps">Max Steps</span>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 flex-1 font-mono" id="cfg-max-steps">--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Channel Config Card -->
|
||||
<div class="placeholder-card bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-9 h-9 rounded-lg bg-amber-50 dark:bg-amber-900/30 flex items-center justify-center">
|
||||
<i class="fas fa-tower-broadcast text-amber-500 text-sm"></i>
|
||||
</div>
|
||||
<h3 class="font-semibold text-slate-800 dark:text-slate-100" data-i18n="config_channel">Channel Configuration</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-4 p-3 rounded-lg bg-slate-50 dark:bg-white/5">
|
||||
<span class="text-sm font-medium text-slate-500 dark:text-slate-400 w-32 flex-shrink-0" data-i18n="config_channel_type">Channel Type</span>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200 flex-1 font-mono" id="cfg-channel">--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Coming Soon Banner -->
|
||||
<div class="mt-6 p-4 rounded-xl bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800/50 flex items-center gap-3">
|
||||
<i class="fas fa-info-circle text-primary-500"></i>
|
||||
<span class="text-sm text-primary-700 dark:text-primary-300" data-i18n="config_coming_soon">Full editing capability coming soon. Currently displaying read-only configuration.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -427,35 +373,14 @@
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1" data-i18n="skills_desc">View, enable, or disable agent skills</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Built-in Tools Section -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500" data-i18n="tools_section_title">Built-in Tools</span>
|
||||
<span id="tools-count-badge" class="hidden px-2 py-0.5 rounded-full text-xs bg-slate-100 dark:bg-white/10 text-slate-500 dark:text-slate-400"></span>
|
||||
<div id="skills-empty" class="flex flex-col items-center justify-center py-20">
|
||||
<div class="w-16 h-16 rounded-2xl bg-amber-50 dark:bg-amber-900/20 flex items-center justify-center mb-4">
|
||||
<i class="fas fa-bolt text-amber-400 text-xl"></i>
|
||||
</div>
|
||||
<div id="tools-empty" class="flex items-center gap-2 py-4 text-slate-400 dark:text-slate-500 text-sm">
|
||||
<i class="fas fa-spinner fa-spin text-xs"></i>
|
||||
<span data-i18n="tools_loading">Loading tools...</span>
|
||||
</div>
|
||||
<div id="tools-list" class="grid gap-3 sm:grid-cols-2 hidden"></div>
|
||||
</div>
|
||||
|
||||
<!-- Skills Section -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500" data-i18n="skills_section_title">Skills</span>
|
||||
<span id="skills-count-badge" class="hidden px-2 py-0.5 rounded-full text-xs bg-slate-100 dark:bg-white/10 text-slate-500 dark:text-slate-400"></span>
|
||||
</div>
|
||||
<div id="skills-empty" class="flex flex-col items-center justify-center py-12">
|
||||
<div class="w-14 h-14 rounded-2xl bg-amber-50 dark:bg-amber-900/20 flex items-center justify-center mb-3">
|
||||
<i class="fas fa-bolt text-amber-400 text-lg"></i>
|
||||
</div>
|
||||
<p class="text-slate-500 dark:text-slate-400 font-medium" data-i18n="skills_loading">Loading skills...</p>
|
||||
<p class="text-sm text-slate-400 dark:text-slate-500 mt-1" data-i18n="skills_loading_desc">Skills will be displayed here after loading</p>
|
||||
</div>
|
||||
<div id="skills-list" class="grid gap-4 sm:grid-cols-2"></div>
|
||||
<p class="text-slate-500 dark:text-slate-400 font-medium" data-i18n="skills_loading">Loading skills...</p>
|
||||
<p class="text-sm text-slate-400 dark:text-slate-500 mt-1" data-i18n="skills_loading_desc">Skills will be displayed here after loading</p>
|
||||
</div>
|
||||
<div id="skills-list" class="grid gap-4 sm:grid-cols-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -535,15 +460,8 @@
|
||||
<h2 class="text-xl font-bold text-slate-800 dark:text-slate-100" data-i18n="channels_title">Channels</h2>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1" data-i18n="channels_desc">View and manage messaging channels</p>
|
||||
</div>
|
||||
<button id="add-channel-btn" onclick="openAddChannelPanel()"
|
||||
class="flex items-center gap-2 px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600
|
||||
text-white text-sm font-medium cursor-pointer transition-colors duration-150">
|
||||
<i class="fas fa-plus text-xs"></i>
|
||||
<span data-i18n="channels_add">Connect</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="channels-content" class="grid gap-4"></div>
|
||||
<div id="channels-add-panel" class="hidden mt-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -610,32 +528,6 @@
|
||||
</div><!-- /main-content -->
|
||||
</div><!-- /app -->
|
||||
|
||||
<!-- Confirm Dialog -->
|
||||
<div id="confirm-dialog-overlay" class="fixed inset-0 bg-black/50 z-[100] hidden flex items-center justify-center">
|
||||
<div class="bg-white dark:bg-[#1A1A1A] rounded-2xl border border-slate-200 dark:border-white/10 shadow-xl
|
||||
w-full max-w-sm mx-4 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="w-10 h-10 rounded-xl bg-red-50 dark:bg-red-900/20 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-triangle-exclamation text-red-500"></i>
|
||||
</div>
|
||||
<h3 id="confirm-dialog-title" class="font-semibold text-slate-800 dark:text-slate-100 text-base"></h3>
|
||||
</div>
|
||||
<p id="confirm-dialog-message" class="text-sm text-slate-500 dark:text-slate-400 leading-relaxed ml-[52px]"></p>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-slate-100 dark:border-white/5">
|
||||
<button id="confirm-dialog-cancel"
|
||||
class="px-4 py-2 rounded-lg border border-slate-200 dark:border-white/10
|
||||
text-slate-600 dark:text-slate-300 text-sm font-medium
|
||||
hover:bg-slate-50 dark:hover:bg-white/5
|
||||
cursor-pointer transition-colors duration-150"></button>
|
||||
<button id="confirm-dialog-ok"
|
||||
class="px-4 py-2 rounded-lg bg-red-500 hover:bg-red-600 text-white text-sm font-medium
|
||||
cursor-pointer transition-colors duration-150"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/console.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -222,121 +222,6 @@
|
||||
/* Tool failed state */
|
||||
.agent-tool-step.tool-failed .tool-name { color: #f87171; }
|
||||
|
||||
/* Config form controls */
|
||||
#view-config input[type="text"],
|
||||
#view-config input[type="number"],
|
||||
#view-config input[type="password"] {
|
||||
height: 40px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
#view-config input:focus {
|
||||
border-color: #4ABE6E;
|
||||
box-shadow: 0 0 0 3px rgba(74, 190, 110, 0.12);
|
||||
}
|
||||
#view-config input[type="text"]:hover,
|
||||
#view-config input[type="number"]:hover,
|
||||
#view-config input[type="password"]:hover {
|
||||
border-color: #94a3b8;
|
||||
}
|
||||
.dark #view-config input[type="text"]:hover,
|
||||
.dark #view-config input[type="number"]:hover,
|
||||
.dark #view-config input[type="password"]:hover {
|
||||
border-color: #64748b;
|
||||
}
|
||||
|
||||
/* Custom dropdown */
|
||||
.cfg-dropdown {
|
||||
position: relative;
|
||||
outline: none;
|
||||
}
|
||||
.cfg-dropdown-selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 40px;
|
||||
padding: 0 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
font-size: 0.875rem;
|
||||
color: #1e293b;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
.dark .cfg-dropdown-selected {
|
||||
border-color: #475569;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #f1f5f9;
|
||||
}
|
||||
.cfg-dropdown-selected:hover { border-color: #94a3b8; }
|
||||
.dark .cfg-dropdown-selected:hover { border-color: #64748b; }
|
||||
.cfg-dropdown.open .cfg-dropdown-selected,
|
||||
.cfg-dropdown:focus .cfg-dropdown-selected {
|
||||
border-color: #4ABE6E;
|
||||
box-shadow: 0 0 0 3px rgba(74, 190, 110, 0.12);
|
||||
}
|
||||
.cfg-dropdown-arrow {
|
||||
font-size: 0.625rem;
|
||||
color: #94a3b8;
|
||||
transition: transform 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.cfg-dropdown.open .cfg-dropdown-arrow { transform: rotate(180deg); }
|
||||
.cfg-dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 50;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
padding: 4px;
|
||||
}
|
||||
.dark .cfg-dropdown-menu {
|
||||
border-color: #334155;
|
||||
background: #1e1e1e;
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.cfg-dropdown.open .cfg-dropdown-menu { display: block; }
|
||||
.cfg-dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.875rem;
|
||||
color: #334155;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.dark .cfg-dropdown-item { color: #cbd5e1; }
|
||||
.cfg-dropdown-item:hover { background: #f1f5f9; }
|
||||
.dark .cfg-dropdown-item:hover { background: rgba(255, 255, 255, 0.08); }
|
||||
.cfg-dropdown-item.active {
|
||||
background: rgba(74, 190, 110, 0.1);
|
||||
color: #228547;
|
||||
font-weight: 500;
|
||||
}
|
||||
.dark .cfg-dropdown-item.active {
|
||||
background: rgba(74, 190, 110, 0.15);
|
||||
color: #74E9A4;
|
||||
}
|
||||
|
||||
/* API Key masking via CSS (avoids browser password prompts) */
|
||||
.cfg-key-masked {
|
||||
-webkit-text-security: disc;
|
||||
text-security: disc;
|
||||
}
|
||||
|
||||
/* Chat Input */
|
||||
#chat-input {
|
||||
resize: none; height: 42px; max-height: 180px;
|
||||
|
||||
@@ -28,29 +28,15 @@ const I18N = {
|
||||
config_agent_enabled: 'Agent 模式', config_max_tokens: '最大 Token',
|
||||
config_max_turns: '最大轮次', config_max_steps: '最大步数',
|
||||
config_channel_type: '通道类型',
|
||||
config_provider: '模型厂商', config_model_name: '模型',
|
||||
config_custom_model_hint: '输入自定义模型名称',
|
||||
config_save: '保存', config_saved: '已保存',
|
||||
config_save_error: '保存失败',
|
||||
config_custom_option: '自定义...',
|
||||
config_coming_soon: '完整编辑功能即将推出,当前为只读展示。',
|
||||
skills_title: '技能管理', skills_desc: '查看、启用或禁用 Agent 技能',
|
||||
skills_loading: '加载技能中...', skills_loading_desc: '技能加载后将显示在此处',
|
||||
tools_section_title: '内置工具', tools_loading: '加载工具中...',
|
||||
skills_section_title: '技能', skill_enable: '启用', skill_disable: '禁用',
|
||||
skill_toggle_error: '操作失败,请稍后再试',
|
||||
memory_title: '记忆管理', memory_desc: '查看 Agent 记忆文件和内容',
|
||||
memory_loading: '加载记忆文件中...', memory_loading_desc: '记忆文件将显示在此处',
|
||||
memory_back: '返回列表',
|
||||
memory_col_name: '文件名', memory_col_type: '类型', memory_col_size: '大小', memory_col_updated: '更新时间',
|
||||
channels_title: '通道管理', channels_desc: '管理已接入的消息通道',
|
||||
channels_add: '接入通道', channels_disconnect: '断开',
|
||||
channels_save: '保存配置', channels_saved: '已保存', channels_save_error: '保存失败',
|
||||
channels_restarted: '已保存并重启',
|
||||
channels_connect_btn: '接入', channels_cancel: '取消',
|
||||
channels_select_placeholder: '选择要接入的通道...',
|
||||
channels_empty: '暂未接入任何通道', channels_empty_desc: '点击右上角「接入通道」按钮开始配置',
|
||||
channels_disconnect_confirm: '确认断开该通道?配置将保留但通道会停止运行。',
|
||||
channels_connected: '已接入', channels_connecting: '接入中...',
|
||||
channels_title: '通道管理', channels_desc: '查看和管理消息通道',
|
||||
channels_coming: '即将推出', channels_coming_desc: '通道管理功能即将在此提供',
|
||||
tasks_title: '定时任务', tasks_desc: '查看和管理定时任务',
|
||||
tasks_coming: '即将推出', tasks_coming_desc: '定时任务管理功能即将在此提供',
|
||||
logs_title: '日志', logs_desc: '实时日志输出 (run.log)',
|
||||
@@ -74,29 +60,15 @@ const I18N = {
|
||||
config_agent_enabled: 'Agent Mode', config_max_tokens: 'Max Tokens',
|
||||
config_max_turns: 'Max Turns', config_max_steps: 'Max Steps',
|
||||
config_channel_type: 'Channel Type',
|
||||
config_provider: 'Provider', config_model_name: 'Model',
|
||||
config_custom_model_hint: 'Enter custom model name',
|
||||
config_save: 'Save', config_saved: 'Saved',
|
||||
config_save_error: 'Save failed',
|
||||
config_custom_option: 'Custom...',
|
||||
config_coming_soon: 'Full editing capability coming soon. Currently displaying read-only configuration.',
|
||||
skills_title: 'Skills', skills_desc: 'View, enable, or disable agent skills',
|
||||
skills_loading: 'Loading skills...', skills_loading_desc: 'Skills will be displayed here after loading',
|
||||
tools_section_title: 'Built-in Tools', tools_loading: 'Loading tools...',
|
||||
skills_section_title: 'Skills', skill_enable: 'Enable', skill_disable: 'Disable',
|
||||
skill_toggle_error: 'Operation failed, please try again',
|
||||
memory_title: 'Memory', memory_desc: 'View agent memory files and contents',
|
||||
memory_loading: 'Loading memory files...', memory_loading_desc: 'Memory files will be displayed here',
|
||||
memory_back: 'Back to list',
|
||||
memory_col_name: 'Filename', memory_col_type: 'Type', memory_col_size: 'Size', memory_col_updated: 'Updated',
|
||||
channels_title: 'Channels', channels_desc: 'Manage connected messaging channels',
|
||||
channels_add: 'Connect', channels_disconnect: 'Disconnect',
|
||||
channels_save: 'Save', channels_saved: 'Saved', channels_save_error: 'Save failed',
|
||||
channels_restarted: 'Saved & Restarted',
|
||||
channels_connect_btn: 'Connect', channels_cancel: 'Cancel',
|
||||
channels_select_placeholder: 'Select a channel to connect...',
|
||||
channels_empty: 'No channels connected', channels_empty_desc: 'Click the "Connect" button above to get started',
|
||||
channels_disconnect_confirm: 'Disconnect this channel? Config will be preserved but the channel will stop.',
|
||||
channels_connected: 'Connected', channels_connecting: 'Connecting...',
|
||||
channels_title: 'Channels', channels_desc: 'View and manage messaging channels',
|
||||
channels_coming: 'Coming Soon', channels_coming_desc: 'Channel management will be available here',
|
||||
tasks_title: 'Scheduled Tasks', tasks_desc: 'View and manage scheduled tasks',
|
||||
tasks_coming: 'Coming Soon', tasks_coming_desc: 'Scheduled task management will be available here',
|
||||
logs_title: 'Logs', logs_desc: 'Real-time log output (run.log)',
|
||||
@@ -264,7 +236,7 @@ let isPolling = false;
|
||||
let loadingContainers = {};
|
||||
let activeStreams = {}; // request_id -> EventSource
|
||||
let isComposing = false;
|
||||
let appConfig = { use_agent: false, title: 'CowAgent', subtitle: '', providers: {}, api_bases: {} };
|
||||
let appConfig = { use_agent: false, title: 'CowAgent', subtitle: '' };
|
||||
|
||||
const SESSION_ID_KEY = 'cow_session_id';
|
||||
|
||||
@@ -296,8 +268,14 @@ fetch('/config').then(r => r.json()).then(data => {
|
||||
appConfig = data;
|
||||
const title = data.title || 'CowAgent';
|
||||
document.getElementById('welcome-title').textContent = title;
|
||||
initConfigView(data);
|
||||
document.getElementById('cfg-model').textContent = data.model || '--';
|
||||
document.getElementById('cfg-agent').textContent = data.use_agent ? 'Enabled' : 'Disabled';
|
||||
document.getElementById('cfg-max-tokens').textContent = data.agent_max_context_tokens || '--';
|
||||
document.getElementById('cfg-max-turns').textContent = data.agent_max_context_turns || '--';
|
||||
document.getElementById('cfg-max-steps').textContent = data.agent_max_steps || '--';
|
||||
document.getElementById('cfg-channel').textContent = data.channel_type || '--';
|
||||
}
|
||||
// Load conversation history after config is ready
|
||||
loadHistory(1);
|
||||
}).catch(() => { loadHistory(1); });
|
||||
|
||||
@@ -842,471 +820,70 @@ function applyHighlighting(container) {
|
||||
// =====================================================================
|
||||
// Config View
|
||||
// =====================================================================
|
||||
let configProviders = {};
|
||||
let configApiBases = {};
|
||||
let configApiKeys = {};
|
||||
let configCurrentModel = '';
|
||||
let cfgProviderValue = '';
|
||||
let cfgModelValue = '';
|
||||
|
||||
// --- Custom dropdown helper ---
|
||||
function initDropdown(el, options, selectedValue, onChange) {
|
||||
const textEl = el.querySelector('.cfg-dropdown-text');
|
||||
const menuEl = el.querySelector('.cfg-dropdown-menu');
|
||||
const selEl = el.querySelector('.cfg-dropdown-selected');
|
||||
|
||||
el._ddValue = selectedValue || '';
|
||||
el._ddOnChange = onChange;
|
||||
|
||||
function render() {
|
||||
menuEl.innerHTML = '';
|
||||
options.forEach(opt => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'cfg-dropdown-item' + (opt.value === el._ddValue ? ' active' : '');
|
||||
item.textContent = opt.label;
|
||||
item.dataset.value = opt.value;
|
||||
item.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
el._ddValue = opt.value;
|
||||
textEl.textContent = opt.label;
|
||||
menuEl.querySelectorAll('.cfg-dropdown-item').forEach(i => i.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
el.classList.remove('open');
|
||||
if (el._ddOnChange) el._ddOnChange(opt.value);
|
||||
});
|
||||
menuEl.appendChild(item);
|
||||
});
|
||||
const sel = options.find(o => o.value === el._ddValue);
|
||||
textEl.textContent = sel ? sel.label : (options[0] ? options[0].label : '--');
|
||||
if (!sel && options[0]) el._ddValue = options[0].value;
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
if (!el._ddBound) {
|
||||
selEl.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
document.querySelectorAll('.cfg-dropdown.open').forEach(d => { if (d !== el) d.classList.remove('open'); });
|
||||
el.classList.toggle('open');
|
||||
});
|
||||
el._ddBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
document.querySelectorAll('.cfg-dropdown.open').forEach(d => d.classList.remove('open'));
|
||||
});
|
||||
|
||||
function getDropdownValue(el) { return el._ddValue || ''; }
|
||||
|
||||
// --- Config init ---
|
||||
function initConfigView(data) {
|
||||
configProviders = data.providers || {};
|
||||
configApiBases = data.api_bases || {};
|
||||
configApiKeys = data.api_keys || {};
|
||||
configCurrentModel = data.model || '';
|
||||
|
||||
const providerEl = document.getElementById('cfg-provider');
|
||||
const providerOpts = Object.entries(configProviders).map(([pid, p]) => ({ value: pid, label: p.label }));
|
||||
|
||||
const detected = detectProvider(configCurrentModel);
|
||||
cfgProviderValue = detected || (providerOpts[0] ? providerOpts[0].value : '');
|
||||
|
||||
initDropdown(providerEl, providerOpts, cfgProviderValue, onProviderChange);
|
||||
|
||||
onProviderChange(cfgProviderValue);
|
||||
syncModelSelection(configCurrentModel);
|
||||
|
||||
document.getElementById('cfg-max-tokens').value = data.agent_max_context_tokens || 50000;
|
||||
document.getElementById('cfg-max-turns').value = data.agent_max_context_turns || 30;
|
||||
document.getElementById('cfg-max-steps').value = data.agent_max_steps || 15;
|
||||
}
|
||||
|
||||
function detectProvider(model) {
|
||||
if (!model) return Object.keys(configProviders)[0] || '';
|
||||
for (const [pid, p] of Object.entries(configProviders)) {
|
||||
if (pid === 'linkai') continue;
|
||||
if (p.models && p.models.includes(model)) return pid;
|
||||
}
|
||||
return Object.keys(configProviders)[0] || '';
|
||||
}
|
||||
|
||||
function onProviderChange(pid) {
|
||||
cfgProviderValue = pid || getDropdownValue(document.getElementById('cfg-provider'));
|
||||
const p = configProviders[cfgProviderValue];
|
||||
if (!p) return;
|
||||
|
||||
const modelEl = document.getElementById('cfg-model-select');
|
||||
const modelOpts = (p.models || []).map(m => ({ value: m, label: m }));
|
||||
modelOpts.push({ value: '__custom__', label: t('config_custom_option') });
|
||||
|
||||
initDropdown(modelEl, modelOpts, modelOpts[0] ? modelOpts[0].value : '', onModelSelectChange);
|
||||
|
||||
// API Key
|
||||
const keyField = p.api_key_field;
|
||||
const keyWrap = document.getElementById('cfg-api-key-wrap');
|
||||
const keyInput = document.getElementById('cfg-api-key');
|
||||
if (keyField) {
|
||||
keyWrap.classList.remove('hidden');
|
||||
keyInput.classList.add('cfg-key-masked');
|
||||
const maskedVal = configApiKeys[keyField] || '';
|
||||
keyInput.value = maskedVal;
|
||||
keyInput.dataset.field = keyField;
|
||||
keyInput.dataset.masked = maskedVal ? '1' : '';
|
||||
keyInput.dataset.maskedVal = maskedVal;
|
||||
const toggleIcon = document.querySelector('#cfg-api-key-toggle i');
|
||||
if (toggleIcon) toggleIcon.className = 'fas fa-eye text-xs';
|
||||
|
||||
if (!keyInput._cfgBound) {
|
||||
keyInput.addEventListener('focus', function() {
|
||||
if (this.dataset.masked === '1') {
|
||||
this.value = '';
|
||||
this.dataset.masked = '';
|
||||
this.classList.remove('cfg-key-masked');
|
||||
}
|
||||
});
|
||||
keyInput.addEventListener('blur', function() {
|
||||
if (!this.value.trim() && this.dataset.maskedVal) {
|
||||
this.value = this.dataset.maskedVal;
|
||||
this.dataset.masked = '1';
|
||||
this.classList.add('cfg-key-masked');
|
||||
}
|
||||
});
|
||||
keyInput.addEventListener('input', function() {
|
||||
this.dataset.masked = '';
|
||||
});
|
||||
keyInput._cfgBound = true;
|
||||
}
|
||||
} else {
|
||||
keyWrap.classList.add('hidden');
|
||||
keyInput.value = '';
|
||||
keyInput.dataset.field = '';
|
||||
}
|
||||
|
||||
// API Base
|
||||
if (p.api_base_key) {
|
||||
document.getElementById('cfg-api-base-wrap').classList.remove('hidden');
|
||||
document.getElementById('cfg-api-base').value = configApiBases[p.api_base_key] || p.api_base_default || '';
|
||||
} else {
|
||||
document.getElementById('cfg-api-base-wrap').classList.add('hidden');
|
||||
document.getElementById('cfg-api-base').value = '';
|
||||
}
|
||||
|
||||
onModelSelectChange(modelOpts[0] ? modelOpts[0].value : '');
|
||||
}
|
||||
|
||||
function onModelSelectChange(val) {
|
||||
cfgModelValue = val || getDropdownValue(document.getElementById('cfg-model-select'));
|
||||
const customWrap = document.getElementById('cfg-model-custom-wrap');
|
||||
if (cfgModelValue === '__custom__') {
|
||||
customWrap.classList.remove('hidden');
|
||||
document.getElementById('cfg-model-custom').focus();
|
||||
} else {
|
||||
customWrap.classList.add('hidden');
|
||||
document.getElementById('cfg-model-custom').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function syncModelSelection(model) {
|
||||
const p = configProviders[cfgProviderValue];
|
||||
if (!p) return;
|
||||
|
||||
const modelEl = document.getElementById('cfg-model-select');
|
||||
if (p.models && p.models.includes(model)) {
|
||||
const modelOpts = (p.models || []).map(m => ({ value: m, label: m }));
|
||||
modelOpts.push({ value: '__custom__', label: t('config_custom_option') });
|
||||
initDropdown(modelEl, modelOpts, model, onModelSelectChange);
|
||||
cfgModelValue = model;
|
||||
document.getElementById('cfg-model-custom-wrap').classList.add('hidden');
|
||||
} else {
|
||||
cfgModelValue = '__custom__';
|
||||
const modelOpts = (p.models || []).map(m => ({ value: m, label: m }));
|
||||
modelOpts.push({ value: '__custom__', label: t('config_custom_option') });
|
||||
initDropdown(modelEl, modelOpts, '__custom__', onModelSelectChange);
|
||||
document.getElementById('cfg-model-custom-wrap').classList.remove('hidden');
|
||||
document.getElementById('cfg-model-custom').value = model;
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedModel() {
|
||||
if (cfgModelValue === '__custom__') {
|
||||
return document.getElementById('cfg-model-custom').value.trim();
|
||||
}
|
||||
return cfgModelValue;
|
||||
}
|
||||
|
||||
function toggleApiKeyVisibility() {
|
||||
const input = document.getElementById('cfg-api-key');
|
||||
const icon = document.querySelector('#cfg-api-key-toggle i');
|
||||
if (input.classList.contains('cfg-key-masked')) {
|
||||
input.classList.remove('cfg-key-masked');
|
||||
icon.className = 'fas fa-eye-slash text-xs';
|
||||
} else {
|
||||
input.classList.add('cfg-key-masked');
|
||||
icon.className = 'fas fa-eye text-xs';
|
||||
}
|
||||
}
|
||||
|
||||
function showStatus(elId, msgKey, isError) {
|
||||
const el = document.getElementById(elId);
|
||||
el.textContent = t(msgKey);
|
||||
el.classList.toggle('text-red-500', !!isError);
|
||||
el.classList.toggle('text-primary-500', !isError);
|
||||
el.classList.remove('opacity-0');
|
||||
setTimeout(() => el.classList.add('opacity-0'), 2500);
|
||||
}
|
||||
|
||||
function saveModelConfig() {
|
||||
const model = getSelectedModel();
|
||||
if (!model) return;
|
||||
|
||||
const updates = { model: model };
|
||||
const p = configProviders[cfgProviderValue];
|
||||
updates.use_linkai = (cfgProviderValue === 'linkai');
|
||||
if (p && p.api_base_key) {
|
||||
const base = document.getElementById('cfg-api-base').value.trim();
|
||||
if (base) updates[p.api_base_key] = base;
|
||||
}
|
||||
if (p && p.api_key_field) {
|
||||
const keyInput = document.getElementById('cfg-api-key');
|
||||
const rawVal = keyInput.value.trim();
|
||||
if (rawVal && keyInput.dataset.masked !== '1') {
|
||||
updates[p.api_key_field] = rawVal;
|
||||
}
|
||||
}
|
||||
|
||||
const btn = document.getElementById('cfg-model-save');
|
||||
btn.disabled = true;
|
||||
fetch('/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ updates })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
configCurrentModel = model;
|
||||
if (data.applied) {
|
||||
const keyInput = document.getElementById('cfg-api-key');
|
||||
Object.entries(data.applied).forEach(([k, v]) => {
|
||||
if (k === 'model') return;
|
||||
if (k.includes('api_key')) {
|
||||
const masked = v.length > 8
|
||||
? v.substring(0, 4) + '*'.repeat(v.length - 8) + v.substring(v.length - 4)
|
||||
: v;
|
||||
configApiKeys[k] = masked;
|
||||
if (keyInput.dataset.field === k) {
|
||||
keyInput.value = masked;
|
||||
keyInput.dataset.masked = '1';
|
||||
keyInput.dataset.maskedVal = masked;
|
||||
keyInput.classList.add('cfg-key-masked');
|
||||
const toggleIcon = document.querySelector('#cfg-api-key-toggle i');
|
||||
if (toggleIcon) toggleIcon.className = 'fas fa-eye text-xs';
|
||||
}
|
||||
} else {
|
||||
configApiBases[k] = v;
|
||||
}
|
||||
});
|
||||
}
|
||||
showStatus('cfg-model-status', 'config_saved', false);
|
||||
} else {
|
||||
showStatus('cfg-model-status', 'config_save_error', true);
|
||||
}
|
||||
})
|
||||
.catch(() => showStatus('cfg-model-status', 'config_save_error', true))
|
||||
.finally(() => { btn.disabled = false; });
|
||||
}
|
||||
|
||||
function saveAgentConfig() {
|
||||
const updates = {
|
||||
agent_max_context_tokens: parseInt(document.getElementById('cfg-max-tokens').value) || 50000,
|
||||
agent_max_context_turns: parseInt(document.getElementById('cfg-max-turns').value) || 30,
|
||||
agent_max_steps: parseInt(document.getElementById('cfg-max-steps').value) || 15,
|
||||
};
|
||||
|
||||
const btn = document.getElementById('cfg-agent-save');
|
||||
btn.disabled = true;
|
||||
fetch('/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ updates })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showStatus('cfg-agent-status', 'config_saved', false);
|
||||
} else {
|
||||
showStatus('cfg-agent-status', 'config_save_error', true);
|
||||
}
|
||||
})
|
||||
.catch(() => showStatus('cfg-agent-status', 'config_save_error', true))
|
||||
.finally(() => { btn.disabled = false; });
|
||||
}
|
||||
|
||||
function loadConfigView() {
|
||||
fetch('/config').then(r => r.json()).then(data => {
|
||||
if (data.status !== 'success') return;
|
||||
appConfig = data;
|
||||
initConfigView(data);
|
||||
document.getElementById('cfg-model').textContent = data.model || '--';
|
||||
document.getElementById('cfg-agent').textContent = data.use_agent ? 'Enabled' : 'Disabled';
|
||||
document.getElementById('cfg-max-tokens').textContent = data.agent_max_context_tokens || '--';
|
||||
document.getElementById('cfg-max-turns').textContent = data.agent_max_context_turns || '--';
|
||||
document.getElementById('cfg-max-steps').textContent = data.agent_max_steps || '--';
|
||||
document.getElementById('cfg-channel').textContent = data.channel_type || '--';
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Skills View
|
||||
// =====================================================================
|
||||
let toolsLoaded = false;
|
||||
|
||||
const TOOL_ICONS = {
|
||||
bash: 'fa-terminal',
|
||||
edit: 'fa-pen-to-square',
|
||||
read: 'fa-file-lines',
|
||||
write: 'fa-file-pen',
|
||||
ls: 'fa-folder-open',
|
||||
send: 'fa-paper-plane',
|
||||
web_search: 'fa-magnifying-glass',
|
||||
browser: 'fa-globe',
|
||||
env_config: 'fa-key',
|
||||
scheduler: 'fa-clock',
|
||||
memory_get: 'fa-brain',
|
||||
memory_search: 'fa-brain',
|
||||
};
|
||||
|
||||
function getToolIcon(name) {
|
||||
return TOOL_ICONS[name] || 'fa-wrench';
|
||||
}
|
||||
|
||||
let skillsLoaded = false;
|
||||
function loadSkillsView() {
|
||||
loadToolsSection();
|
||||
loadSkillsSection();
|
||||
}
|
||||
|
||||
function loadToolsSection() {
|
||||
if (toolsLoaded) return;
|
||||
const emptyEl = document.getElementById('tools-empty');
|
||||
const listEl = document.getElementById('tools-list');
|
||||
const badge = document.getElementById('tools-count-badge');
|
||||
|
||||
fetch('/api/tools').then(r => r.json()).then(data => {
|
||||
if (data.status !== 'success') return;
|
||||
const tools = data.tools || [];
|
||||
emptyEl.classList.add('hidden');
|
||||
if (tools.length === 0) {
|
||||
emptyEl.classList.remove('hidden');
|
||||
emptyEl.innerHTML = `<span class="text-sm text-slate-400 dark:text-slate-500">${currentLang === 'zh' ? '暂无内置工具' : 'No built-in tools'}</span>`;
|
||||
return;
|
||||
}
|
||||
badge.textContent = tools.length;
|
||||
badge.classList.remove('hidden');
|
||||
listEl.innerHTML = '';
|
||||
tools.forEach(tool => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-4 flex items-start gap-3';
|
||||
card.innerHTML = `
|
||||
<div class="w-9 h-9 rounded-lg bg-blue-50 dark:bg-blue-900/20 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas ${getToolIcon(tool.name)} text-blue-500 dark:text-blue-400 text-sm"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm text-slate-700 dark:text-slate-200 font-mono">${escapeHtml(tool.name)}</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 dark:text-slate-500 mt-1 line-clamp-2">${escapeHtml(tool.description || '--')}</p>
|
||||
</div>`;
|
||||
listEl.appendChild(card);
|
||||
});
|
||||
listEl.classList.remove('hidden');
|
||||
toolsLoaded = true;
|
||||
}).catch(() => {
|
||||
emptyEl.classList.remove('hidden');
|
||||
emptyEl.innerHTML = `<span class="text-sm text-slate-400 dark:text-slate-500">${currentLang === 'zh' ? '加载失败' : 'Failed to load'}</span>`;
|
||||
});
|
||||
}
|
||||
|
||||
function loadSkillsSection() {
|
||||
const emptyEl = document.getElementById('skills-empty');
|
||||
const listEl = document.getElementById('skills-list');
|
||||
const badge = document.getElementById('skills-count-badge');
|
||||
|
||||
if (skillsLoaded) return;
|
||||
fetch('/api/skills').then(r => r.json()).then(data => {
|
||||
if (data.status !== 'success') return;
|
||||
const emptyEl = document.getElementById('skills-empty');
|
||||
const listEl = document.getElementById('skills-list');
|
||||
const skills = data.skills || [];
|
||||
if (skills.length === 0) {
|
||||
const p = emptyEl.querySelector('p');
|
||||
if (p) p.textContent = currentLang === 'zh' ? '暂无技能' : 'No skills found';
|
||||
emptyEl.querySelector('p').textContent = currentLang === 'zh' ? '暂无技能' : 'No skills found';
|
||||
return;
|
||||
}
|
||||
badge.textContent = skills.length;
|
||||
badge.classList.remove('hidden');
|
||||
emptyEl.classList.add('hidden');
|
||||
listEl.innerHTML = '';
|
||||
|
||||
skills.forEach(sk => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-4 flex items-start gap-3 transition-opacity';
|
||||
card.dataset.skillName = sk.name;
|
||||
card.dataset.skillDesc = sk.description || '';
|
||||
card.dataset.enabled = sk.enabled ? '1' : '0';
|
||||
renderSkillCard(card, sk);
|
||||
listEl.appendChild(card);
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
const builtins = skills.filter(s => s.source === 'builtin');
|
||||
const customs = skills.filter(s => s.source !== 'builtin');
|
||||
|
||||
function renderSkillCard(card, sk) {
|
||||
const enabled = sk.enabled;
|
||||
const iconColor = enabled ? 'text-primary-400' : 'text-slate-300 dark:text-slate-600';
|
||||
const trackClass = enabled
|
||||
? 'bg-primary-400'
|
||||
: 'bg-slate-200 dark:bg-slate-700';
|
||||
const thumbTranslate = enabled ? 'translate-x-3' : 'translate-x-0.5';
|
||||
card.innerHTML = `
|
||||
<div class="w-9 h-9 rounded-lg bg-amber-50 dark:bg-amber-900/20 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-bolt ${iconColor} text-sm"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-medium text-sm text-slate-700 dark:text-slate-200 truncate flex-1">${escapeHtml(sk.name)}</span>
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked="${enabled}"
|
||||
onclick="toggleSkill('${escapeHtml(sk.name)}', ${enabled})"
|
||||
class="relative inline-flex h-4 w-7 flex-shrink-0 cursor-pointer rounded-full transition-colors duration-200 ease-in-out focus:outline-none ${trackClass}"
|
||||
title="${enabled ? (currentLang === 'zh' ? '点击禁用' : 'Click to disable') : (currentLang === 'zh' ? '点击启用' : 'Click to enable')}"
|
||||
>
|
||||
<span class="inline-block h-3 w-3 mt-0.5 rounded-full bg-white shadow transform transition-transform duration-200 ease-in-out ${thumbTranslate}"></span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 dark:text-slate-500 line-clamp-2">${escapeHtml(sk.description || '--')}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function toggleSkill(name, currentlyEnabled) {
|
||||
const action = currentlyEnabled ? 'close' : 'open';
|
||||
const card = document.querySelector(`[data-skill-name="${CSS.escape(name)}"]`);
|
||||
if (card) card.style.opacity = '0.5';
|
||||
|
||||
fetch('/api/skills', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action, name })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
if (card) {
|
||||
const desc = card.dataset.skillDesc || '';
|
||||
card.dataset.enabled = currentlyEnabled ? '0' : '1';
|
||||
card.style.opacity = '1';
|
||||
renderSkillCard(card, { name, description: desc, enabled: !currentlyEnabled });
|
||||
}
|
||||
} else {
|
||||
if (card) card.style.opacity = '1';
|
||||
alert(currentLang === 'zh' ? '操作失败,请稍后再试' : 'Operation failed, please try again');
|
||||
function renderGroup(title, items) {
|
||||
if (items.length === 0) return;
|
||||
const header = document.createElement('div');
|
||||
header.className = 'sm:col-span-2 text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500 mt-2';
|
||||
header.textContent = title;
|
||||
listEl.appendChild(header);
|
||||
items.forEach(sk => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-4 flex items-start gap-3';
|
||||
const iconColor = sk.enabled ? 'text-primary-400' : 'text-slate-300 dark:text-slate-600';
|
||||
const statusDot = sk.enabled
|
||||
? '<span class="w-2 h-2 rounded-full bg-primary-400 flex-shrink-0 mt-1"></span>'
|
||||
: '<span class="w-2 h-2 rounded-full bg-slate-300 dark:bg-slate-600 flex-shrink-0 mt-1"></span>';
|
||||
card.innerHTML = `
|
||||
<div class="w-9 h-9 rounded-lg bg-amber-50 dark:bg-amber-900/20 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-bolt ${iconColor} text-sm"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm text-slate-700 dark:text-slate-200 truncate">${escapeHtml(sk.name)}</span>
|
||||
${statusDot}
|
||||
</div>
|
||||
<p class="text-xs text-slate-400 dark:text-slate-500 mt-1 line-clamp-2">${escapeHtml(sk.description || '--')}</p>
|
||||
</div>`;
|
||||
listEl.appendChild(card);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (card) card.style.opacity = '1';
|
||||
alert(currentLang === 'zh' ? '操作失败,请稍后再试' : 'Operation failed, please try again');
|
||||
});
|
||||
renderGroup(currentLang === 'zh' ? '内置技能' : 'Built-in Skills', builtins);
|
||||
renderGroup(currentLang === 'zh' ? '自定义技能' : 'Custom Skills', customs);
|
||||
skillsLoaded = true;
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
@@ -1381,362 +958,36 @@ function closeMemoryViewer() {
|
||||
document.getElementById('memory-panel-list').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Custom Confirm Dialog
|
||||
// =====================================================================
|
||||
function showConfirmDialog({ title, message, okText, cancelText, onConfirm }) {
|
||||
const overlay = document.getElementById('confirm-dialog-overlay');
|
||||
document.getElementById('confirm-dialog-title').textContent = title || '';
|
||||
document.getElementById('confirm-dialog-message').textContent = message || '';
|
||||
document.getElementById('confirm-dialog-ok').textContent = okText || 'OK';
|
||||
document.getElementById('confirm-dialog-cancel').textContent = cancelText || t('channels_cancel');
|
||||
|
||||
function cleanup() {
|
||||
overlay.classList.add('hidden');
|
||||
okBtn.removeEventListener('click', onOk);
|
||||
cancelBtn.removeEventListener('click', onCancel);
|
||||
overlay.removeEventListener('click', onOverlayClick);
|
||||
}
|
||||
function onOk() { cleanup(); if (onConfirm) onConfirm(); }
|
||||
function onCancel() { cleanup(); }
|
||||
function onOverlayClick(e) { if (e.target === overlay) cleanup(); }
|
||||
|
||||
const okBtn = document.getElementById('confirm-dialog-ok');
|
||||
const cancelBtn = document.getElementById('confirm-dialog-cancel');
|
||||
okBtn.addEventListener('click', onOk);
|
||||
cancelBtn.addEventListener('click', onCancel);
|
||||
overlay.addEventListener('click', onOverlayClick);
|
||||
overlay.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Channels View
|
||||
// =====================================================================
|
||||
let channelsData = [];
|
||||
|
||||
function loadChannelsView() {
|
||||
const container = document.getElementById('channels-content');
|
||||
container.innerHTML = `<div class="flex items-center gap-2 py-8 justify-center text-slate-400 dark:text-slate-500 text-sm">
|
||||
<i class="fas fa-spinner fa-spin text-xs"></i><span>Loading...</span></div>`;
|
||||
|
||||
fetch('/api/channels').then(r => r.json()).then(data => {
|
||||
if (data.status !== 'success') return;
|
||||
channelsData = data.channels || [];
|
||||
renderActiveChannels();
|
||||
}).catch(() => {
|
||||
container.innerHTML = '<p class="text-sm text-red-400 py-8 text-center">Failed to load channels</p>';
|
||||
});
|
||||
}
|
||||
|
||||
function renderActiveChannels() {
|
||||
const container = document.getElementById('channels-content');
|
||||
container.innerHTML = '';
|
||||
closeAddChannelPanel();
|
||||
|
||||
const activeChannels = channelsData.filter(ch => ch.active);
|
||||
|
||||
if (activeChannels.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="flex flex-col items-center justify-center py-20">
|
||||
<div class="w-16 h-16 rounded-2xl bg-blue-50 dark:bg-blue-900/20 flex items-center justify-center mb-4">
|
||||
<i class="fas fa-tower-broadcast text-blue-400 text-xl"></i>
|
||||
</div>
|
||||
<p class="text-slate-500 dark:text-slate-400 font-medium">${t('channels_empty')}</p>
|
||||
<p class="text-sm text-slate-400 dark:text-slate-500 mt-1">${t('channels_empty_desc')}</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
activeChannels.forEach(ch => {
|
||||
const label = (typeof ch.label === 'object') ? (ch.label[currentLang] || ch.label.en) : ch.label;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6';
|
||||
card.id = `channel-card-${ch.name}`;
|
||||
|
||||
const fieldsHtml = buildChannelFieldsHtml(ch.name, ch.fields || []);
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="flex items-center gap-4 mb-5">
|
||||
<div class="w-10 h-10 rounded-xl bg-${ch.color}-50 dark:bg-${ch.color}-900/20 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas ${ch.icon} text-${ch.color}-500 text-base"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold text-slate-800 dark:text-slate-100">${escapeHtml(label)}</span>
|
||||
<span class="w-2 h-2 rounded-full bg-primary-400"></span>
|
||||
<span class="text-xs text-primary-500">${t('channels_connected')}</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5 font-mono">${escapeHtml(ch.name)}</p>
|
||||
</div>
|
||||
<button onclick="disconnectChannel('${ch.name}')"
|
||||
class="px-3 py-1.5 rounded-lg text-xs font-medium
|
||||
bg-red-50 dark:bg-red-900/20 text-red-500 dark:text-red-400
|
||||
hover:bg-red-100 dark:hover:bg-red-900/40
|
||||
cursor-pointer transition-colors flex-shrink-0">
|
||||
${t('channels_disconnect')}
|
||||
</button>
|
||||
const channelType = appConfig.channel_type || 'web';
|
||||
const channelMap = {
|
||||
web: { name: 'Web', icon: 'fa-globe', color: 'primary' },
|
||||
terminal: { name: 'Terminal', icon: 'fa-terminal', color: 'slate' },
|
||||
feishu: { name: 'Feishu', icon: 'fa-paper-plane', color: 'blue' },
|
||||
dingtalk: { name: 'DingTalk', icon: 'fa-comments', color: 'blue' },
|
||||
wechatcom_app: { name: 'WeCom', icon: 'fa-building', color: 'emerald' },
|
||||
wechatmp: { name: 'WeChat MP', icon: 'fa-comment-dots', color: 'emerald' },
|
||||
wechatmp_service: { name: 'WeChat Service', icon: 'fa-comment-dots', color: 'emerald' },
|
||||
};
|
||||
const info = channelMap[channelType] || { name: channelType, icon: 'fa-tower-broadcast', color: 'sky' };
|
||||
container.innerHTML = `
|
||||
<div class="bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6 flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-${info.color}-50 dark:bg-${info.color}-900/20 flex items-center justify-center">
|
||||
<i class="fas ${info.icon} text-${info.color}-500 text-lg"></i>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
${fieldsHtml}
|
||||
<div class="flex items-center justify-end gap-3 pt-1">
|
||||
<span id="ch-status-${ch.name}" class="text-xs text-primary-500 opacity-0 transition-opacity duration-300"></span>
|
||||
<button onclick="saveChannelConfig('${ch.name}')"
|
||||
class="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white text-sm font-medium
|
||||
cursor-pointer transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
id="ch-save-${ch.name}">${t('channels_save')}</button>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold text-slate-800 dark:text-slate-100">${info.name}</span>
|
||||
<span class="w-2 h-2 rounded-full bg-primary-400"></span>
|
||||
<span class="text-xs text-primary-500">Active</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
container.appendChild(card);
|
||||
bindSecretFieldEvents(card);
|
||||
});
|
||||
}
|
||||
|
||||
function buildChannelFieldsHtml(chName, fields) {
|
||||
let html = '';
|
||||
fields.forEach(f => {
|
||||
const inputId = `ch-${chName}-${f.key}`;
|
||||
let inputHtml = '';
|
||||
if (f.type === 'bool') {
|
||||
const checked = f.value ? 'checked' : '';
|
||||
inputHtml = `<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input id="${inputId}" type="checkbox" ${checked} class="sr-only peer" data-field="${f.key}" data-ch="${chName}">
|
||||
<div class="w-9 h-5 bg-slate-200 dark:bg-slate-700 peer-checked:bg-primary-400 rounded-full
|
||||
after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white
|
||||
after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:after:translate-x-full"></div>
|
||||
</label>`;
|
||||
} else if (f.type === 'secret') {
|
||||
inputHtml = `<input id="${inputId}" type="text" value="${escapeHtml(String(f.value || ''))}"
|
||||
data-field="${f.key}" data-ch="${chName}" data-masked="${f.value ? '1' : ''}"
|
||||
class="w-full px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600
|
||||
bg-slate-50 dark:bg-white/5 text-sm text-slate-800 dark:text-slate-100
|
||||
focus:outline-none focus:border-primary-500 font-mono transition-colors
|
||||
${f.value ? 'cfg-key-masked' : ''}"
|
||||
placeholder="${escapeHtml(f.label)}">`;
|
||||
} else {
|
||||
const inputType = f.type === 'number' ? 'number' : 'text';
|
||||
inputHtml = `<input id="${inputId}" type="${inputType}" value="${escapeHtml(String(f.value ?? f.default ?? ''))}"
|
||||
data-field="${f.key}" data-ch="${chName}"
|
||||
class="w-full px-3 py-2 rounded-lg border border-slate-200 dark:border-slate-600
|
||||
bg-slate-50 dark:bg-white/5 text-sm text-slate-800 dark:text-slate-100
|
||||
focus:outline-none focus:border-primary-500 font-mono transition-colors"
|
||||
placeholder="${escapeHtml(f.label)}">`;
|
||||
}
|
||||
html += `<div>
|
||||
<label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5">${escapeHtml(f.label)}</label>
|
||||
${inputHtml}
|
||||
</div>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
function bindSecretFieldEvents(container) {
|
||||
container.querySelectorAll('input[data-masked="1"]').forEach(inp => {
|
||||
inp.addEventListener('focus', function() {
|
||||
if (this.dataset.masked === '1') {
|
||||
this.value = '';
|
||||
this.dataset.masked = '';
|
||||
this.classList.remove('cfg-key-masked');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showChannelStatus(chName, msgKey, isError) {
|
||||
const el = document.getElementById(`ch-status-${chName}`);
|
||||
if (!el) return;
|
||||
el.textContent = t(msgKey);
|
||||
el.classList.toggle('text-red-500', !!isError);
|
||||
el.classList.toggle('text-primary-500', !isError);
|
||||
el.classList.remove('opacity-0');
|
||||
setTimeout(() => el.classList.add('opacity-0'), 2500);
|
||||
}
|
||||
|
||||
function saveChannelConfig(chName) {
|
||||
const card = document.getElementById(`channel-card-${chName}`);
|
||||
if (!card) return;
|
||||
|
||||
const updates = {};
|
||||
card.querySelectorAll('input[data-ch="' + chName + '"]').forEach(inp => {
|
||||
const key = inp.dataset.field;
|
||||
if (inp.type === 'checkbox') {
|
||||
updates[key] = inp.checked;
|
||||
} else {
|
||||
if (inp.dataset.masked === '1') return;
|
||||
updates[key] = inp.value;
|
||||
}
|
||||
});
|
||||
|
||||
const btn = document.getElementById(`ch-save-${chName}`);
|
||||
if (btn) btn.disabled = true;
|
||||
|
||||
fetch('/api/channels', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'save', channel: chName, config: updates })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showChannelStatus(chName, data.restarted ? 'channels_restarted' : 'channels_saved', false);
|
||||
} else {
|
||||
showChannelStatus(chName, 'channels_save_error', true);
|
||||
}
|
||||
})
|
||||
.catch(() => showChannelStatus(chName, 'channels_save_error', true))
|
||||
.finally(() => { if (btn) btn.disabled = false; });
|
||||
}
|
||||
|
||||
function disconnectChannel(chName) {
|
||||
const ch = channelsData.find(c => c.name === chName);
|
||||
const label = ch ? ((typeof ch.label === 'object') ? (ch.label[currentLang] || ch.label.en) : ch.label) : chName;
|
||||
|
||||
showConfirmDialog({
|
||||
title: t('channels_disconnect'),
|
||||
message: t('channels_disconnect_confirm'),
|
||||
okText: t('channels_disconnect'),
|
||||
cancelText: t('channels_cancel'),
|
||||
onConfirm: () => {
|
||||
fetch('/api/channels', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'disconnect', channel: chName })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
if (ch) ch.active = false;
|
||||
renderActiveChannels();
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Add channel panel ---
|
||||
function openAddChannelPanel() {
|
||||
const panel = document.getElementById('channels-add-panel');
|
||||
const activeNames = new Set(channelsData.filter(c => c.active).map(c => c.name));
|
||||
const available = channelsData.filter(c => !activeNames.has(c.name));
|
||||
|
||||
if (available.length === 0) {
|
||||
panel.innerHTML = `<div class="bg-white dark:bg-[#1A1A1A] rounded-xl border border-slate-200 dark:border-white/10 p-6 text-center">
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">${currentLang === 'zh' ? '所有通道均已接入' : 'All channels are already connected'}</p>
|
||||
<button onclick="closeAddChannelPanel()" class="mt-3 text-xs text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 cursor-pointer">${t('channels_cancel')}</button>
|
||||
</div>`;
|
||||
panel.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const ddOptions = [
|
||||
{ value: '', label: t('channels_select_placeholder') },
|
||||
...available.map(ch => {
|
||||
const label = (typeof ch.label === 'object') ? (ch.label[currentLang] || ch.label.en) : ch.label;
|
||||
return { value: ch.name, label: `${label} (${ch.name})` };
|
||||
})
|
||||
];
|
||||
|
||||
panel.innerHTML = `
|
||||
<div class="bg-white dark:bg-[#1A1A1A] rounded-xl border border-primary-200 dark:border-primary-800 p-6">
|
||||
<div class="flex items-center gap-3 mb-5">
|
||||
<div class="w-9 h-9 rounded-lg bg-primary-50 dark:bg-primary-900/30 flex items-center justify-center">
|
||||
<i class="fas fa-plus text-primary-500 text-sm"></i>
|
||||
</div>
|
||||
<h3 class="font-semibold text-slate-800 dark:text-slate-100">${t('channels_add')}</h3>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div id="add-channel-select" class="cfg-dropdown" tabindex="0">
|
||||
<div class="cfg-dropdown-selected">
|
||||
<span class="cfg-dropdown-text">--</span>
|
||||
<i class="fas fa-chevron-down cfg-dropdown-arrow"></i>
|
||||
</div>
|
||||
<div class="cfg-dropdown-menu"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="add-channel-fields" class="space-y-4"></div>
|
||||
<div id="add-channel-actions" class="hidden flex items-center justify-end gap-3 pt-4">
|
||||
<button onclick="closeAddChannelPanel()"
|
||||
class="px-4 py-2 rounded-lg border border-slate-200 dark:border-white/10
|
||||
text-slate-600 dark:text-slate-300 text-sm font-medium
|
||||
hover:bg-slate-50 dark:hover:bg-white/5
|
||||
cursor-pointer transition-colors duration-150">${t('channels_cancel')}</button>
|
||||
<button id="add-channel-submit" onclick="submitAddChannel()"
|
||||
class="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white text-sm font-medium
|
||||
cursor-pointer transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed">${t('channels_connect_btn')}</button>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-0.5 font-mono">${escapeHtml(channelType)}</p>
|
||||
</div>
|
||||
</div>`;
|
||||
panel.classList.remove('hidden');
|
||||
panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
const ddEl = document.getElementById('add-channel-select');
|
||||
initDropdown(ddEl, ddOptions, '', onAddChannelSelect);
|
||||
}
|
||||
|
||||
function closeAddChannelPanel() {
|
||||
const panel = document.getElementById('channels-add-panel');
|
||||
if (panel) {
|
||||
panel.classList.add('hidden');
|
||||
panel.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
function onAddChannelSelect(chName) {
|
||||
const fieldsContainer = document.getElementById('add-channel-fields');
|
||||
const actions = document.getElementById('add-channel-actions');
|
||||
|
||||
if (!chName) {
|
||||
fieldsContainer.innerHTML = '';
|
||||
actions.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const ch = channelsData.find(c => c.name === chName);
|
||||
if (!ch) return;
|
||||
|
||||
fieldsContainer.innerHTML = buildChannelFieldsHtml(chName, ch.fields || []);
|
||||
bindSecretFieldEvents(fieldsContainer);
|
||||
actions.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function submitAddChannel() {
|
||||
const ddEl = document.getElementById('add-channel-select');
|
||||
const chName = getDropdownValue(ddEl);
|
||||
if (!chName) return;
|
||||
|
||||
const fieldsContainer = document.getElementById('add-channel-fields');
|
||||
const updates = {};
|
||||
fieldsContainer.querySelectorAll('input[data-ch="' + chName + '"]').forEach(inp => {
|
||||
const key = inp.dataset.field;
|
||||
if (inp.type === 'checkbox') {
|
||||
updates[key] = inp.checked;
|
||||
} else {
|
||||
if (inp.dataset.masked === '1') return;
|
||||
updates[key] = inp.value;
|
||||
}
|
||||
});
|
||||
|
||||
const btn = document.getElementById('add-channel-submit');
|
||||
if (btn) { btn.disabled = true; btn.textContent = t('channels_connecting'); }
|
||||
|
||||
fetch('/api/channels', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'connect', channel: chName, config: updates })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
const ch = channelsData.find(c => c.name === chName);
|
||||
if (ch) ch.active = true;
|
||||
renderActiveChannels();
|
||||
} else {
|
||||
if (btn) { btn.disabled = false; btn.textContent = t('channels_connect_btn'); }
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (btn) { btn.disabled = false; btn.textContent = t('channels_connect_btn'); }
|
||||
});
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
@@ -1838,8 +1089,7 @@ navigateTo = function(viewId) {
|
||||
_origNavigateTo(viewId);
|
||||
|
||||
// Lazy-load view data
|
||||
if (viewId === 'config') loadConfigView();
|
||||
else if (viewId === 'skills') loadSkillsView();
|
||||
if (viewId === 'skills') loadSkillsView();
|
||||
else if (viewId === 'memory') {
|
||||
// Always start from the list panel when navigating to memory
|
||||
document.getElementById('memory-panel-viewer').classList.add('hidden');
|
||||
|
||||
@@ -14,8 +14,6 @@ from bridge.context import *
|
||||
from bridge.reply import Reply, ReplyType
|
||||
from channel.chat_channel import ChatChannel, check_prefix
|
||||
from channel.chat_message import ChatMessage
|
||||
from collections import OrderedDict
|
||||
from common import const
|
||||
from common.log import logger
|
||||
from common.singleton import singleton
|
||||
from config import conf
|
||||
@@ -304,8 +302,6 @@ class WebChannel(ChatChannel):
|
||||
'/stream', 'StreamHandler',
|
||||
'/chat', 'ChatHandler',
|
||||
'/config', 'ConfigHandler',
|
||||
'/api/channels', 'ChannelsHandler',
|
||||
'/api/tools', 'ToolsHandler',
|
||||
'/api/skills', 'SkillsHandler',
|
||||
'/api/memory', 'MemoryHandler',
|
||||
'/api/memory/content', 'MemoryContentHandler',
|
||||
@@ -327,8 +323,6 @@ class WebChannel(ChatChannel):
|
||||
func = web.httpserver.StaticMiddleware(app.wsgifunc())
|
||||
func = web.httpserver.LogMiddleware(func)
|
||||
server = web.httpserver.WSGIServer(("0.0.0.0", port), func)
|
||||
# Allow concurrent requests by not blocking on in-flight handler threads
|
||||
server.daemon_threads = True
|
||||
self._http_server = server
|
||||
try:
|
||||
server.start()
|
||||
@@ -385,137 +379,16 @@ class ChatHandler:
|
||||
|
||||
|
||||
class ConfigHandler:
|
||||
|
||||
_RECOMMENDED_MODELS = [
|
||||
const.MINIMAX_M2_5, const.MINIMAX_M2_1, const.MINIMAX_M2_1_LIGHTNING,
|
||||
const.GLM_5, const.GLM_4_7,
|
||||
const.QWEN3_MAX, const.QWEN35_PLUS,
|
||||
const.KIMI_K2_5, const.KIMI_K2,
|
||||
const.DOUBAO_SEED_2_PRO, const.DOUBAO_SEED_2_CODE,
|
||||
const.CLAUDE_4_6_SONNET, const.CLAUDE_4_6_OPUS, const.CLAUDE_4_5_SONNET,
|
||||
const.GEMINI_31_PRO_PRE, const.GEMINI_3_FLASH_PRE,
|
||||
const.GPT_5, const.GPT_41, const.GPT_4o,
|
||||
const.DEEPSEEK_CHAT, const.DEEPSEEK_REASONER,
|
||||
]
|
||||
|
||||
PROVIDER_MODELS = OrderedDict([
|
||||
("minimax", {
|
||||
"label": "MiniMax",
|
||||
"api_key_field": "minimax_api_key",
|
||||
"api_base_key": None,
|
||||
"api_base_default": None,
|
||||
"models": [const.MINIMAX_M2_5, const.MINIMAX_M2_1, const.MINIMAX_M2_1_LIGHTNING],
|
||||
}),
|
||||
("glm-4", {
|
||||
"label": "智谱AI",
|
||||
"api_key_field": "zhipu_ai_api_key",
|
||||
"api_base_key": "zhipu_ai_api_base",
|
||||
"api_base_default": "https://open.bigmodel.cn/api/paas/v4",
|
||||
"models": [const.GLM_5, const.GLM_4_7],
|
||||
}),
|
||||
("dashscope", {
|
||||
"label": "通义千问",
|
||||
"api_key_field": "dashscope_api_key",
|
||||
"api_base_key": None,
|
||||
"api_base_default": None,
|
||||
"models": [const.QWEN3_MAX, const.QWEN35_PLUS],
|
||||
}),
|
||||
("moonshot", {
|
||||
"label": "Kimi",
|
||||
"api_key_field": "moonshot_api_key",
|
||||
"api_base_key": "moonshot_base_url",
|
||||
"api_base_default": "https://api.moonshot.cn/v1",
|
||||
"models": [const.KIMI_K2_5, const.KIMI_K2],
|
||||
}),
|
||||
("doubao", {
|
||||
"label": "豆包",
|
||||
"api_key_field": "ark_api_key",
|
||||
"api_base_key": "ark_base_url",
|
||||
"api_base_default": "https://ark.cn-beijing.volces.com/api/v3",
|
||||
"models": [const.DOUBAO_SEED_2_PRO, const.DOUBAO_SEED_2_CODE],
|
||||
}),
|
||||
("claudeAPI", {
|
||||
"label": "Claude",
|
||||
"api_key_field": "claude_api_key",
|
||||
"api_base_key": "claude_api_base",
|
||||
"api_base_default": "https://api.anthropic.com/v1",
|
||||
"models": [const.CLAUDE_4_6_SONNET, const.CLAUDE_4_6_OPUS, const.CLAUDE_4_5_SONNET],
|
||||
}),
|
||||
("gemini", {
|
||||
"label": "Gemini",
|
||||
"api_key_field": "gemini_api_key",
|
||||
"api_base_key": "gemini_api_base",
|
||||
"api_base_default": "https://generativelanguage.googleapis.com",
|
||||
"models": [const.GEMINI_31_PRO_PRE, const.GEMINI_3_FLASH_PRE],
|
||||
}),
|
||||
("openAI", {
|
||||
"label": "OpenAI",
|
||||
"api_key_field": "open_ai_api_key",
|
||||
"api_base_key": "open_ai_api_base",
|
||||
"api_base_default": "https://api.openai.com/v1",
|
||||
"models": [const.GPT_5, const.GPT_41, const.GPT_4o],
|
||||
}),
|
||||
("deepseek", {
|
||||
"label": "DeepSeek",
|
||||
"api_key_field": "open_ai_api_key",
|
||||
"api_base_key": None,
|
||||
"api_base_default": None,
|
||||
"models": [const.DEEPSEEK_CHAT, const.DEEPSEEK_REASONER],
|
||||
}),
|
||||
("linkai", {
|
||||
"label": "LinkAI",
|
||||
"api_key_field": "linkai_api_key",
|
||||
"api_base_key": None,
|
||||
"api_base_default": None,
|
||||
"models": _RECOMMENDED_MODELS,
|
||||
}),
|
||||
])
|
||||
|
||||
EDITABLE_KEYS = {
|
||||
"model", "use_linkai",
|
||||
"open_ai_api_base", "claude_api_base", "gemini_api_base",
|
||||
"zhipu_ai_api_base", "moonshot_base_url", "ark_base_url",
|
||||
"open_ai_api_key", "claude_api_key", "gemini_api_key",
|
||||
"zhipu_ai_api_key", "dashscope_api_key", "moonshot_api_key",
|
||||
"ark_api_key", "minimax_api_key", "linkai_api_key",
|
||||
"agent_max_context_tokens", "agent_max_context_turns", "agent_max_steps",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _mask_key(value: str) -> str:
|
||||
"""Mask the middle part of an API key for display."""
|
||||
if not value or len(value) <= 8:
|
||||
return value
|
||||
return value[:4] + "*" * (len(value) - 8) + value[-4:]
|
||||
|
||||
def GET(self):
|
||||
"""Return configuration info and provider/model metadata."""
|
||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||
"""Return configuration info for the web console."""
|
||||
try:
|
||||
local_config = conf()
|
||||
use_agent = local_config.get("agent", False)
|
||||
title = "CowAgent" if use_agent else "AI Assistant"
|
||||
|
||||
api_bases = {}
|
||||
api_keys_masked = {}
|
||||
for pid, pinfo in self.PROVIDER_MODELS.items():
|
||||
base_key = pinfo.get("api_base_key")
|
||||
if base_key:
|
||||
api_bases[base_key] = local_config.get(base_key, pinfo["api_base_default"])
|
||||
key_field = pinfo.get("api_key_field")
|
||||
if key_field and key_field not in api_keys_masked:
|
||||
raw = local_config.get(key_field, "")
|
||||
api_keys_masked[key_field] = self._mask_key(raw) if raw else ""
|
||||
|
||||
providers = {}
|
||||
for pid, p in self.PROVIDER_MODELS.items():
|
||||
providers[pid] = {
|
||||
"label": p["label"],
|
||||
"models": p["models"],
|
||||
"api_base_key": p["api_base_key"],
|
||||
"api_base_default": p["api_base_default"],
|
||||
"api_key_field": p.get("api_key_field"),
|
||||
}
|
||||
if use_agent:
|
||||
title = "CowAgent"
|
||||
else:
|
||||
title = "AI Assistant"
|
||||
|
||||
return json.dumps({
|
||||
"status": "success",
|
||||
@@ -523,375 +396,14 @@ class ConfigHandler:
|
||||
"title": title,
|
||||
"model": local_config.get("model", ""),
|
||||
"channel_type": local_config.get("channel_type", ""),
|
||||
"agent_max_context_tokens": local_config.get("agent_max_context_tokens", 50000),
|
||||
"agent_max_context_turns": local_config.get("agent_max_context_turns", 30),
|
||||
"agent_max_steps": local_config.get("agent_max_steps", 15),
|
||||
"api_bases": api_bases,
|
||||
"api_keys": api_keys_masked,
|
||||
"providers": providers,
|
||||
}, ensure_ascii=False)
|
||||
"agent_max_context_tokens": local_config.get("agent_max_context_tokens", ""),
|
||||
"agent_max_context_turns": local_config.get("agent_max_context_turns", ""),
|
||||
"agent_max_steps": local_config.get("agent_max_steps", ""),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting config: {e}")
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
def POST(self):
|
||||
"""Update configuration values in memory and persist to config.json."""
|
||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||
try:
|
||||
data = json.loads(web.data())
|
||||
updates = data.get("updates", {})
|
||||
if not updates:
|
||||
return json.dumps({"status": "error", "message": "no updates provided"})
|
||||
|
||||
local_config = conf()
|
||||
applied = {}
|
||||
for key, value in updates.items():
|
||||
if key not in self.EDITABLE_KEYS:
|
||||
continue
|
||||
if key in ("agent_max_context_tokens", "agent_max_context_turns", "agent_max_steps"):
|
||||
value = int(value)
|
||||
if key == "use_linkai":
|
||||
value = bool(value)
|
||||
local_config[key] = value
|
||||
applied[key] = value
|
||||
|
||||
if not applied:
|
||||
return json.dumps({"status": "error", "message": "no valid keys to update"})
|
||||
|
||||
config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__)))), "config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
file_cfg = json.load(f)
|
||||
else:
|
||||
file_cfg = {}
|
||||
file_cfg.update(applied)
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(file_cfg, f, indent=4, ensure_ascii=False)
|
||||
|
||||
logger.info(f"[WebChannel] Config updated: {list(applied.keys())}")
|
||||
return json.dumps({"status": "success", "applied": applied}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating config: {e}")
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
|
||||
class ChannelsHandler:
|
||||
"""API for managing external channel configurations (feishu, dingtalk, etc)."""
|
||||
|
||||
CHANNEL_DEFS = OrderedDict([
|
||||
("feishu", {
|
||||
"label": {"zh": "飞书", "en": "Feishu"},
|
||||
"icon": "fa-paper-plane",
|
||||
"color": "blue",
|
||||
"fields": [
|
||||
{"key": "feishu_app_id", "label": "App ID", "type": "text"},
|
||||
{"key": "feishu_app_secret", "label": "App Secret", "type": "secret"},
|
||||
{"key": "feishu_token", "label": "Verification Token", "type": "secret"},
|
||||
{"key": "feishu_bot_name", "label": "Bot Name", "type": "text"},
|
||||
],
|
||||
}),
|
||||
("dingtalk", {
|
||||
"label": {"zh": "钉钉", "en": "DingTalk"},
|
||||
"icon": "fa-comments",
|
||||
"color": "blue",
|
||||
"fields": [
|
||||
{"key": "dingtalk_client_id", "label": "Client ID", "type": "text"},
|
||||
{"key": "dingtalk_client_secret", "label": "Client Secret", "type": "secret"},
|
||||
],
|
||||
}),
|
||||
("wechatcom_app", {
|
||||
"label": {"zh": "企微自建应用", "en": "WeCom App"},
|
||||
"icon": "fa-building",
|
||||
"color": "emerald",
|
||||
"fields": [
|
||||
{"key": "wechatcom_corp_id", "label": "Corp ID", "type": "text"},
|
||||
{"key": "wechatcomapp_agent_id", "label": "Agent ID", "type": "text"},
|
||||
{"key": "wechatcomapp_secret", "label": "Secret", "type": "secret"},
|
||||
{"key": "wechatcomapp_token", "label": "Token", "type": "secret"},
|
||||
{"key": "wechatcomapp_aes_key", "label": "AES Key", "type": "secret"},
|
||||
{"key": "wechatcomapp_port", "label": "Port", "type": "number", "default": 9898},
|
||||
],
|
||||
}),
|
||||
("wechatmp", {
|
||||
"label": {"zh": "公众号", "en": "WeChat MP"},
|
||||
"icon": "fa-comment-dots",
|
||||
"color": "emerald",
|
||||
"fields": [
|
||||
{"key": "wechatmp_app_id", "label": "App ID", "type": "text"},
|
||||
{"key": "wechatmp_app_secret", "label": "App Secret", "type": "secret"},
|
||||
{"key": "wechatmp_token", "label": "Token", "type": "secret"},
|
||||
{"key": "wechatmp_aes_key", "label": "AES Key", "type": "secret"},
|
||||
{"key": "wechatmp_port", "label": "Port", "type": "number", "default": 8080},
|
||||
],
|
||||
}),
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def _mask_secret(value: str) -> str:
|
||||
if not value or len(value) <= 8:
|
||||
return value
|
||||
return value[:4] + "*" * (len(value) - 8) + value[-4:]
|
||||
|
||||
@staticmethod
|
||||
def _parse_channel_list(raw) -> list:
|
||||
if isinstance(raw, list):
|
||||
return [ch.strip() for ch in raw if ch.strip()]
|
||||
if isinstance(raw, str):
|
||||
return [ch.strip() for ch in raw.split(",") if ch.strip()]
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def _active_channel_set(cls) -> set:
|
||||
return set(cls._parse_channel_list(conf().get("channel_type", "")))
|
||||
|
||||
def GET(self):
|
||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||
try:
|
||||
local_config = conf()
|
||||
active_channels = self._active_channel_set()
|
||||
channels = []
|
||||
for ch_name, ch_def in self.CHANNEL_DEFS.items():
|
||||
fields_out = []
|
||||
for f in ch_def["fields"]:
|
||||
raw_val = local_config.get(f["key"], f.get("default", ""))
|
||||
if f["type"] == "secret" and raw_val:
|
||||
display_val = self._mask_secret(str(raw_val))
|
||||
else:
|
||||
display_val = raw_val
|
||||
fields_out.append({
|
||||
"key": f["key"],
|
||||
"label": f["label"],
|
||||
"type": f["type"],
|
||||
"value": display_val,
|
||||
"default": f.get("default", ""),
|
||||
})
|
||||
channels.append({
|
||||
"name": ch_name,
|
||||
"label": ch_def["label"],
|
||||
"icon": ch_def["icon"],
|
||||
"color": ch_def["color"],
|
||||
"active": ch_name in active_channels,
|
||||
"fields": fields_out,
|
||||
})
|
||||
return json.dumps({"status": "success", "channels": channels}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"[WebChannel] Channels API error: {e}")
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
def POST(self):
|
||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||
try:
|
||||
body = json.loads(web.data())
|
||||
action = body.get("action")
|
||||
channel_name = body.get("channel")
|
||||
|
||||
if not action or not channel_name:
|
||||
return json.dumps({"status": "error", "message": "action and channel required"})
|
||||
|
||||
if channel_name not in self.CHANNEL_DEFS:
|
||||
return json.dumps({"status": "error", "message": f"unknown channel: {channel_name}"})
|
||||
|
||||
if action == "save":
|
||||
return self._handle_save(channel_name, body.get("config", {}))
|
||||
elif action == "connect":
|
||||
return self._handle_connect(channel_name, body.get("config", {}))
|
||||
elif action == "disconnect":
|
||||
return self._handle_disconnect(channel_name)
|
||||
else:
|
||||
return json.dumps({"status": "error", "message": f"unknown action: {action}"})
|
||||
except Exception as e:
|
||||
logger.error(f"[WebChannel] Channels POST error: {e}")
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
def _handle_save(self, channel_name: str, updates: dict):
|
||||
ch_def = self.CHANNEL_DEFS[channel_name]
|
||||
valid_keys = {f["key"] for f in ch_def["fields"]}
|
||||
secret_keys = {f["key"] for f in ch_def["fields"] if f["type"] == "secret"}
|
||||
|
||||
local_config = conf()
|
||||
applied = {}
|
||||
for key, value in updates.items():
|
||||
if key not in valid_keys:
|
||||
continue
|
||||
if key in secret_keys:
|
||||
if not value or (len(value) > 8 and "*" * 4 in value):
|
||||
continue
|
||||
field_def = next((f for f in ch_def["fields"] if f["key"] == key), None)
|
||||
if field_def:
|
||||
if field_def["type"] == "number":
|
||||
value = int(value)
|
||||
elif field_def["type"] == "bool":
|
||||
value = bool(value)
|
||||
local_config[key] = value
|
||||
applied[key] = value
|
||||
|
||||
if not applied:
|
||||
return json.dumps({"status": "error", "message": "no valid fields to update"})
|
||||
|
||||
config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__)))), "config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
file_cfg = json.load(f)
|
||||
else:
|
||||
file_cfg = {}
|
||||
file_cfg.update(applied)
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(file_cfg, f, indent=4, ensure_ascii=False)
|
||||
|
||||
logger.info(f"[WebChannel] Channel '{channel_name}' config updated: {list(applied.keys())}")
|
||||
|
||||
should_restart = False
|
||||
active_channels = self._active_channel_set()
|
||||
if channel_name in active_channels:
|
||||
should_restart = True
|
||||
try:
|
||||
import sys
|
||||
app_module = sys.modules.get('__main__') or sys.modules.get('app')
|
||||
mgr = getattr(app_module, '_channel_mgr', None) if app_module else None
|
||||
if mgr:
|
||||
threading.Thread(
|
||||
target=mgr.restart,
|
||||
args=(channel_name,),
|
||||
daemon=True,
|
||||
).start()
|
||||
logger.info(f"[WebChannel] Channel '{channel_name}' restart triggered")
|
||||
except Exception as e:
|
||||
logger.warning(f"[WebChannel] Failed to restart channel '{channel_name}': {e}")
|
||||
|
||||
return json.dumps({
|
||||
"status": "success",
|
||||
"applied": list(applied.keys()),
|
||||
"restarted": should_restart,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
def _handle_connect(self, channel_name: str, updates: dict):
|
||||
"""Save config fields, add channel to channel_type, and start it."""
|
||||
ch_def = self.CHANNEL_DEFS[channel_name]
|
||||
valid_keys = {f["key"] for f in ch_def["fields"]}
|
||||
secret_keys = {f["key"] for f in ch_def["fields"] if f["type"] == "secret"}
|
||||
|
||||
# Feishu connected via web console must use websocket (long connection) mode
|
||||
if channel_name == "feishu":
|
||||
updates.setdefault("feishu_event_mode", "websocket")
|
||||
valid_keys.add("feishu_event_mode")
|
||||
|
||||
local_config = conf()
|
||||
applied = {}
|
||||
for key, value in updates.items():
|
||||
if key not in valid_keys:
|
||||
continue
|
||||
if key in secret_keys:
|
||||
if not value or (len(value) > 8 and "*" * 4 in value):
|
||||
continue
|
||||
field_def = next((f for f in ch_def["fields"] if f["key"] == key), None)
|
||||
if field_def:
|
||||
if field_def["type"] == "number":
|
||||
value = int(value)
|
||||
elif field_def["type"] == "bool":
|
||||
value = bool(value)
|
||||
local_config[key] = value
|
||||
applied[key] = value
|
||||
|
||||
existing = self._parse_channel_list(conf().get("channel_type", ""))
|
||||
if channel_name not in existing:
|
||||
existing.append(channel_name)
|
||||
new_channel_type = ",".join(existing)
|
||||
local_config["channel_type"] = new_channel_type
|
||||
|
||||
config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__)))), "config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
file_cfg = json.load(f)
|
||||
else:
|
||||
file_cfg = {}
|
||||
file_cfg.update(applied)
|
||||
file_cfg["channel_type"] = new_channel_type
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(file_cfg, f, indent=4, ensure_ascii=False)
|
||||
|
||||
logger.info(f"[WebChannel] Channel '{channel_name}' connecting, channel_type={new_channel_type}")
|
||||
|
||||
def _do_start():
|
||||
try:
|
||||
import sys
|
||||
app_module = sys.modules.get('__main__') or sys.modules.get('app')
|
||||
clear_fn = getattr(app_module, '_clear_singleton_cache', None) if app_module else None
|
||||
mgr = getattr(app_module, '_channel_mgr', None) if app_module else None
|
||||
if mgr is None:
|
||||
logger.warning(f"[WebChannel] ChannelManager not available, cannot start '{channel_name}'")
|
||||
return
|
||||
# Stop existing instance first if still running (e.g. re-connect without disconnect)
|
||||
existing_ch = mgr.get_channel(channel_name)
|
||||
if existing_ch is not None:
|
||||
logger.info(f"[WebChannel] Stopping existing '{channel_name}' before reconnect...")
|
||||
mgr.stop(channel_name)
|
||||
# Always wait for the remote service to release the old connection before
|
||||
# establishing a new one (DingTalk drops callbacks on duplicate connections)
|
||||
logger.info(f"[WebChannel] Waiting for '{channel_name}' old connection to close...")
|
||||
time.sleep(5)
|
||||
if clear_fn:
|
||||
clear_fn(channel_name)
|
||||
logger.info(f"[WebChannel] Starting channel '{channel_name}'...")
|
||||
mgr.start([channel_name], first_start=False)
|
||||
logger.info(f"[WebChannel] Channel '{channel_name}' start completed")
|
||||
except Exception as e:
|
||||
logger.error(f"[WebChannel] Failed to start channel '{channel_name}': {e}",
|
||||
exc_info=True)
|
||||
|
||||
threading.Thread(target=_do_start, daemon=True).start()
|
||||
|
||||
return json.dumps({
|
||||
"status": "success",
|
||||
"channel_type": new_channel_type,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
def _handle_disconnect(self, channel_name: str):
|
||||
existing = self._parse_channel_list(conf().get("channel_type", ""))
|
||||
existing = [ch for ch in existing if ch != channel_name]
|
||||
new_channel_type = ",".join(existing)
|
||||
|
||||
local_config = conf()
|
||||
local_config["channel_type"] = new_channel_type
|
||||
|
||||
config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__)))), "config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
file_cfg = json.load(f)
|
||||
else:
|
||||
file_cfg = {}
|
||||
file_cfg["channel_type"] = new_channel_type
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(file_cfg, f, indent=4, ensure_ascii=False)
|
||||
|
||||
def _do_stop():
|
||||
try:
|
||||
import sys
|
||||
app_module = sys.modules.get('__main__') or sys.modules.get('app')
|
||||
mgr = getattr(app_module, '_channel_mgr', None) if app_module else None
|
||||
clear_fn = getattr(app_module, '_clear_singleton_cache', None) if app_module else None
|
||||
if mgr:
|
||||
mgr.stop(channel_name)
|
||||
else:
|
||||
logger.warning(f"[WebChannel] ChannelManager not found, cannot stop '{channel_name}'")
|
||||
if clear_fn:
|
||||
clear_fn(channel_name)
|
||||
logger.info(f"[WebChannel] Channel '{channel_name}' disconnected, "
|
||||
f"channel_type={new_channel_type}")
|
||||
except Exception as e:
|
||||
logger.warning(f"[WebChannel] Failed to stop channel '{channel_name}': {e}",
|
||||
exc_info=True)
|
||||
|
||||
threading.Thread(target=_do_stop, daemon=True).start()
|
||||
|
||||
return json.dumps({
|
||||
"status": "success",
|
||||
"channel_type": new_channel_type,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _get_workspace_root():
|
||||
"""Resolve the agent workspace directory."""
|
||||
@@ -899,30 +411,6 @@ def _get_workspace_root():
|
||||
return expand_path(conf().get("agent_workspace", "~/cow"))
|
||||
|
||||
|
||||
class ToolsHandler:
|
||||
def GET(self):
|
||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||
try:
|
||||
from agent.tools.tool_manager import ToolManager
|
||||
tm = ToolManager()
|
||||
if not tm.tool_classes:
|
||||
tm.load_tools()
|
||||
tools = []
|
||||
for name, cls in tm.tool_classes.items():
|
||||
try:
|
||||
instance = cls()
|
||||
tools.append({
|
||||
"name": name,
|
||||
"description": instance.description,
|
||||
})
|
||||
except Exception:
|
||||
tools.append({"name": name, "description": ""})
|
||||
return json.dumps({"status": "success", "tools": tools}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"[WebChannel] Tools API error: {e}")
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
|
||||
class SkillsHandler:
|
||||
def GET(self):
|
||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||
@@ -938,30 +426,6 @@ class SkillsHandler:
|
||||
logger.error(f"[WebChannel] Skills API error: {e}")
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
def POST(self):
|
||||
web.header('Content-Type', 'application/json; charset=utf-8')
|
||||
try:
|
||||
from agent.skills.service import SkillService
|
||||
from agent.skills.manager import SkillManager
|
||||
body = json.loads(web.data())
|
||||
action = body.get("action")
|
||||
name = body.get("name")
|
||||
if not action or not name:
|
||||
return json.dumps({"status": "error", "message": "action and name are required"})
|
||||
workspace_root = _get_workspace_root()
|
||||
manager = SkillManager(custom_dir=os.path.join(workspace_root, "skills"))
|
||||
service = SkillService(manager)
|
||||
if action == "open":
|
||||
service.open({"name": name})
|
||||
elif action == "close":
|
||||
service.close({"name": name})
|
||||
else:
|
||||
return json.dumps({"status": "error", "message": f"unknown action: {action}"})
|
||||
return json.dumps({"status": "success"}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"[WebChannel] Skills POST error: {e}")
|
||||
return json.dumps({"status": "error", "message": str(e)})
|
||||
|
||||
|
||||
class MemoryHandler:
|
||||
def GET(self):
|
||||
|
||||
@@ -23,7 +23,7 @@ Cow项目从简单的聊天机器人全面升级为超级智能助理 **CowAgent
|
||||
|
||||
在后续的长期对话中,Agent会在需要的时候智能记录或检索记忆,并对自身设定、用户偏好、记忆文件等进行不断更新,总结和记录经验和教训,真正实现自主思考和不断成长。
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260203000455.png">
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260203000455.png" />
|
||||
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@ Cow项目从简单的聊天机器人全面升级为超级智能助理 **CowAgent
|
||||
|
||||
针对操作系统的终端和文件的访问能力,是最基础和核心的工具,其他很多工具或技能都是基于基础工具进行扩展。用户可通过手机端与Agent交互,操作个人电脑或服务器上的资源:
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202181130.png">
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202181130.png" />
|
||||
|
||||
#### 1.2 编程能力
|
||||
|
||||
基于编程能力和系统访问能力,Agent可以实现从信息搜索、图片等素材生成、编码、测试、部署、Nginx配置修改、发布的 Vibecoding 全流程,通过手机端简单的一句命令完成应用的快速demo:
|
||||
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260203121008.png">
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260203121008.png" />
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ Cow项目从简单的聊天机器人全面升级为超级智能助理 **CowAgent
|
||||
基于 scheduler 工具实现动态定时任务,支持 **一次性任务、固定时间间隔、Cron表达式** 三种形式,任务触发可选择**固定消息发送** 或 **Agent动态任务** 执行两种模式,有很高灵活性:
|
||||
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202195402.png">
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202195402.png" />
|
||||
|
||||
同时你也可以通过自然语言快速查看和管理已有的定时任务。
|
||||
|
||||
@@ -62,7 +62,7 @@ Cow项目从简单的聊天机器人全面升级为超级智能助理 **CowAgent
|
||||
|
||||
技能所需要的秘钥存储在环境变量文件中,由 `env_config` 工具进行管理,你可以通过对话的方式更新秘钥,工具内置了安全保护和脱敏策略,会严格保护秘钥安全:
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202234939.png">
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202234939.png" />
|
||||
|
||||
### 3. 技能系统
|
||||
|
||||
@@ -77,7 +77,7 @@ Cow项目从简单的聊天机器人全面升级为超级智能助理 **CowAgent
|
||||
|
||||
通过 `skill-creator` 技能可以通过对话的方式快速创建技能。你可以在与Agent的写作中让他对将某个工作流程固化为技能,或者把任意接口文档和示例发送给Agent,让他直接完成对接:
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202202247.png">
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202202247.png" />
|
||||
|
||||
|
||||
#### 3.2 搜索和图像识别
|
||||
@@ -85,7 +85,7 @@ Cow项目从简单的聊天机器人全面升级为超级智能助理 **CowAgent
|
||||
- **搜索技能:** 系统内置实现了 `bocha-search`(博查搜索)的Skill,依赖环境变量 `BOCHA_SEARCH_API_KEY`,可在[控制台](https://open.bochaai.com/)进行创建,并发送给Agent完成配置
|
||||
- **图像识别技能:** 实现了 `openai-image-vision` 插件,可使用 gpt-4.1-mini、gpt-4.1 等图像识别模型。依赖秘钥 `OPENAI_API_KEY`,可通过config.json或env_config工具进行维护。
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202213219.png">
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202213219.png" />
|
||||
|
||||
|
||||
#### 3.3 三方知识库和插件
|
||||
@@ -113,7 +113,7 @@ Cow项目从简单的聊天机器人全面升级为超级智能助理 **CowAgent
|
||||
|
||||
Agent可根据智能体的名称和描述进行决策,并通过 app_code 调用接口访问对应的应用/工作流,通过该技能,可以灵活访问LinkAI平台上的智能体、知识库、插件等能力,实现效果如下:
|
||||
|
||||
<img width="750" src="https://cdn.link-ai.tech/doc/20260202234350.png">
|
||||
<img width="750" src="https://cdn.link-ai.tech/doc/20260202234350.png" />
|
||||
|
||||
注:需通过 `env_config` 配置 `LINKAI_API_KEY`,或在config.json中添加 `linkai_api_key` 配置。
|
||||
|
||||
|
||||
38
docs/channels/dingtalk.mdx
Normal file
38
docs/channels/dingtalk.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: 钉钉
|
||||
description: 将 CowAgent 接入钉钉应用
|
||||
---
|
||||
|
||||
通过钉钉开放平台创建智能机器人应用,将 CowAgent 接入钉钉。
|
||||
|
||||
## 一、创建应用
|
||||
|
||||
1. 进入 [钉钉开发者后台](https://open-dev.dingtalk.com/fe/app#/corp/app),点击 **创建应用**,填写应用信息
|
||||
2. 点击添加应用能力,选择 **机器人** 能力并添加
|
||||
3. 配置机器人信息后点击 **发布**
|
||||
|
||||
## 二、项目配置
|
||||
|
||||
1. 在 **凭证与基础信息** 中获取 `Client ID` 和 `Client Secret`
|
||||
|
||||
2. 填入 `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "dingtalk",
|
||||
"dingtalk_client_id": "YOUR_CLIENT_ID",
|
||||
"dingtalk_client_secret": "YOUR_CLIENT_SECRET"
|
||||
}
|
||||
```
|
||||
|
||||
3. 安装依赖:
|
||||
|
||||
```bash
|
||||
pip3 install dingtalk_stream
|
||||
```
|
||||
|
||||
4. 启动项目后,在钉钉开发者后台点击 **事件订阅**,点击 **已完成接入,验证连接通道**,显示"连接接入成功"即表示配置完成
|
||||
|
||||
## 三、使用
|
||||
|
||||
与机器人私聊或将机器人拉入企业群中均可开启对话。
|
||||
67
docs/channels/feishu.mdx
Normal file
67
docs/channels/feishu.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: 飞书
|
||||
description: 将 CowAgent 接入飞书应用
|
||||
---
|
||||
|
||||
通过自建应用将 CowAgent 接入飞书,支持 WebSocket 长连接(推荐)和 Webhook 两种事件接收模式。
|
||||
|
||||
## 一、创建企业自建应用
|
||||
|
||||
### 1. 创建应用
|
||||
|
||||
进入 [飞书开发平台](https://open.feishu.cn/app/),点击 **创建企业自建应用**,填写必要信息后创建。
|
||||
|
||||
### 2. 添加机器人能力
|
||||
|
||||
在 **添加应用能力** 菜单中,为应用添加 **机器人** 能力。
|
||||
|
||||
### 3. 配置应用权限
|
||||
|
||||
点击 **权限管理**,粘贴以下权限配置,全选并批量开通:
|
||||
|
||||
```
|
||||
im:message,im:message.group_at_msg,im:message.group_at_msg:readonly,im:message.p2p_msg,im:message.p2p_msg:readonly,im:message:send_as_bot,im:resource
|
||||
```
|
||||
|
||||
## 二、项目配置
|
||||
|
||||
在 **凭证与基础信息** 中获取 `App ID` 和 `App Secret`,填入 `config.json`:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="WebSocket 模式(推荐)">
|
||||
无需公网 IP,配置如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "feishu",
|
||||
"feishu_app_id": "YOUR_APP_ID",
|
||||
"feishu_app_secret": "YOUR_APP_SECRET",
|
||||
"feishu_event_mode": "websocket"
|
||||
}
|
||||
```
|
||||
|
||||
需安装依赖:`pip3 install lark-oapi`
|
||||
</Tab>
|
||||
<Tab title="Webhook 模式">
|
||||
需要公网 IP,配置如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "feishu",
|
||||
"feishu_app_id": "YOUR_APP_ID",
|
||||
"feishu_app_secret": "YOUR_APP_SECRET",
|
||||
"feishu_token": "VERIFICATION_TOKEN",
|
||||
"feishu_event_mode": "webhook",
|
||||
"feishu_port": 9891
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## 三、配置事件订阅
|
||||
|
||||
1. 启动项目后,在飞书开放平台点击 **事件与回调**,选择 **长连接** 方式并保存
|
||||
2. 点击 **添加事件**,搜索 "接收消息",选择 "接收消息v2.0",确认添加
|
||||
3. 点击 **版本管理与发布**,创建版本并申请线上发布,审核通过后即可使用
|
||||
|
||||
完成后在飞书中搜索机器人名称,即可开始对话。
|
||||
31
docs/channels/web.mdx
Normal file
31
docs/channels/web.mdx
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Web 网页
|
||||
description: 通过 Web 网页端使用 CowAgent
|
||||
---
|
||||
|
||||
Web 是 CowAgent 的默认通道,启动后会自动运行 Web 控制台,通过浏览器即可与 Agent 对话。
|
||||
|
||||
## 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "web",
|
||||
"web_port": 9899
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
| --- | --- | --- |
|
||||
| `channel_type` | 设为 `web` | `web` |
|
||||
| `web_port` | Web 服务监听端口 | `9899` |
|
||||
|
||||
## 使用
|
||||
|
||||
启动项目后访问:
|
||||
|
||||
- 本地运行:`http://localhost:9899/chat`
|
||||
- 服务器运行:`http://<server-ip>:9899/chat`
|
||||
|
||||
<Note>
|
||||
请确保服务器防火墙和安全组已放行对应端口。
|
||||
</Note>
|
||||
56
docs/channels/wechatmp.mdx
Normal file
56
docs/channels/wechatmp.mdx
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: 微信公众号
|
||||
description: 将 CowAgent 接入微信公众号
|
||||
---
|
||||
|
||||
CowAgent 支持接入个人订阅号和企业服务号两种公众号类型。
|
||||
|
||||
| 类型 | 要求 | 特点 |
|
||||
| --- | --- | --- |
|
||||
| **个人订阅号** | 个人可申请 | 回复生成后需用户主动发消息获取 |
|
||||
| **企业服务号** | 企业申请,需通过微信认证开通客服接口 | 回复生成后可主动推送给用户 |
|
||||
|
||||
<Note>
|
||||
公众号仅支持服务器和 Docker 部署,需额外安装扩展依赖:`pip3 install -r requirements-optional.txt`
|
||||
</Note>
|
||||
|
||||
## 一、个人订阅号
|
||||
|
||||
在 `config.json` 中配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "wechatmp",
|
||||
"wechatmp_app_id": "YOUR_APP_ID",
|
||||
"wechatmp_app_secret": "YOUR_APP_SECRET",
|
||||
"wechatmp_aes_key": "",
|
||||
"wechatmp_token": "YOUR_TOKEN",
|
||||
"wechatmp_port": 80
|
||||
}
|
||||
```
|
||||
|
||||
### 配置步骤
|
||||
|
||||
1. 在 [微信公众平台](https://mp.weixin.qq.com/) 的 **设置与开发 → 基本配置 → 服务器配置** 中获取参数
|
||||
2. 启用开发者密码,将服务器 IP 加入白名单
|
||||
3. 启动程序(监听 80 端口)
|
||||
4. 在公众号后台 **启用服务器配置**,URL 格式为 `http://{HOST}/wx`
|
||||
|
||||
## 二、企业服务号
|
||||
|
||||
与个人订阅号流程基本相同,差异如下:
|
||||
|
||||
1. 在公众平台申请企业服务号并完成微信认证,确认已获得 **客服接口** 权限
|
||||
2. 在 `config.json` 中设置 `"channel_type": "wechatmp_service"`
|
||||
3. 即使是较长耗时的回复,也可以主动推送给用户
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "wechatmp_service",
|
||||
"wechatmp_app_id": "YOUR_APP_ID",
|
||||
"wechatmp_app_secret": "YOUR_APP_SECRET",
|
||||
"wechatmp_aes_key": "",
|
||||
"wechatmp_token": "YOUR_TOKEN",
|
||||
"wechatmp_port": 80
|
||||
}
|
||||
```
|
||||
59
docs/channels/wecom.mdx
Normal file
59
docs/channels/wecom.mdx
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: 企业微信
|
||||
description: 将 CowAgent 接入企业微信自建应用
|
||||
---
|
||||
|
||||
通过企业微信自建应用接入 CowAgent,支持企业内部人员单聊使用。
|
||||
|
||||
<Note>
|
||||
企业微信只能使用 Docker 部署或服务器 Python 部署,不支持本地运行模式。
|
||||
</Note>
|
||||
|
||||
## 一、准备
|
||||
|
||||
需要的资源:
|
||||
|
||||
1. 一台服务器(有公网 IP)
|
||||
2. 注册一个企业微信(个人也可注册,但无法认证)
|
||||
3. 认证企业微信还需要对应主体备案的域名
|
||||
|
||||
## 二、创建企业微信应用
|
||||
|
||||
1. 在 [企业微信管理后台](https://work.weixin.qq.com/wework_admin/frame#profile) **我的企业** 中获取 **企业ID**
|
||||
2. 切换到 **应用管理**,点击创建应用,记录 `AgentId` 和 `Secret`
|
||||
3. 点击 **设置API接收**,配置应用接口:
|
||||
- URL 格式为 `http://ip:port/wxcomapp`(认证企业需使用备案域名)
|
||||
- 随机获取 `Token` 和 `EncodingAESKey` 并保存
|
||||
|
||||
## 三、配置和运行
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "wechatcom_app",
|
||||
"wechatcom_corp_id": "YOUR_CORP_ID",
|
||||
"wechatcomapp_token": "YOUR_TOKEN",
|
||||
"wechatcomapp_secret": "YOUR_SECRET",
|
||||
"wechatcomapp_agent_id": "YOUR_AGENT_ID",
|
||||
"wechatcomapp_aes_key": "YOUR_AES_KEY",
|
||||
"wechatcomapp_port": 9898
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `wechatcom_corp_id` | 企业 ID |
|
||||
| `wechatcomapp_token` | API 接收配置中的 Token |
|
||||
| `wechatcomapp_secret` | 应用的 Secret |
|
||||
| `wechatcomapp_agent_id` | 应用的 AgentId |
|
||||
| `wechatcomapp_aes_key` | API 接收配置中的 EncodingAESKey |
|
||||
| `wechatcomapp_port` | 监听端口,默认 9898 |
|
||||
|
||||
启动程序后,回到企业微信后台保存 **消息服务器配置**,并将服务器 IP 添加到 **企业可信IP** 中。
|
||||
|
||||
<Warning>
|
||||
如遇到配置失败:1. 确保防火墙和安全组已放行端口;2. 检查各参数配置是否一致;3. 认证企业需配置备案域名。
|
||||
</Warning>
|
||||
|
||||
## 四、使用
|
||||
|
||||
在企业微信中搜索应用名称即可直接对话。如需让外部微信用户使用,可在 **我的企业 → 微信插件** 中分享邀请关注二维码。
|
||||
323
docs/docs.json
Normal file
323
docs/docs.json
Normal file
@@ -0,0 +1,323 @@
|
||||
{
|
||||
"$schema": "https://mintlify.com/docs.json",
|
||||
"name": "CowAgent",
|
||||
"description": "CowAgent - AI Super Assistant powered by LLMs, with autonomous task planning, long-term memory, skills system, and multi-channel deployment.",
|
||||
"theme": "mint",
|
||||
"appearance": {
|
||||
"default": "light"
|
||||
},
|
||||
"colors": {
|
||||
"primary": "#35A85B",
|
||||
"light": "#4ABE6E",
|
||||
"dark": "#228547"
|
||||
},
|
||||
"logo": {
|
||||
"light": "/images/logo.jpg",
|
||||
"dark": "/images/logo.jpg"
|
||||
},
|
||||
"favicon": "/images/favicon.ico",
|
||||
"navbar": {
|
||||
"links": [
|
||||
{
|
||||
"label": "官网",
|
||||
"href": "https://cowagent.ai/"
|
||||
},
|
||||
{
|
||||
"label": "GitHub",
|
||||
"href": "https://github.com/zhayujie/chatgpt-on-wechat"
|
||||
}
|
||||
]
|
||||
},
|
||||
"footer": {
|
||||
"socials": {
|
||||
"github": "https://github.com/zhayujie/chatgpt-on-wechat"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"languages": [
|
||||
{
|
||||
"language": "zh",
|
||||
"default": true,
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "项目介绍",
|
||||
"groups": [
|
||||
{
|
||||
"group": "概览",
|
||||
"pages": [
|
||||
"intro/index",
|
||||
"intro/architecture",
|
||||
"intro/features"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "快速开始",
|
||||
"groups": [
|
||||
{
|
||||
"group": "安装部署",
|
||||
"pages": [
|
||||
"guide/quick-start",
|
||||
"guide/manual-install"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "模型",
|
||||
"groups": [
|
||||
{
|
||||
"group": "模型配置",
|
||||
"pages": [
|
||||
"models/index",
|
||||
"models/minimax",
|
||||
"models/glm",
|
||||
"models/qwen",
|
||||
"models/kimi",
|
||||
"models/doubao",
|
||||
"models/claude",
|
||||
"models/gemini",
|
||||
"models/openai",
|
||||
"models/deepseek",
|
||||
"models/linkai"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "工具",
|
||||
"groups": [
|
||||
{
|
||||
"group": "工具系统",
|
||||
"pages": [
|
||||
"tools/index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "内置工具",
|
||||
"pages": [
|
||||
"tools/read",
|
||||
"tools/write",
|
||||
"tools/edit",
|
||||
"tools/ls",
|
||||
"tools/bash",
|
||||
"tools/send",
|
||||
"tools/memory",
|
||||
"tools/env-config"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "可选工具",
|
||||
"pages": [
|
||||
"tools/web-search",
|
||||
"tools/scheduler"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "技能",
|
||||
"groups": [
|
||||
{
|
||||
"group": "技能系统",
|
||||
"pages": [
|
||||
"skills/index",
|
||||
"skills/skill-creator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "内置技能",
|
||||
"pages": [
|
||||
"skills/image-vision",
|
||||
"skills/linkai-agent",
|
||||
"skills/web-fetch"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "记忆",
|
||||
"groups": [
|
||||
{
|
||||
"group": "记忆系统",
|
||||
"pages": [
|
||||
"memory"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "通道",
|
||||
"groups": [
|
||||
{
|
||||
"group": "接入渠道",
|
||||
"pages": [
|
||||
"channels/web",
|
||||
"channels/feishu",
|
||||
"channels/dingtalk",
|
||||
"channels/wecom",
|
||||
"channels/wechatmp"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "版本",
|
||||
"groups": [
|
||||
{
|
||||
"group": "发布记录",
|
||||
"pages": [
|
||||
"releases/overview",
|
||||
"releases/v2.0.1",
|
||||
"releases/v2.0.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "en",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Introduction",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
"en/intro/index",
|
||||
"en/intro/architecture",
|
||||
"en/intro/features"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Get Started",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Installation",
|
||||
"pages": [
|
||||
"en/guide/quick-start",
|
||||
"en/guide/manual-install"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Models",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Model Configuration",
|
||||
"pages": [
|
||||
"en/models/index",
|
||||
"en/models/minimax",
|
||||
"en/models/glm",
|
||||
"en/models/qwen",
|
||||
"en/models/kimi",
|
||||
"en/models/doubao",
|
||||
"en/models/claude",
|
||||
"en/models/gemini",
|
||||
"en/models/openai",
|
||||
"en/models/deepseek",
|
||||
"en/models/linkai"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Tools",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Tools System",
|
||||
"pages": [
|
||||
"en/tools/index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Built-in Tools",
|
||||
"pages": [
|
||||
"en/tools/read",
|
||||
"en/tools/write",
|
||||
"en/tools/edit",
|
||||
"en/tools/ls",
|
||||
"en/tools/bash",
|
||||
"en/tools/send",
|
||||
"en/tools/memory",
|
||||
"en/tools/env-config"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Optional Tools",
|
||||
"pages": [
|
||||
"en/tools/web-search",
|
||||
"en/tools/scheduler"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Skills",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Skills System",
|
||||
"pages": [
|
||||
"en/skills/index",
|
||||
"en/skills/skill-creator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Built-in Skills",
|
||||
"pages": [
|
||||
"en/skills/image-vision",
|
||||
"en/skills/linkai-agent",
|
||||
"en/skills/web-fetch"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Memory",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Memory System",
|
||||
"pages": [
|
||||
"en/memory"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Channels",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Platforms",
|
||||
"pages": [
|
||||
"en/channels/web",
|
||||
"en/channels/feishu",
|
||||
"en/channels/dingtalk",
|
||||
"en/channels/wecom",
|
||||
"en/channels/wechatmp"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Releases",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Release Notes",
|
||||
"pages": [
|
||||
"en/releases/overview",
|
||||
"en/releases/v2.0.1",
|
||||
"en/releases/v2.0.0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
38
docs/en/channels/dingtalk.mdx
Normal file
38
docs/en/channels/dingtalk.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: DingTalk
|
||||
description: Integrate CowAgent into DingTalk application
|
||||
---
|
||||
|
||||
Integrate CowAgent into DingTalk by creating an intelligent robot app on the DingTalk Open Platform.
|
||||
|
||||
## 1. Create App
|
||||
|
||||
1. Go to [DingTalk Developer Console](https://open-dev.dingtalk.com/fe/app#/corp/app), click **Create App**, fill in app information
|
||||
2. Click **Add App Capability**, select **Robot** capability and add
|
||||
3. Configure robot information and click **Publish**
|
||||
|
||||
## 2. Project Configuration
|
||||
|
||||
1. Get `Client ID` and `Client Secret` from **Credentials & Basic Info**
|
||||
|
||||
2. Fill in `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "dingtalk",
|
||||
"dingtalk_client_id": "YOUR_CLIENT_ID",
|
||||
"dingtalk_client_secret": "YOUR_CLIENT_SECRET"
|
||||
}
|
||||
```
|
||||
|
||||
3. Install dependency:
|
||||
|
||||
```bash
|
||||
pip3 install dingtalk_stream
|
||||
```
|
||||
|
||||
4. After starting the project, go to DingTalk Developer Console **Event Subscription**, click **Connection verified, verify channel**. When "Connection successful" is displayed, configuration is complete
|
||||
|
||||
## 3. Usage
|
||||
|
||||
Chat privately with the robot or add it to an enterprise group to start a conversation.
|
||||
67
docs/en/channels/feishu.mdx
Normal file
67
docs/en/channels/feishu.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Feishu (Lark)
|
||||
description: Integrate CowAgent into Feishu application
|
||||
---
|
||||
|
||||
Integrate CowAgent into Feishu by creating a custom app. Supports WebSocket (recommended, no public IP required) and Webhook event receiving modes.
|
||||
|
||||
## 1. Create Enterprise Custom App
|
||||
|
||||
### 1.1 Create App
|
||||
|
||||
Go to [Feishu Developer Platform](https://open.feishu.cn/app/), click **Create Enterprise Custom App**, fill in the required information and create.
|
||||
|
||||
### 1.2 Add Bot Capability
|
||||
|
||||
In **Add App Capabilities**, add **Bot** capability to the app.
|
||||
|
||||
### 1.3 Configure App Permissions
|
||||
|
||||
Click **Permission Management**, paste the following permission string, select all and enable in batch:
|
||||
|
||||
```
|
||||
im:message,im:message.group_at_msg,im:message.group_at_msg:readonly,im:message.p2p_msg,im:message.p2p_msg:readonly,im:message:send_as_bot,im:resource
|
||||
```
|
||||
|
||||
## 2. Project Configuration
|
||||
|
||||
Get `App ID` and `App Secret` from **Credentials & Basic Info**, then fill in `config.json`:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="WebSocket Mode (Recommended)">
|
||||
No public IP required. Configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "feishu",
|
||||
"feishu_app_id": "YOUR_APP_ID",
|
||||
"feishu_app_secret": "YOUR_APP_SECRET",
|
||||
"feishu_event_mode": "websocket"
|
||||
}
|
||||
```
|
||||
|
||||
Install dependency: `pip3 install lark-oapi`
|
||||
</Tab>
|
||||
<Tab title="Webhook Mode">
|
||||
Requires public IP. Configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "feishu",
|
||||
"feishu_app_id": "YOUR_APP_ID",
|
||||
"feishu_app_secret": "YOUR_APP_SECRET",
|
||||
"feishu_token": "VERIFICATION_TOKEN",
|
||||
"feishu_event_mode": "webhook",
|
||||
"feishu_port": 9891
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## 3. Configure Event Subscription
|
||||
|
||||
1. After starting the project, go to Feishu Developer Platform **Events & Callbacks**, select **Long Connection** and save
|
||||
2. Click **Add Event**, search for "Receive Message", select "Receive Message v2.0", confirm and add
|
||||
3. Click **Version Management & Release**, create a version and apply for production release. After approval, you can use it
|
||||
|
||||
Search for the bot name in Feishu to start chatting.
|
||||
31
docs/en/channels/web.mdx
Normal file
31
docs/en/channels/web.mdx
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Web
|
||||
description: Use CowAgent through the web interface
|
||||
---
|
||||
|
||||
Web is CowAgent's default channel. The web console starts automatically after launch, allowing you to chat with the Agent through a browser.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "web",
|
||||
"web_port": 9899
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `channel_type` | Set to `web` | `web` |
|
||||
| `web_port` | Web service listen port | `9899` |
|
||||
|
||||
## Usage
|
||||
|
||||
After starting the project, visit:
|
||||
|
||||
- Local: `http://localhost:9899/chat`
|
||||
- Server: `http://<server-ip>:9899/chat`
|
||||
|
||||
<Note>
|
||||
Ensure the server firewall and security group allow the corresponding port.
|
||||
</Note>
|
||||
54
docs/en/channels/wechatmp.mdx
Normal file
54
docs/en/channels/wechatmp.mdx
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: WeChat Official Account
|
||||
description: Integrate CowAgent with WeChat Official Accounts
|
||||
---
|
||||
|
||||
CowAgent supports both personal subscription accounts and enterprise service accounts.
|
||||
|
||||
| Type | Requirements | Features |
|
||||
| --- | --- | --- |
|
||||
| **Personal Subscription** | Available to individuals | Users must send a message to retrieve replies |
|
||||
| **Enterprise Service** | Enterprise with verified customer service API | Can proactively push replies to users |
|
||||
|
||||
<Note>
|
||||
Official Accounts only support server and Docker deployment. Install extended dependencies: `pip3 install -r requirements-optional.txt`
|
||||
</Note>
|
||||
|
||||
## Personal Subscription Account
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "wechatmp",
|
||||
"wechatmp_app_id": "YOUR_APP_ID",
|
||||
"wechatmp_app_secret": "YOUR_APP_SECRET",
|
||||
"wechatmp_aes_key": "",
|
||||
"wechatmp_token": "YOUR_TOKEN",
|
||||
"wechatmp_port": 80
|
||||
}
|
||||
```
|
||||
|
||||
### Setup Steps
|
||||
|
||||
1. Get parameters from [WeChat Official Account Platform](https://mp.weixin.qq.com/) under **Settings & Development → Basic Configuration → Server Configuration**
|
||||
2. Enable developer secret and add server IP to the whitelist
|
||||
3. Start the program (listens on port 80)
|
||||
4. Enable server configuration with URL format `http://{HOST}/wx`
|
||||
|
||||
## Enterprise Service Account
|
||||
|
||||
Same setup with these differences:
|
||||
|
||||
1. Register an enterprise service account with verified **Customer Service API** permission
|
||||
2. Set `"channel_type": "wechatmp_service"` in `config.json`
|
||||
3. Replies can be proactively pushed to users
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "wechatmp_service",
|
||||
"wechatmp_app_id": "YOUR_APP_ID",
|
||||
"wechatmp_app_secret": "YOUR_APP_SECRET",
|
||||
"wechatmp_aes_key": "",
|
||||
"wechatmp_token": "YOUR_TOKEN",
|
||||
"wechatmp_port": 80
|
||||
}
|
||||
```
|
||||
59
docs/en/channels/wecom.mdx
Normal file
59
docs/en/channels/wecom.mdx
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: WeCom
|
||||
description: Integrate CowAgent into WeCom enterprise app
|
||||
---
|
||||
|
||||
Integrate CowAgent into WeCom through a custom enterprise app, supporting one-on-one chat for internal employees.
|
||||
|
||||
<Note>
|
||||
WeCom only supports Docker deployment or server Python deployment. Local run mode is not supported.
|
||||
</Note>
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
Required resources:
|
||||
|
||||
1. A server with public IP
|
||||
2. A registered WeCom account (individual registration is possible, but cannot be certified)
|
||||
3. Certified WeCom requires a domain with corresponding entity filing
|
||||
|
||||
## 2. Create WeCom App
|
||||
|
||||
1. Get **Corp ID** from **My Enterprise** in [WeCom Admin Console](https://work.weixin.qq.com/wework_admin/frame#profile)
|
||||
2. Switch to **Application Management**, click Create Application, record `AgentId` and `Secret`
|
||||
3. Click **Set API Reception**, configure application interface:
|
||||
- URL format: `http://ip:port/wxcomapp` (certified enterprises must use filed domain)
|
||||
- Generate random `Token` and `EncodingAESKey` and save
|
||||
|
||||
## 3. Configuration and Run
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "wechatcom_app",
|
||||
"wechatcom_corp_id": "YOUR_CORP_ID",
|
||||
"wechatcomapp_token": "YOUR_TOKEN",
|
||||
"wechatcomapp_secret": "YOUR_SECRET",
|
||||
"wechatcomapp_agent_id": "YOUR_AGENT_ID",
|
||||
"wechatcomapp_aes_key": "YOUR_AES_KEY",
|
||||
"wechatcomapp_port": 9898
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `wechatcom_corp_id` | Corp ID |
|
||||
| `wechatcomapp_token` | Token from API reception config |
|
||||
| `wechatcomapp_secret` | App Secret |
|
||||
| `wechatcomapp_agent_id` | App AgentId |
|
||||
| `wechatcomapp_aes_key` | EncodingAESKey from API reception config |
|
||||
| `wechatcomapp_port` | Listen port, default 9898 |
|
||||
|
||||
After starting the program, return to WeCom Admin Console to save **Message Server Configuration**, and add the server IP to **Enterprise Trusted IPs**.
|
||||
|
||||
<Warning>
|
||||
If configuration fails: 1. Ensure firewall and security group allow the port; 2. Verify all parameters are consistent; 3. Certified enterprises must configure a filed domain.
|
||||
</Warning>
|
||||
|
||||
## 4. Usage
|
||||
|
||||
Search for the app name in WeCom to start chatting. To allow external WeChat users, share the invite QR code from **My Enterprise → WeChat Plugin**.
|
||||
113
docs/en/guide/manual-install.mdx
Normal file
113
docs/en/guide/manual-install.mdx
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: Manual Install
|
||||
description: Deploy CowAgent manually (source code / Docker)
|
||||
---
|
||||
|
||||
## Source Code Deployment
|
||||
|
||||
### 1. Clone the project
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zhayujie/chatgpt-on-wechat
|
||||
cd chatgpt-on-wechat/
|
||||
```
|
||||
|
||||
<Tip>
|
||||
For network issues, use the mirror: https://gitee.com/zhayujie/chatgpt-on-wechat
|
||||
</Tip>
|
||||
|
||||
### 2. Install dependencies
|
||||
|
||||
Core dependencies (required):
|
||||
|
||||
```bash
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
Optional dependencies (recommended):
|
||||
|
||||
```bash
|
||||
pip3 install -r requirements-optional.txt
|
||||
```
|
||||
|
||||
### 3. Configure
|
||||
|
||||
Copy the config template and edit:
|
||||
|
||||
```bash
|
||||
cp config-template.json config.json
|
||||
```
|
||||
|
||||
Fill in model API keys, channel type, and other settings in `config.json`. See the [model docs](/en/models/index) for details.
|
||||
|
||||
### 4. Run
|
||||
|
||||
**Local run:**
|
||||
|
||||
```bash
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
By default, the Web service starts. Access `http://localhost:9899/chat` to chat.
|
||||
|
||||
**Background run on server:**
|
||||
|
||||
```bash
|
||||
nohup python3 app.py & tail -f nohup.out
|
||||
```
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
Docker deployment does not require cloning source code or installing dependencies. For Agent mode, source deployment is recommended for broader system access.
|
||||
|
||||
<Note>
|
||||
Requires [Docker](https://docs.docker.com/engine/install/) and docker-compose.
|
||||
</Note>
|
||||
|
||||
**1. Download config**
|
||||
|
||||
```bash
|
||||
wget https://cdn.link-ai.tech/code/cow/docker-compose.yml
|
||||
```
|
||||
|
||||
Edit `docker-compose.yml` with your configuration.
|
||||
|
||||
**2. Start container**
|
||||
|
||||
```bash
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
**3. View logs**
|
||||
|
||||
```bash
|
||||
sudo docker logs -f chatgpt-on-wechat
|
||||
```
|
||||
|
||||
## Core Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "web",
|
||||
"model": "MiniMax-M2.5",
|
||||
"agent": true,
|
||||
"agent_workspace": "~/cow",
|
||||
"agent_max_context_tokens": 40000,
|
||||
"agent_max_context_turns": 30,
|
||||
"agent_max_steps": 15
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `channel_type` | Channel type | `web` |
|
||||
| `model` | Model name | `MiniMax-M2.5` |
|
||||
| `agent` | Enable Agent mode | `true` |
|
||||
| `agent_workspace` | Agent workspace path | `~/cow` |
|
||||
| `agent_max_context_tokens` | Max context tokens | `40000` |
|
||||
| `agent_max_context_turns` | Max context turns | `30` |
|
||||
| `agent_max_steps` | Max decision steps per task | `15` |
|
||||
|
||||
<Tip>
|
||||
Full configuration options are in the project [`config.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/config.py).
|
||||
</Tip>
|
||||
39
docs/en/guide/quick-start.mdx
Normal file
39
docs/en/guide/quick-start.mdx
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: One-click Install
|
||||
description: One-click install and manage CowAgent with scripts
|
||||
---
|
||||
|
||||
The project provides scripts for one-click install, configuration, startup, and management. Script-based deployment is recommended for quick setup.
|
||||
|
||||
Supports Linux, macOS, and Windows. Requires Python 3.7-3.12 (3.9 recommended).
|
||||
|
||||
## Install Command
|
||||
|
||||
```bash
|
||||
bash <(curl -sS https://cdn.link-ai.tech/code/cow/run.sh)
|
||||
```
|
||||
|
||||
The script automatically performs these steps:
|
||||
|
||||
1. Check Python environment (requires Python 3.7+)
|
||||
2. Install required tools (git, curl, etc.)
|
||||
3. Clone project to `~/chatgpt-on-wechat`
|
||||
4. Install Python dependencies
|
||||
5. Guided configuration for AI model and channel
|
||||
6. Start service
|
||||
|
||||
By default, the Web service starts after installation. Access `http://localhost:9899/chat` to begin chatting.
|
||||
|
||||
## Management Commands
|
||||
|
||||
After installation, use these commands to manage the service:
|
||||
|
||||
| Command | Description |
|
||||
| --- | --- |
|
||||
| `./run.sh start` | Start service |
|
||||
| `./run.sh stop` | Stop service |
|
||||
| `./run.sh restart` | Restart service |
|
||||
| `./run.sh status` | Check run status |
|
||||
| `./run.sh logs` | View real-time logs |
|
||||
| `./run.sh config` | Reconfigure |
|
||||
| `./run.sh update` | Update project code |
|
||||
71
docs/en/intro/architecture.mdx
Normal file
71
docs/en/intro/architecture.mdx
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: CowAgent 2.0 system architecture and core design
|
||||
---
|
||||
|
||||
CowAgent 2.0 has evolved from a simple chatbot into a super intelligent assistant with Agent architecture, featuring autonomous thinking, task planning, long-term memory, and skill extensibility.
|
||||
|
||||
## System Architecture
|
||||
|
||||
CowAgent's architecture consists of the following core modules:
|
||||
|
||||
<img src="https://cdn.link-ai.tech/doc/68ef7b212c6f791e0e74314b912149f9-sz_5847990.png" alt="CowAgent Architecture" />
|
||||
|
||||
### Core Modules
|
||||
|
||||
| Module | Description |
|
||||
| --- | --- |
|
||||
| **Channels** | Message channel layer for receiving and sending messages. Supports Web, Feishu, DingTalk, WeCom, WeChat Official Account, and more |
|
||||
| **Agent Core** | Agent engine including task planning, memory system, and skills engine |
|
||||
| **Tools** | Tool layer for Agent to access OS resources. 10+ built-in tools |
|
||||
| **Models** | Model layer with unified access to mainstream LLMs |
|
||||
|
||||
## Agent Mode Workflow
|
||||
|
||||
When Agent mode is enabled, CowAgent runs as an autonomous agent with the following workflow:
|
||||
|
||||
1. **Receive Message** — Receive user input through channels
|
||||
2. **Understand Intent** — Analyze task requirements and context
|
||||
3. **Plan Task** — Break complex tasks into multiple steps
|
||||
4. **Invoke Tools** — Select and execute appropriate tools for each step
|
||||
5. **Update Memory** — Store important information in long-term memory
|
||||
6. **Return Result** — Send execution results back to the user
|
||||
|
||||
## Workspace Directory Structure
|
||||
|
||||
The Agent workspace is located at `~/cow` by default and stores system prompts, memory files, and skill files:
|
||||
|
||||
```
|
||||
~/cow/
|
||||
├── system.md # Agent system prompt
|
||||
├── user.md # User profile
|
||||
├── memory/ # Long-term memory storage
|
||||
│ ├── core.md # Core memory
|
||||
│ └── daily/ # Daily memory
|
||||
├── skills/ # Custom skills
|
||||
│ ├── skill-1/
|
||||
│ └── skill-2/
|
||||
└── .env # Secret keys for skills
|
||||
```
|
||||
|
||||
## Core Configuration
|
||||
|
||||
Configure Agent mode parameters in `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent": true,
|
||||
"agent_workspace": "~/cow",
|
||||
"agent_max_context_tokens": 40000,
|
||||
"agent_max_context_turns": 30,
|
||||
"agent_max_steps": 15
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `agent` | Enable Agent mode | `true` |
|
||||
| `agent_workspace` | Workspace path | `~/cow` |
|
||||
| `agent_max_context_tokens` | Max context tokens | `40000` |
|
||||
| `agent_max_context_turns` | Max context turns | `30` |
|
||||
| `agent_max_steps` | Max decision steps per task | `15` |
|
||||
105
docs/en/intro/features.mdx
Normal file
105
docs/en/intro/features.mdx
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Features
|
||||
description: CowAgent long-term memory, task planning, and skills system in detail
|
||||
---
|
||||
|
||||
## 1. Long-term Memory
|
||||
|
||||
The memory system enables the Agent to remember important information over time. The Agent proactively stores information when users share preferences, decisions, or key facts, and automatically extracts summaries when conversations reach a certain length. Memory is divided into core memory and daily memory, with hybrid retrieval supporting both keyword search and vector search.
|
||||
|
||||
On first launch, the Agent proactively asks the user for key information and records it in the workspace (default `~/cow`) — including agent settings, user identity, and memory files.
|
||||
|
||||
In subsequent long-term conversations, the Agent intelligently stores or retrieves memory as needed, continuously updating its own settings, user preferences, and memory files, summarizing experiences and lessons learned — truly achieving autonomous thinking and continuous growth.
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## 2. Task Planning and Tool Use
|
||||
|
||||
Tools are the core of how the Agent accesses operating system resources. The Agent intelligently selects and invokes tools based on task requirements, performing file read/write, command execution, scheduled tasks, and more. Built-in tools are implemented in the project's `agent/tools/` directory.
|
||||
|
||||
**Key tools:** file read/write/edit, Bash terminal, file send, scheduler, memory search, web search, environment config, and more.
|
||||
|
||||
### 2.1 Terminal and File Access
|
||||
|
||||
Access to the OS terminal and file system is the most fundamental and core capability. Many other tools and skills build on top of this. Users can interact with the Agent from a mobile device to operate resources on their personal computer or server:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202181130.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 2.2 Programming Capability
|
||||
|
||||
Combining programming and system access, the Agent can execute the complete **Vibecoding workflow** — from information search, asset generation, coding, testing, deployment, Nginx configuration, to publishing — all triggered by a single command from your phone:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203121008.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 2.3 Scheduled Tasks
|
||||
|
||||
The `scheduler` tool enables dynamic scheduled tasks, supporting **one-time tasks, fixed intervals, and Cron expressions**. Tasks can be triggered as either a **fixed message send** or an **Agent dynamic task** execution:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202195402.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 2.4 Environment Variable Management
|
||||
|
||||
Secrets required by skills are stored in an environment variable file, managed by the `env_config` tool. You can update secrets through conversation, with built-in security protection and desensitization:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234939.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## 3. Skills System
|
||||
|
||||
The Skills system provides infinite extensibility for the Agent. Each Skill consists of a description file, execution scripts (optional), and resources (optional), describing how to complete specific types of tasks. Skills allow the Agent to follow instructions for complex workflows, invoke tools, or integrate third-party systems.
|
||||
|
||||
- **Built-in skills:** Located in the project's `skills/` directory, including skill creator, image recognition, LinkAI agent, web fetch, and more. Built-in skills are automatically enabled based on dependency conditions (API keys, system commands, etc.).
|
||||
- **Custom skills:** Created by users through conversation, stored in the workspace (`~/cow/skills/`), capable of implementing any complex business process or third-party integration.
|
||||
|
||||
### 3.1 Creating Skills
|
||||
|
||||
The `skill-creator` skill enables rapid skill creation through conversation. You can ask the Agent to codify a workflow as a skill, or send any API documentation and examples for the Agent to complete the integration directly:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202202247.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 3.2 Web Search and Image Recognition
|
||||
|
||||
- **Web search:** Built-in `web_search` tool, supports multiple search engines. Configure `BOCHA_API_KEY` or `LINKAI_API_KEY` to enable.
|
||||
- **Image recognition:** Built-in `openai-image-vision` skill, supports `gpt-4.1-mini`, `gpt-4.1`, and other models. Requires `OPENAI_API_KEY`.
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202213219.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 3.3 Third-party Knowledge Bases and Plugins
|
||||
|
||||
The `linkai-agent` skill makes all agents on [LinkAI](https://link-ai.tech/) available as Skills for the Agent, enabling multi-agent decision making.
|
||||
|
||||
Configuration: set `LINKAI_API_KEY` via `env_config`, then add agent descriptions in `skills/linkai-agent/config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"app_code": "G7z6vKwp",
|
||||
"app_name": "LinkAI Customer Support",
|
||||
"app_description": "Select only when the user needs help with LinkAI platform questions"
|
||||
},
|
||||
{
|
||||
"app_code": "SFY5x7JR",
|
||||
"app_name": "Content Creator",
|
||||
"app_description": "Use only when the user needs to create images or videos"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234350.png" width="750" />
|
||||
</Frame>
|
||||
68
docs/en/intro/index.mdx
Normal file
68
docs/en/intro/index.mdx
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: CowAgent - AI Super Assistant powered by LLMs
|
||||
---
|
||||
|
||||
<img src="https://cdn.link-ai.tech/doc/78c5dd674e2c828642ecc0406669fed7.png" alt="CowAgent" width="600px"/>
|
||||
|
||||
**CowAgent** is an AI super assistant powered by LLMs with autonomous task planning, long-term memory, skills system, multimodal messages, multiple model support, and multi-platform deployment.
|
||||
|
||||
CowAgent can proactively think and plan tasks, operate computers and external resources, create and execute Skills, and continuously grow with long-term memory. It supports flexible switching between multiple models, handles text, voice, images, files and other multimodal messages, and can be integrated into web, Feishu, DingTalk, WeCom, and WeChat Official Account. It runs 7x24 hours on your personal computer or server.
|
||||
|
||||
<Card title="GitHub" icon="github" href="https://github.com/zhayujie/chatgpt-on-wechat">
|
||||
github.com/zhayujie/chatgpt-on-wechat
|
||||
</Card>
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Autonomous Task Planning" icon="brain" href="/en/intro/architecture">
|
||||
Understands complex tasks and autonomously plans execution, continuously thinking and invoking tools until goals are achieved. Supports accessing file systems, terminals, browsers, schedulers, and other system resources through tools.
|
||||
</Card>
|
||||
<Card title="Long-term Memory" icon="database" href="/en/memory">
|
||||
Automatically persists conversation memory to local files and databases, including core memory and daily memory, with keyword and vector retrieval support.
|
||||
</Card>
|
||||
<Card title="Skills System" icon="puzzle-piece" href="/en/skills/index">
|
||||
Implements a Skills creation and execution engine with built-in skills, and supports custom Skills development through natural language conversation.
|
||||
</Card>
|
||||
<Card title="Multimodal Messages" icon="image" href="/en/channels/web">
|
||||
Supports parsing, processing, generating, and sending text, images, voice, files, and other message types.
|
||||
</Card>
|
||||
<Card title="Multiple Model Support" icon="microchip" href="/en/models/index">
|
||||
Supports mainstream model providers including OpenAI, Claude, Gemini, DeepSeek, MiniMax, GLM, Qwen, Kimi, Doubao, and more.
|
||||
</Card>
|
||||
<Card title="Multi-platform Deployment" icon="server" href="/en/channels/web">
|
||||
Runs on local computers or servers, integrable into web, Feishu, DingTalk, WeChat Official Account, and WeCom applications.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Quick Experience
|
||||
|
||||
Run the following command in your terminal for one-click install, configuration, and startup:
|
||||
|
||||
```bash
|
||||
bash <(curl -sS https://cdn.link-ai.tech/code/cow/run.sh)
|
||||
```
|
||||
|
||||
By default, the Web service starts after running. Access `http://localhost:9899/chat` to chat in the web interface.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Quick Start" icon="rocket" href="/en/guide/quick-start">
|
||||
Complete installation and run guide
|
||||
</Card>
|
||||
<Card title="Architecture" icon="sitemap" href="/en/intro/architecture">
|
||||
CowAgent system architecture design
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Disclaimer
|
||||
|
||||
1. This project follows the [MIT License](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/LICENSE) and is intended for technical research and learning. Users must comply with local laws, regulations, policies, and corporate bylaws. Any illegal or rights-infringing use is prohibited.
|
||||
2. Agent mode consumes more tokens than normal chat mode. Choose models based on effectiveness and cost. Agent has access to the host operating system — deploy with caution.
|
||||
3. CowAgent focuses on open-source development and does not participate in, authorize, or issue any cryptocurrency.
|
||||
|
||||
## Community
|
||||
|
||||
Add our assistant on WeChat to join the open-source community:
|
||||
|
||||
<img width="140" src="https://img-1317903499.cos.ap-guangzhou.myqcloud.com/docs/open-community.png" />
|
||||
64
docs/en/memory.mdx
Normal file
64
docs/en/memory.mdx
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Memory
|
||||
description: CowAgent long-term memory system
|
||||
---
|
||||
|
||||
The memory system enables the Agent to remember important information over time, continuously accumulating experience, understanding user preferences, and truly achieving autonomous thinking and continuous growth.
|
||||
|
||||
## How It Works
|
||||
|
||||
The Agent proactively stores memory in the following scenarios:
|
||||
|
||||
- **When user shares important information** — Automatically identifies and stores preferences, decisions, facts, and other key information
|
||||
- **When conversation reaches a certain length** — Automatically extracts summaries to prevent information loss
|
||||
- **When retrieval is needed** — Intelligently searches historical memory, combining context for responses
|
||||
|
||||
## Memory Types
|
||||
|
||||
### Core Memory
|
||||
|
||||
Stored in `~/cow/memory/core.md`, containing long-term user preferences, important decisions, key facts, and other information that doesn't fade over time.
|
||||
|
||||
### Daily Memory
|
||||
|
||||
Stored in `~/cow/memory/daily/` directory, organized by date, recording daily conversation summaries and key events.
|
||||
|
||||
## First Launch
|
||||
|
||||
On first launch, the Agent will proactively ask the user for key information and save it to the workspace (default `~/cow`):
|
||||
|
||||
| File | Description |
|
||||
| --- | --- |
|
||||
| `system.md` | Agent system prompt and behavior settings |
|
||||
| `user.md` | User identity information and preferences |
|
||||
| `memory/core.md` | Core memory |
|
||||
| `memory/daily/` | Daily memory directory |
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## Memory Retrieval
|
||||
|
||||
The memory system supports hybrid retrieval modes:
|
||||
|
||||
- **Keyword retrieval** — Match historical memory based on keywords
|
||||
- **Vector retrieval** — Semantic similarity search, finds relevant memory even with different wording
|
||||
|
||||
The Agent automatically triggers memory retrieval during conversation as needed, incorporating relevant historical information into context.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_workspace": "~/cow",
|
||||
"agent_max_context_tokens": 40000,
|
||||
"agent_max_context_turns": 30
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `agent_workspace` | Workspace path, memory files stored under this directory | `~/cow` |
|
||||
| `agent_max_context_tokens` | Max context tokens, affects short-term memory capacity | `40000` |
|
||||
| `agent_max_context_turns` | Max context turns, oldest conversations discarded when exceeded | `30` |
|
||||
17
docs/en/models/claude.mdx
Normal file
17
docs/en/models/claude.mdx
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Claude
|
||||
description: Claude model configuration
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "claude-sonnet-4-6",
|
||||
"claude_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | Options include `claude-sonnet-4-6`, `claude-opus-4-6`, `claude-sonnet-4-5`, `claude-sonnet-4-0`, `claude-3-5-sonnet-latest`, etc. See [official models](https://docs.anthropic.com/en/docs/about-claude/models/overview) |
|
||||
| `claude_api_key` | Create at [Claude Console](https://console.anthropic.com/settings/keys) |
|
||||
| `claude_api_base` | Optional. Defaults to `https://api.anthropic.com/v1`. Change to use third-party proxy |
|
||||
22
docs/en/models/deepseek.mdx
Normal file
22
docs/en/models/deepseek.mdx
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: DeepSeek
|
||||
description: DeepSeek model configuration
|
||||
---
|
||||
|
||||
Use OpenAI-compatible configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "deepseek-chat",
|
||||
"bot_type": "chatGPT",
|
||||
"open_ai_api_key": "YOUR_API_KEY",
|
||||
"open_ai_api_base": "https://api.deepseek.com/v1"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | `deepseek-chat` (DeepSeek-V3), `deepseek-reasoner` (DeepSeek-R1) |
|
||||
| `bot_type` | Must be `chatGPT` (OpenAI-compatible mode) |
|
||||
| `open_ai_api_key` | Create at [DeepSeek Platform](https://platform.deepseek.com/api_keys) |
|
||||
| `open_ai_api_base` | DeepSeek platform BASE URL |
|
||||
17
docs/en/models/doubao.mdx
Normal file
17
docs/en/models/doubao.mdx
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Doubao (ByteDance)
|
||||
description: Doubao (Volcano Ark) model configuration
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "doubao-seed-2-0-code-preview-260215",
|
||||
"ark_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | Options include `doubao-seed-2-0-code-preview-260215`, `doubao-seed-2-0-pro-260215`, `doubao-seed-2-0-lite-260215`, etc. |
|
||||
| `ark_api_key` | Create at [Volcano Ark Console](https://console.volcengine.com/ark/region:ark+cn-beijing/apikey) |
|
||||
| `ark_base_url` | Optional. Defaults to `https://ark.cn-beijing.volces.com/api/v3` |
|
||||
16
docs/en/models/gemini.mdx
Normal file
16
docs/en/models/gemini.mdx
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Gemini
|
||||
description: Google Gemini model configuration
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gemini-3.1-pro-preview",
|
||||
"gemini_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | Options include `gemini-3.1-pro-preview`, `gemini-3-flash-preview`, `gemini-3-pro-preview`, `gemini-2.5-pro`, `gemini-2.0-flash`, etc. See [official docs](https://ai.google.dev/gemini-api/docs/models) |
|
||||
| `gemini_api_key` | Create at [Google AI Studio](https://aistudio.google.com/app/apikey) |
|
||||
27
docs/en/models/glm.mdx
Normal file
27
docs/en/models/glm.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: GLM (Zhipu AI)
|
||||
description: Zhipu AI GLM model configuration
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "glm-5",
|
||||
"zhipu_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | Options include `glm-5`, `glm-4.7`, `glm-4-plus`, `glm-4-flash`, `glm-4-air`, etc. See [model codes](https://bigmodel.cn/dev/api/normal-model/glm-4) |
|
||||
| `zhipu_ai_api_key` | Create at [Zhipu AI Console](https://www.bigmodel.cn/usercenter/proj-mgmt/apikeys) |
|
||||
|
||||
OpenAI-compatible configuration is also supported:
|
||||
|
||||
```json
|
||||
{
|
||||
"bot_type": "chatGPT",
|
||||
"model": "glm-5",
|
||||
"open_ai_api_base": "https://open.bigmodel.cn/api/paas/v4",
|
||||
"open_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
55
docs/en/models/index.mdx
Normal file
55
docs/en/models/index.mdx
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Models Overview
|
||||
description: Supported models and recommended choices for CowAgent
|
||||
---
|
||||
|
||||
CowAgent supports mainstream LLMs from domestic and international providers. Model interfaces are implemented in the project's `models/` directory.
|
||||
|
||||
<Note>
|
||||
For Agent mode, the following models are recommended based on quality and cost: MiniMax-M2.5, glm-5, kimi-k2.5, qwen3.5-plus, claude-sonnet-4-6, gemini-3.1-pro-preview
|
||||
</Note>
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure the model name and API key in `config.json` according to your chosen model. Each model also supports OpenAI-compatible access by setting `bot_type` to `chatGPT` and configuring `open_ai_api_base` and `open_ai_api_key`.
|
||||
|
||||
You can also use the [LinkAI](https://link-ai.tech) platform interface to flexibly switch between multiple models with support for knowledge base, workflows, and other Agent capabilities.
|
||||
|
||||
## Supported Models
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="MiniMax" href="/en/models/minimax">
|
||||
MiniMax-M2.5 and other series models
|
||||
</Card>
|
||||
<Card title="GLM (Zhipu AI)" href="/en/models/glm">
|
||||
glm-5, glm-4.7 and other series models
|
||||
</Card>
|
||||
<Card title="Qwen (Tongyi Qianwen)" href="/en/models/qwen">
|
||||
qwen3.5-plus, qwen3-max and more
|
||||
</Card>
|
||||
<Card title="Kimi" href="/en/models/kimi">
|
||||
kimi-k2.5, kimi-k2 and more
|
||||
</Card>
|
||||
<Card title="Doubao (ByteDance)" href="/en/models/doubao">
|
||||
doubao-seed series models
|
||||
</Card>
|
||||
<Card title="Claude" href="/en/models/claude">
|
||||
claude-sonnet-4-6 and more
|
||||
</Card>
|
||||
<Card title="Gemini" href="/en/models/gemini">
|
||||
gemini-3.1-pro-preview and more
|
||||
</Card>
|
||||
<Card title="OpenAI" href="/en/models/openai">
|
||||
gpt-4.1, o-series and more
|
||||
</Card>
|
||||
<Card title="DeepSeek" href="/en/models/deepseek">
|
||||
deepseek-chat, deepseek-reasoner
|
||||
</Card>
|
||||
<Card title="LinkAI" href="/en/models/linkai">
|
||||
Unified multi-model interface + knowledge base
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Tip>
|
||||
For a full list of model names, refer to the project's [`common/const.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/common/const.py) file.
|
||||
</Tip>
|
||||
27
docs/en/models/kimi.mdx
Normal file
27
docs/en/models/kimi.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Kimi (Moonshot)
|
||||
description: Kimi (Moonshot) model configuration
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "kimi-k2.5",
|
||||
"moonshot_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | Options include `kimi-k2.5`, `kimi-k2`, `moonshot-v1-8k`, `moonshot-v1-32k`, `moonshot-v1-128k` |
|
||||
| `moonshot_api_key` | Create at [Moonshot Console](https://platform.moonshot.cn/console/api-keys) |
|
||||
|
||||
OpenAI-compatible configuration is also supported:
|
||||
|
||||
```json
|
||||
{
|
||||
"bot_type": "chatGPT",
|
||||
"model": "kimi-k2.5",
|
||||
"open_ai_api_base": "https://api.moonshot.cn/v1",
|
||||
"open_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
23
docs/en/models/linkai.mdx
Normal file
23
docs/en/models/linkai.mdx
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: LinkAI
|
||||
description: Unified access to multiple models via LinkAI platform
|
||||
---
|
||||
|
||||
The [LinkAI](https://link-ai.tech) platform lets you flexibly switch between OpenAI, Claude, Gemini, DeepSeek, Qwen, Kimi, and other models, with support for knowledge base, workflows, plugins, and other Agent capabilities.
|
||||
|
||||
```json
|
||||
{
|
||||
"use_linkai": true,
|
||||
"linkai_api_key": "YOUR_API_KEY",
|
||||
"linkai_app_code": "YOUR_APP_CODE"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `use_linkai` | Set to `true` to enable LinkAI interface |
|
||||
| `linkai_api_key` | Create at [LinkAI Console](https://link-ai.tech/console/interface) |
|
||||
| `linkai_app_code` | Optional. Code of the LinkAI agent (app or workflow) |
|
||||
| `model` | Leave empty to use the agent's default model. Can be switched flexibly on the platform. All models in the [model list](https://link-ai.tech/console/models) are supported |
|
||||
|
||||
See the [API documentation](https://docs.link-ai.tech/platform/api) for more details.
|
||||
27
docs/en/models/minimax.mdx
Normal file
27
docs/en/models/minimax.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: MiniMax
|
||||
description: MiniMax model configuration
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "MiniMax-M2.5",
|
||||
"minimax_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | Options include `MiniMax-M2.5`, `MiniMax-M2.1`, `MiniMax-M2.1-lightning`, `MiniMax-M2`, etc. |
|
||||
| `minimax_api_key` | Create at [MiniMax Console](https://platform.minimaxi.com/user-center/basic-information/interface-key) |
|
||||
|
||||
OpenAI-compatible configuration is also supported:
|
||||
|
||||
```json
|
||||
{
|
||||
"bot_type": "chatGPT",
|
||||
"model": "MiniMax-M2.5",
|
||||
"open_ai_api_base": "https://api.minimaxi.com/v1",
|
||||
"open_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
19
docs/en/models/openai.mdx
Normal file
19
docs/en/models/openai.mdx
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: OpenAI
|
||||
description: OpenAI model configuration
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-4.1-mini",
|
||||
"open_ai_api_key": "YOUR_API_KEY",
|
||||
"open_ai_api_base": "https://api.openai.com/v1"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | Matches the [model parameter](https://platform.openai.com/docs/models) of the OpenAI API. Supports o-series, gpt-5.2, gpt-5.1, gpt-4.1, etc. |
|
||||
| `open_ai_api_key` | Create at [OpenAI Platform](https://platform.openai.com/api-keys) |
|
||||
| `open_ai_api_base` | Optional. Change to use third-party proxy |
|
||||
| `bot_type` | Not required for official OpenAI models. Set to `chatGPT` when using Claude or other non-OpenAI models via proxy |
|
||||
27
docs/en/models/qwen.mdx
Normal file
27
docs/en/models/qwen.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Qwen (Tongyi Qianwen)
|
||||
description: Tongyi Qianwen model configuration
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "qwen3.5-plus",
|
||||
"dashscope_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| --- | --- |
|
||||
| `model` | Options include `qwen3.5-plus`, `qwen3-max`, `qwen-max`, `qwen-plus`, `qwen-turbo`, `qwq-plus`, etc. |
|
||||
| `dashscope_api_key` | Create at [Bailian Console](https://bailian.console.aliyun.com/?tab=model#/api-key). See [official docs](https://bailian.console.aliyun.com/?tab=api#/api) |
|
||||
|
||||
OpenAI-compatible configuration is also supported:
|
||||
|
||||
```json
|
||||
{
|
||||
"bot_type": "chatGPT",
|
||||
"model": "qwen3.5-plus",
|
||||
"open_ai_api_base": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"open_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
22
docs/en/releases/overview.mdx
Normal file
22
docs/en/releases/overview.mdx
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Changelog
|
||||
description: CowAgent version history
|
||||
---
|
||||
|
||||
| Version | Date | Description |
|
||||
| --- | --- | --- |
|
||||
| [2.0.1](/en/releases/v2.0.1) | 2026.02.27 | Built-in Web Search tool, smart context management, multiple fixes |
|
||||
| [2.0.0](/en/releases/v2.0.0) | 2026.02.03 | Full upgrade to AI super assistant |
|
||||
| 1.7.6 | 2025.05.23 | Web Channel optimization, AgentMesh plugin |
|
||||
| 1.7.5 | 2025.04.11 | DeepSeek model |
|
||||
| 1.7.4 | 2024.12.13 | Gemini 2.0 model, Web Channel |
|
||||
| 1.7.3 | 2024.10.31 | Stability improvements, database features |
|
||||
| 1.7.2 | 2024.09.26 | One-click install script, o1 model |
|
||||
| 1.7.0 | 2024.08.02 | iFlytek 4.0 model, knowledge base references |
|
||||
| 1.6.9 | 2024.07.19 | gpt-4o-mini, Alibaba voice recognition |
|
||||
| 1.6.8 | 2024.07.05 | Claude 3.5, Gemini 1.5 Pro |
|
||||
| 1.6.0 | 2024.04.26 | Kimi integration, gpt-4-turbo upgrade |
|
||||
| 1.5.0 | 2023.11.10 | gpt-4-turbo, dall-e-3, tts multimodal |
|
||||
| 1.0.0 | 2022.12.12 | Project created, first ChatGPT integration |
|
||||
|
||||
See [GitHub Releases](https://github.com/zhayujie/chatgpt-on-wechat/releases) for full history.
|
||||
63
docs/en/releases/v2.0.0.mdx
Normal file
63
docs/en/releases/v2.0.0.mdx
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: v2.0.0
|
||||
description: CowAgent 2.0 - Full upgrade from chatbot to AI super assistant
|
||||
---
|
||||
|
||||
CowAgent 2.0 is a comprehensive upgrade from a chatbot to an **AI super assistant** — capable of autonomous thinking and task planning, long-term memory, operating computers, and creating and executing skills.
|
||||
|
||||
**Release Date**: 2026.02.03 | [GitHub Release](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/2.0.0)
|
||||
|
||||
## Key Updates
|
||||
|
||||
### Agent Core
|
||||
|
||||
- **Complex Task Planning**: Autonomous planning with multi-turn reasoning
|
||||
- **Long-term Memory**: Persistent memory with keyword and vector search
|
||||
- **Built-in Tools**: 10+ tools including file ops, Bash, browser, scheduler
|
||||
- **Web search**: Built-in `web_search` tool, supports multiple search engines, configure corresponding API key to use
|
||||
- **Skills System**: Skill engine with built-in and custom skill support
|
||||
- **Security & Cost**: Secret management, prompt controls, token limits
|
||||
|
||||
### Other
|
||||
|
||||
- **Channels**: Feishu/DingTalk WebSocket support, image/file messages
|
||||
- **Models**: claude-sonnet-4-5, gemini-3-pro-preview, glm-4.7, MiniMax-M2.1, qwen3-max
|
||||
- **Deployment**: One-click install, configure, run, and management script
|
||||
|
||||
## Long-term Memory
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## Task Planning & Tools
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202181130.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203121008.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202195402.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## Skills System
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202202247.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202213219.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234350.png" width="750" />
|
||||
</Frame>
|
||||
|
||||
## Contributing
|
||||
|
||||
Welcome to [submit feedback](https://github.com/zhayujie/chatgpt-on-wechat/issues) and [contribute code](https://github.com/zhayujie/chatgpt-on-wechat/pulls).
|
||||
36
docs/en/releases/v2.0.1.mdx
Normal file
36
docs/en/releases/v2.0.1.mdx
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: v2.0.1
|
||||
description: CowAgent 2.0.1 - Built-in Web Search, smart context management, multiple fixes
|
||||
---
|
||||
|
||||
**Release Date**: 2026.02.27 | [Full Changelog](https://github.com/zhayujie/chatgpt-on-wechat/compare/2.0.0..2.0.1)
|
||||
|
||||
## New Features
|
||||
|
||||
- **Built-in Web Search tool**: Integrated web search as a built-in Agent tool, reducing decision cost ([4f0ea5d](https://github.com/zhayujie/chatgpt-on-wechat/commit/4f0ea5d7568d61db91ff69c91c429e785fd1b1c2))
|
||||
- **Claude Opus 4.6 model support**: Added support for Claude Opus 4.6 model ([#2661](https://github.com/zhayujie/chatgpt-on-wechat/pull/2661))
|
||||
- **WeCom image recognition**: Support image message recognition in WeCom channel ([#2667](https://github.com/zhayujie/chatgpt-on-wechat/pull/2667))
|
||||
|
||||
## Improvements
|
||||
|
||||
- **Smart context management**: Resolved chat context overflow with intelligent context trimming strategy to prevent token limits ([cea7fb7](https://github.com/zhayujie/chatgpt-on-wechat/commit/cea7fb7490c53454602bf05955a0e9f059bcf0fd), [8acf2db](https://github.com/zhayujie/chatgpt-on-wechat/commit/8acf2dbdfe713b84ad74b761b7f86674b1c1904d)) [#2663](https://github.com/zhayujie/chatgpt-on-wechat/issues/2663)
|
||||
- **Runtime info dynamic update**: Automatic update of timestamps and other runtime info in system prompts via dynamic functions ([#2655](https://github.com/zhayujie/chatgpt-on-wechat/pull/2655), [#2657](https://github.com/zhayujie/chatgpt-on-wechat/pull/2657))
|
||||
- **Skill prompt optimization**: Improved Skill system prompt generation, simplified tool descriptions for better Agent performance ([6c21833](https://github.com/zhayujie/chatgpt-on-wechat/commit/6c218331b1f1208ea8be6bf226936d3b556ade3e))
|
||||
- **GLM custom API Base URL**: Support custom API Base URL for GLM models ([#2660](https://github.com/zhayujie/chatgpt-on-wechat/pull/2660))
|
||||
- **Startup script optimization**: Improved `run.sh` script interaction and configuration flow ([#2656](https://github.com/zhayujie/chatgpt-on-wechat/pull/2656))
|
||||
- **Decision step logging**: Added Agent decision step logging for debugging ([cb303e6](https://github.com/zhayujie/chatgpt-on-wechat/commit/cb303e6109c50c8dfef1f5e6c1ec47223bf3cd11))
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Scheduler memory loss**: Fixed memory loss caused by Scheduler dispatcher ([a77a874](https://github.com/zhayujie/chatgpt-on-wechat/commit/a77a8741b500a408c6f5c8868856fb4b018fe9db))
|
||||
- **Empty tool calls & long results**: Fixed handling of empty tool calls and excessively long tool results ([0542700](https://github.com/zhayujie/chatgpt-on-wechat/commit/0542700f9091ebb08c1a56103b0f0f45f24aa621))
|
||||
- **OpenAI Function Call**: Fixed function call compatibility with OpenAI models ([158c87a](https://github.com/zhayujie/chatgpt-on-wechat/commit/158c87ab8b05bae054cc1b4eacdbb64fc1062ba9))
|
||||
- **Claude tool name field**: Removed extraneous tool name field from Claude model responses ([eec10cb](https://github.com/zhayujie/chatgpt-on-wechat/commit/eec10cb5db6a3d5bc12ef606606532237d2c5f6e))
|
||||
- **MiniMax reasoning**: Optimized MiniMax model reasoning content handling, hidden thinking process output ([c72cda3](https://github.com/zhayujie/chatgpt-on-wechat/commit/c72cda33864bd1542012ee6e0a8bd8c6c88cb5ed), [72b1cac](https://github.com/zhayujie/chatgpt-on-wechat/commit/72b1cacea1ba0d1f3dedacbab2e088e98fd7e172))
|
||||
- **GLM thinking process**: Hidden GLM model thinking process display ([72b1cac](https://github.com/zhayujie/chatgpt-on-wechat/commit/72b1cacea1ba0d1f3dedacbab2e088e98fd7e172))
|
||||
- **Feishu connection & SSL**: Fixed Feishu channel SSL certificate errors and connection issues ([229b14b](https://github.com/zhayujie/chatgpt-on-wechat/commit/229b14b6fcabe7123d53cab1dea39f38dab26d6d), [8674421](https://github.com/zhayujie/chatgpt-on-wechat/commit/867442155e7f095b4f38b0856f8c1d8312b5fcf7))
|
||||
- **model_type validation**: Fixed `AttributeError` caused by non-string `model_type` ([#2666](https://github.com/zhayujie/chatgpt-on-wechat/pull/2666))
|
||||
|
||||
## Platform Compatibility
|
||||
|
||||
- **Windows compatibility**: Fixed path handling, file encoding, and `os.getuid()` unavailability on Windows across multiple tool modules ([051ffd7](https://github.com/zhayujie/chatgpt-on-wechat/commit/051ffd78a372f71a967fd3259e37fe19131f83cf), [5264f7c](https://github.com/zhayujie/chatgpt-on-wechat/commit/5264f7ce18360ee4db5dcb4ebe67307977d40014))
|
||||
33
docs/en/skills/image-vision.mdx
Normal file
33
docs/en/skills/image-vision.mdx
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Image Vision
|
||||
description: Recognize images using OpenAI vision models
|
||||
---
|
||||
|
||||
# openai-image-vision
|
||||
|
||||
Analyze image content using OpenAI's GPT-4 Vision API, understanding objects, text, colors, and other elements in images.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Description |
|
||||
| --- | --- |
|
||||
| `OPENAI_API_KEY` | OpenAI API key |
|
||||
| `curl`, `base64` | System commands (usually pre-installed) |
|
||||
|
||||
Configuration:
|
||||
|
||||
- Configure `OPENAI_API_KEY` via the `env_config` tool
|
||||
- Or set `open_ai_api_key` in `config.json`
|
||||
|
||||
## Supported Models
|
||||
|
||||
- `gpt-4.1-mini` (recommended, cost-effective)
|
||||
- `gpt-4.1`
|
||||
|
||||
## Usage
|
||||
|
||||
Once configured, send an image to the Agent to automatically trigger image recognition.
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202213219.png" width="800" />
|
||||
</Frame>
|
||||
67
docs/en/skills/index.mdx
Normal file
67
docs/en/skills/index.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Skills Overview
|
||||
description: CowAgent skills system introduction
|
||||
---
|
||||
|
||||
Skills provide infinite extensibility for the Agent. Each Skill consists of a description file (`SKILL.md`), execution scripts (optional), and resources (optional), describing how to accomplish specific types of tasks.
|
||||
|
||||
The difference between Skills and Tools: Tools are atomic operations implemented in code (e.g., file read/write, command execution), while Skills are high-level workflows based on description files that can combine multiple Tools to complete complex tasks.
|
||||
|
||||
## Built-in Skills
|
||||
|
||||
Located in the project `skills/` directory, automatically enabled based on dependency conditions:
|
||||
|
||||
| Skill | Description | Dependencies |
|
||||
| --- | --- | --- |
|
||||
| [`skill-creator`](/en/skills/skill-creator) | Create custom skills through conversation | None |
|
||||
| [`openai-image-vision`](/en/skills/image-vision) | Recognize images using OpenAI vision models | `OPENAI_API_KEY` |
|
||||
| [`linkai-agent`](/en/skills/linkai-agent) | Integrate LinkAI platform agents | `LINKAI_API_KEY` |
|
||||
| [`web-fetch`](/en/skills/web-fetch) | Fetch web page text content | `curl` (enabled by default) |
|
||||
|
||||
## Custom Skills
|
||||
|
||||
Created by users through conversation, stored in workspace (`~/cow/skills/`), can implement any complex business process and third-party system integration.
|
||||
|
||||
## Skill Loading Priority
|
||||
|
||||
1. **Workspace skills** (highest): `~/cow/skills/`
|
||||
2. **Project built-in skills** (lowest): `skills/`
|
||||
|
||||
Skills with the same name are overridden by priority.
|
||||
|
||||
## Skill File Structure
|
||||
|
||||
```
|
||||
skills/
|
||||
├── my-skill/
|
||||
│ ├── SKILL.md # Skill description (frontmatter + instructions)
|
||||
│ ├── scripts/ # Execution scripts (optional)
|
||||
│ └── resources/ # Additional resources (optional)
|
||||
```
|
||||
|
||||
### SKILL.md Format
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Brief description of the skill
|
||||
metadata:
|
||||
emoji: 🔧
|
||||
requires:
|
||||
bins: ["curl"]
|
||||
env: ["MY_API_KEY"]
|
||||
primaryEnv: "MY_API_KEY"
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
Detailed instructions...
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `name` | Skill name, must match directory name |
|
||||
| `description` | Skill description, Agent decides whether to invoke based on this |
|
||||
| `metadata.requires.bins` | Required system commands |
|
||||
| `metadata.requires.env` | Required environment variables |
|
||||
| `metadata.always` | Always load (default false) |
|
||||
49
docs/en/skills/linkai-agent.mdx
Normal file
49
docs/en/skills/linkai-agent.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: LinkAI Agent
|
||||
description: Integrate LinkAI platform multi-agent skill
|
||||
---
|
||||
|
||||
# linkai-agent
|
||||
|
||||
Use agents from the [LinkAI](https://link-ai.tech/) platform as Skills for multi-agent decision-making. The Agent intelligently selects based on agent names and descriptions, calling the corresponding application or workflow via `app_code`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Description |
|
||||
| --- | --- |
|
||||
| `LINKAI_API_KEY` | LinkAI platform API key, created in [Console](https://link-ai.tech/console/interface) |
|
||||
| `curl` | System command (usually pre-installed) |
|
||||
|
||||
Configuration:
|
||||
|
||||
- Configure `LINKAI_API_KEY` via the `env_config` tool
|
||||
- Or set `linkai_api_key` in `config.json`
|
||||
|
||||
## Configure Agents
|
||||
|
||||
Add available agents in `skills/linkai-agent/config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"app_code": "G7z6vKwp",
|
||||
"app_name": "LinkAI Customer Support",
|
||||
"app_description": "Select this assistant only when the user needs help with LinkAI platform questions"
|
||||
},
|
||||
{
|
||||
"app_code": "SFY5x7JR",
|
||||
"app_name": "Content Creator",
|
||||
"app_description": "Use this assistant only when the user needs to create images or videos"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once configured, the Agent will automatically select the appropriate LinkAI agent based on the user's question.
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234350.png" width="750" />
|
||||
</Frame>
|
||||
33
docs/en/skills/skill-creator.mdx
Normal file
33
docs/en/skills/skill-creator.mdx
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Skill Creator
|
||||
description: Create custom skills through conversation
|
||||
---
|
||||
|
||||
# skill-creator
|
||||
|
||||
Quickly create, install, or update skills through natural language conversation.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No extra dependencies, always available.
|
||||
|
||||
## Usage
|
||||
|
||||
- Codify workflows as skills: "Create a skill from this deployment process"
|
||||
- Integrate third-party APIs: "Create a skill based on this API documentation"
|
||||
- Install remote skills: "Install xxx skill for me"
|
||||
|
||||
## Creation Flow
|
||||
|
||||
1. Tell the Agent what skill you want to create
|
||||
2. Agent automatically generates `SKILL.md` description and execution scripts
|
||||
3. Skill is saved to the workspace `~/cow/skills/` directory
|
||||
4. Agent will automatically recognize and use the skill in future conversations
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202202247.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
<Tip>
|
||||
See the [Skill Creator documentation](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/skills/skill-creator/SKILL.md) for details.
|
||||
</Tip>
|
||||
33
docs/en/skills/web-fetch.mdx
Normal file
33
docs/en/skills/web-fetch.mdx
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Web Fetch
|
||||
description: Fetch web page text content
|
||||
---
|
||||
|
||||
# web-fetch
|
||||
|
||||
Use curl to fetch web pages and extract readable text content. A lightweight web access method without browser automation.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Description |
|
||||
| --- | --- |
|
||||
| `curl` | System command (usually pre-installed) |
|
||||
|
||||
This skill has `always: true` set, enabled by default as long as the system has the `curl` command.
|
||||
|
||||
## Usage
|
||||
|
||||
Automatically invoked when the Agent needs to fetch content from a URL, no extra configuration needed.
|
||||
|
||||
## Comparison with browser Tool
|
||||
|
||||
| Feature | web-fetch (skill) | browser (tool) |
|
||||
| --- | --- | --- |
|
||||
| Dependencies | curl only | browser-use + playwright |
|
||||
| JS rendering | Not supported | Supported |
|
||||
| Page interaction | Not supported | Supports click, type, etc. |
|
||||
| Best for | Static page text | Dynamic web pages |
|
||||
|
||||
<Tip>
|
||||
For most web content retrieval scenarios, web-fetch is sufficient. Only use the browser tool when you need JS rendering or page interaction.
|
||||
</Tip>
|
||||
30
docs/en/tools/bash.mdx
Normal file
30
docs/en/tools/bash.mdx
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: bash - Terminal
|
||||
description: Execute system commands
|
||||
---
|
||||
|
||||
# bash
|
||||
|
||||
Execute Bash commands in the current working directory, returns stdout and stderr. API keys configured via `env_config` are automatically injected into the environment.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No extra dependencies, available by default.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `command` | string | Yes | Command to execute |
|
||||
| `timeout` | integer | No | Timeout in seconds |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Install packages and dependencies
|
||||
- Run code and tests
|
||||
- Deploy applications and services (Nginx config, process management, etc.)
|
||||
- System administration and troubleshooting
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203121008.png" width="800" />
|
||||
</Frame>
|
||||
27
docs/en/tools/browser.mdx
Normal file
27
docs/en/tools/browser.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: browser - Browser
|
||||
description: Access and interact with web pages
|
||||
---
|
||||
|
||||
# browser
|
||||
|
||||
Use a browser to access and interact with web pages, supports JavaScript-rendered dynamic pages.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Install Command |
|
||||
| --- | --- |
|
||||
| `browser-use` ≥ 0.1.40 | `pip install browser-use` |
|
||||
| `markdownify` | `pip install markdownify` |
|
||||
| `playwright` + chromium | `pip install playwright && playwright install chromium` |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Access specific URLs to get page content
|
||||
- Interact with web page elements (click, type, etc.)
|
||||
- Verify deployed web pages
|
||||
- Scrape dynamic content requiring JS rendering
|
||||
|
||||
<Note>
|
||||
The browser tool has heavy dependencies. If not needed, skip installation. For lightweight web content retrieval, use the `web-fetch` skill instead.
|
||||
</Note>
|
||||
26
docs/en/tools/edit.mdx
Normal file
26
docs/en/tools/edit.mdx
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: edit - File Edit
|
||||
description: Edit files via precise text replacement
|
||||
---
|
||||
|
||||
# edit
|
||||
|
||||
Edit files via precise text replacement. If `oldText` is empty, appends to the end of the file.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No extra dependencies, available by default.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | Yes | File path |
|
||||
| `oldText` | string | Yes | Original text to replace (empty to append) |
|
||||
| `newText` | string | Yes | Replacement text |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Modify specific parameters in configuration files
|
||||
- Fix bugs in code
|
||||
- Insert content at specific positions in files
|
||||
38
docs/en/tools/env-config.mdx
Normal file
38
docs/en/tools/env-config.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: env_config - Environment
|
||||
description: Manage API keys and secrets
|
||||
---
|
||||
|
||||
# env_config
|
||||
|
||||
Manage environment variables (API keys and secrets) in the workspace `.env` file, with secure conversational updates. Built-in security protection and desensitization.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Install Command |
|
||||
| --- | --- |
|
||||
| `python-dotenv` ≥ 1.0.0 | `pip install python-dotenv>=1.0.0` |
|
||||
|
||||
Included when installing optional dependencies: `pip3 install -r requirements-optional.txt`
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `action` | string | Yes | Operation type: `get`, `set`, `list`, `delete` |
|
||||
| `key` | string | No | Environment variable name |
|
||||
| `value` | string | No | Environment variable value (only for `set`) |
|
||||
|
||||
## Usage
|
||||
|
||||
Tell the Agent what key you need to configure, and it will automatically invoke this tool:
|
||||
|
||||
- "Configure my BOCHA_API_KEY"
|
||||
- "Set OPENAI_API_KEY to sk-xxx"
|
||||
- "Show configured environment variables"
|
||||
|
||||
Configured keys are automatically injected into the `bash` tool's execution environment.
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234939.png" width="800" />
|
||||
</Frame>
|
||||
50
docs/en/tools/index.mdx
Normal file
50
docs/en/tools/index.mdx
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Tools Overview
|
||||
description: CowAgent built-in tools system
|
||||
---
|
||||
|
||||
Tools are the core capability for Agent to access operating system resources. The Agent intelligently selects and invokes tools based on task requirements, performing file operations, command execution, web search, scheduled tasks, and more. Tools are implemented in the `agent/tools/` directory.
|
||||
|
||||
## Built-in Tools
|
||||
|
||||
The following tools are available by default with no extra configuration:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="read - File Read" icon="file" href="/en/tools/read">
|
||||
Read file content, supports text, images, PDF
|
||||
</Card>
|
||||
<Card title="write - File Write" icon="pen" href="/en/tools/write">
|
||||
Create or overwrite files
|
||||
</Card>
|
||||
<Card title="edit - File Edit" icon="pen-to-square" href="/en/tools/edit">
|
||||
Edit files via precise text replacement
|
||||
</Card>
|
||||
<Card title="ls - Directory List" icon="folder-open" href="/en/tools/ls">
|
||||
List directory contents
|
||||
</Card>
|
||||
<Card title="bash - Terminal" icon="terminal" href="/en/tools/bash">
|
||||
Execute system commands
|
||||
</Card>
|
||||
<Card title="send - File Send" icon="paper-plane" href="/en/tools/send">
|
||||
Send files or images to user
|
||||
</Card>
|
||||
<Card title="memory - Memory" icon="brain" href="/en/tools/memory">
|
||||
Search and read long-term memory
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Optional Tools
|
||||
|
||||
The following tools require additional dependencies or API key configuration:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="env_config - Environment" icon="key" href="/en/tools/env-config">
|
||||
Manage API keys and secrets
|
||||
</Card>
|
||||
<Card title="scheduler - Scheduler" icon="clock" href="/en/tools/scheduler">
|
||||
Create and manage scheduled tasks
|
||||
</Card>
|
||||
<Card title="web_search - Web Search" icon="magnifying-glass" href="/en/tools/web-search">
|
||||
Search the internet for real-time information
|
||||
</Card>
|
||||
</CardGroup>
|
||||
25
docs/en/tools/ls.mdx
Normal file
25
docs/en/tools/ls.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: ls - Directory List
|
||||
description: List directory contents
|
||||
---
|
||||
|
||||
# ls
|
||||
|
||||
List directory contents, sorted alphabetically, directories suffixed with `/`, includes hidden files.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No extra dependencies, available by default.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | Yes | Directory path, relative paths are based on workspace directory |
|
||||
| `limit` | integer | No | Maximum entries to return, default 500 |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Browse project structure
|
||||
- Find specific files
|
||||
- Check if a directory exists
|
||||
38
docs/en/tools/memory.mdx
Normal file
38
docs/en/tools/memory.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: memory - Memory
|
||||
description: Search and read long-term memory
|
||||
---
|
||||
|
||||
# memory
|
||||
|
||||
The memory tool contains two sub-tools: `memory_search` (search memory) and `memory_get` (read memory files).
|
||||
|
||||
## Dependencies
|
||||
|
||||
No extra dependencies, available by default. Managed by the Agent Core memory system.
|
||||
|
||||
## memory_search
|
||||
|
||||
Search historical memory with hybrid keyword and vector retrieval.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `query` | string | Yes | Search query |
|
||||
|
||||
## memory_get
|
||||
|
||||
Read the content of a specific memory file.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | Yes | Relative path to memory file (e.g. `MEMORY.md`, `memory/2026-01-01.md`) |
|
||||
| `start_line` | integer | No | Start line number |
|
||||
| `end_line` | integer | No | End line number |
|
||||
|
||||
## How It Works
|
||||
|
||||
The Agent automatically invokes memory tools in these scenarios:
|
||||
|
||||
- When the user shares important information → stores to memory
|
||||
- When historical context is needed → searches relevant memory
|
||||
- When conversation reaches a certain length → extracts summary for storage
|
||||
26
docs/en/tools/read.mdx
Normal file
26
docs/en/tools/read.mdx
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: read - File Read
|
||||
description: Read file content
|
||||
---
|
||||
|
||||
# read
|
||||
|
||||
Read file content. Supports text files, PDF files, images (returns metadata), and more.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No extra dependencies, available by default.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | Yes | File path, relative paths are based on workspace directory |
|
||||
| `offset` | integer | No | Start line number (1-indexed), negative values read from the end |
|
||||
| `limit` | integer | No | Number of lines to read |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- View configuration files, log files
|
||||
- Read code files for analysis
|
||||
- Check image/video file info
|
||||
42
docs/en/tools/scheduler.mdx
Normal file
42
docs/en/tools/scheduler.mdx
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: scheduler - Scheduler
|
||||
description: Create and manage scheduled tasks
|
||||
---
|
||||
|
||||
# scheduler
|
||||
|
||||
Create and manage dynamic scheduled tasks with flexible scheduling and execution modes.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Install Command |
|
||||
| --- | --- |
|
||||
| `croniter` ≥ 2.0.0 | `pip install croniter>=2.0.0` |
|
||||
|
||||
Included in core dependencies: `pip3 install -r requirements.txt`
|
||||
|
||||
## Scheduling Modes
|
||||
|
||||
| Mode | Description |
|
||||
| --- | --- |
|
||||
| One-time | Execute once at a specified time |
|
||||
| Fixed interval | Repeat at fixed time intervals |
|
||||
| Cron expression | Define complex schedules using Cron syntax |
|
||||
|
||||
## Execution Modes
|
||||
|
||||
- **Fixed message**: Send a preset message when triggered
|
||||
- **Agent dynamic task**: Agent intelligently executes the task when triggered
|
||||
|
||||
## Usage
|
||||
|
||||
Create and manage scheduled tasks with natural language:
|
||||
|
||||
- "Send me a weather report every morning at 9 AM"
|
||||
- "Check server status every 2 hours"
|
||||
- "Remind me about the meeting tomorrow at 3 PM"
|
||||
- "Show all scheduled tasks"
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202195402.png" width="800" />
|
||||
</Frame>
|
||||
25
docs/en/tools/send.mdx
Normal file
25
docs/en/tools/send.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: send - File Send
|
||||
description: Send files to user
|
||||
---
|
||||
|
||||
# send
|
||||
|
||||
Send files to the user (images, videos, audio, documents, etc.), used when the user explicitly requests to send/share a file.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No extra dependencies, available by default.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | Yes | File path, can be absolute or relative to workspace |
|
||||
| `message` | string | No | Accompanying message |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Send generated code or documents to the user
|
||||
- Send screenshots, charts
|
||||
- Share downloaded files
|
||||
34
docs/en/tools/web-search.mdx
Normal file
34
docs/en/tools/web-search.mdx
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: web_search - Web Search
|
||||
description: Search the internet for real-time information
|
||||
---
|
||||
|
||||
# web_search
|
||||
|
||||
Search the internet for real-time information, news, research, and more. Supports two search backends with automatic fallback.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Requires at least one search API key (configured via `env_config` tool or workspace `.env` file):
|
||||
|
||||
| Backend | Environment Variable | Priority | How to Get |
|
||||
| --- | --- | --- | --- |
|
||||
| Bocha Search | `BOCHA_API_KEY` | Primary | [Bocha Open Platform](https://open.bochaai.com/) |
|
||||
| LinkAI Search | `LINKAI_API_KEY` | Fallback | [LinkAI Console](https://link-ai.tech/console/interface) |
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `query` | string | Yes | Search keywords |
|
||||
| `count` | integer | No | Number of results (1-50, default 10) |
|
||||
| `freshness` | string | No | Time range: `noLimit`, `oneDay`, `oneWeek`, `oneMonth`, `oneYear`, or date range like `2025-01-01..2025-02-01` |
|
||||
| `summary` | boolean | No | Return page summaries (default false) |
|
||||
|
||||
## Use Cases
|
||||
|
||||
When the user asks about latest information, needs fact-checking, or real-time data, the Agent automatically invokes this tool.
|
||||
|
||||
<Note>
|
||||
If no search API key is configured, this tool will not be loaded.
|
||||
</Note>
|
||||
29
docs/en/tools/write.mdx
Normal file
29
docs/en/tools/write.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: write - File Write
|
||||
description: Create or overwrite files
|
||||
---
|
||||
|
||||
# write
|
||||
|
||||
Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.
|
||||
|
||||
## Dependencies
|
||||
|
||||
No extra dependencies, available by default.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | Yes | File path |
|
||||
| `content` | string | Yes | Content to write |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Create new code files or scripts
|
||||
- Generate configuration files
|
||||
- Save processing results
|
||||
|
||||
<Note>
|
||||
Single writes should not exceed 10KB. For large files, create a skeleton first, then use the edit tool to add content in chunks.
|
||||
</Note>
|
||||
113
docs/guide/manual-install.mdx
Normal file
113
docs/guide/manual-install.mdx
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: 手动安装
|
||||
description: 手动部署 CowAgent(源码 / Docker)
|
||||
---
|
||||
|
||||
## 源码部署
|
||||
|
||||
### 1. 克隆项目代码
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zhayujie/chatgpt-on-wechat
|
||||
cd chatgpt-on-wechat/
|
||||
```
|
||||
|
||||
<Tip>
|
||||
若遇到网络问题可使用国内仓库地址:https://gitee.com/zhayujie/chatgpt-on-wechat
|
||||
</Tip>
|
||||
|
||||
### 2. 安装依赖
|
||||
|
||||
核心依赖(必选):
|
||||
|
||||
```bash
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
扩展依赖(可选,建议安装):
|
||||
|
||||
```bash
|
||||
pip3 install -r requirements-optional.txt
|
||||
```
|
||||
|
||||
### 3. 配置
|
||||
|
||||
复制配置文件模板并编辑:
|
||||
|
||||
```bash
|
||||
cp config-template.json config.json
|
||||
```
|
||||
|
||||
在 `config.json` 中填写模型 API Key 和通道类型等配置,详细说明参考各 [模型文档](/models/minimax)。
|
||||
|
||||
### 4. 运行
|
||||
|
||||
**本地运行:**
|
||||
|
||||
```bash
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
运行后默认启动 Web 服务,访问 `http://localhost:9899/chat` 开始对话。
|
||||
|
||||
**服务器后台运行:**
|
||||
|
||||
```bash
|
||||
nohup python3 app.py & tail -f nohup.out
|
||||
```
|
||||
|
||||
## Docker 部署
|
||||
|
||||
使用 Docker 部署无需下载源码和安装依赖。Agent 模式下更推荐使用源码部署以获得更多系统访问能力。
|
||||
|
||||
<Note>
|
||||
需要安装 [Docker](https://docs.docker.com/engine/install/) 和 docker-compose。
|
||||
</Note>
|
||||
|
||||
**1. 下载配置文件**
|
||||
|
||||
```bash
|
||||
wget https://cdn.link-ai.tech/code/cow/docker-compose.yml
|
||||
```
|
||||
|
||||
打开 `docker-compose.yml` 填写所需配置。
|
||||
|
||||
**2. 启动容器**
|
||||
|
||||
```bash
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
**3. 查看日志**
|
||||
|
||||
```bash
|
||||
sudo docker logs -f chatgpt-on-wechat
|
||||
```
|
||||
|
||||
## 核心配置项
|
||||
|
||||
```json
|
||||
{
|
||||
"channel_type": "web",
|
||||
"model": "MiniMax-M2.5",
|
||||
"agent": true,
|
||||
"agent_workspace": "~/cow",
|
||||
"agent_max_context_tokens": 40000,
|
||||
"agent_max_context_turns": 30,
|
||||
"agent_max_steps": 15
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
| --- | --- | --- |
|
||||
| `channel_type` | 接入渠道类型 | `web` |
|
||||
| `model` | 模型名称 | `MiniMax-M2.5` |
|
||||
| `agent` | 是否启用 Agent 模式 | `true` |
|
||||
| `agent_workspace` | Agent 工作空间路径 | `~/cow` |
|
||||
| `agent_max_context_tokens` | 最大上下文 tokens | `40000` |
|
||||
| `agent_max_context_turns` | 最大上下文记忆轮次 | `30` |
|
||||
| `agent_max_steps` | 单次任务最大决策步数 | `15` |
|
||||
|
||||
<Tip>
|
||||
全部配置项可在项目 [`config.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/config.py) 文件中查看。
|
||||
</Tip>
|
||||
39
docs/guide/quick-start.mdx
Normal file
39
docs/guide/quick-start.mdx
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: 一键安装
|
||||
description: 使用脚本一键安装和管理 CowAgent
|
||||
---
|
||||
|
||||
项目提供了一键安装、配置、启动、管理程序的脚本,推荐使用脚本快速运行。
|
||||
|
||||
支持 Linux、macOS、Windows 操作系统,需安装 Python 3.7 ~ 3.12(推荐 3.9)。
|
||||
|
||||
## 安装命令
|
||||
|
||||
```bash
|
||||
bash <(curl -sS https://cdn.link-ai.tech/code/cow/run.sh)
|
||||
```
|
||||
|
||||
脚本自动执行以下流程:
|
||||
|
||||
1. 检查 Python 环境(需要 Python 3.7+)
|
||||
2. 安装必要工具(git、curl 等)
|
||||
3. 克隆项目代码到 `~/chatgpt-on-wechat`
|
||||
4. 安装 Python 依赖
|
||||
5. 引导配置 AI 模型和通信渠道
|
||||
6. 启动服务
|
||||
|
||||
运行后默认启动 Web 服务,访问 `http://localhost:9899/chat` 开始对话。
|
||||
|
||||
## 管理命令
|
||||
|
||||
安装完成后,可使用以下命令管理服务:
|
||||
|
||||
| 命令 | 说明 |
|
||||
| --- | --- |
|
||||
| `./run.sh start` | 启动服务 |
|
||||
| `./run.sh stop` | 停止服务 |
|
||||
| `./run.sh restart` | 重启服务 |
|
||||
| `./run.sh status` | 查看运行状态 |
|
||||
| `./run.sh logs` | 查看实时日志 |
|
||||
| `./run.sh config` | 重新配置 |
|
||||
| `./run.sh update` | 更新项目代码 |
|
||||
BIN
docs/images/favicon.ico
Normal file
BIN
docs/images/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
docs/images/logo.jpg
Normal file
BIN
docs/images/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
71
docs/intro/architecture.mdx
Normal file
71
docs/intro/architecture.mdx
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: 项目架构
|
||||
description: CowAgent 2.0 的系统架构和核心设计
|
||||
---
|
||||
|
||||
CowAgent 2.0 从简单的聊天机器人全面升级为超级智能助理,采用 Agent 架构设计,具备自主思考、规划任务、长期记忆和技能扩展等能力。
|
||||
|
||||
## 系统架构
|
||||
|
||||
CowAgent 的整体架构由以下核心模块组成:
|
||||
|
||||
<img src="https://cdn.link-ai.tech/doc/68ef7b212c6f791e0e74314b912149f9-sz_5847990.png" alt="CowAgent Architecture" />
|
||||
|
||||
### 核心模块说明
|
||||
|
||||
| 模块 | 说明 |
|
||||
| --- | --- |
|
||||
| **Channels** | 消息通道层,负责接收和发送消息,支持 Web、飞书、钉钉、企微、公众号等 |
|
||||
| **Agent Core** | 智能体核心引擎,包括任务规划、记忆系统和技能引擎 |
|
||||
| **Tools** | 工具层,Agent 通过工具访问操作系统资源,内置 10+ 种工具 |
|
||||
| **Models** | 模型层,支持国内外主流大语言模型的统一接入 |
|
||||
|
||||
## Agent 模式
|
||||
|
||||
启用 Agent 模式后,CowAgent 会以自主智能体的方式运行,核心工作流如下:
|
||||
|
||||
1. **接收消息** - 通过通道接收用户输入
|
||||
2. **理解意图** - 分析任务需求和上下文
|
||||
3. **规划任务** - 将复杂任务分解为多个步骤
|
||||
4. **调用工具** - 选择合适的工具执行每个步骤
|
||||
5. **记忆更新** - 将重要信息存入长期记忆
|
||||
6. **返回结果** - 将执行结果发送回用户
|
||||
|
||||
## 工作空间
|
||||
|
||||
Agent 的工作空间默认位于 `~/cow` 目录,用于存储系统提示词、记忆文件、技能文件等:
|
||||
|
||||
```
|
||||
~/cow/
|
||||
├── system.md # Agent system prompt
|
||||
├── user.md # User profile
|
||||
├── memory/ # Long-term memory storage
|
||||
│ ├── core.md # Core memory
|
||||
│ └── daily/ # Daily memory
|
||||
├── skills/ # Custom skills
|
||||
│ ├── skill-1/
|
||||
│ └── skill-2/
|
||||
└── .env # Secret keys for skills
|
||||
```
|
||||
|
||||
## 核心配置
|
||||
|
||||
在 `config.json` 中配置 Agent 模式的核心参数:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent": true,
|
||||
"agent_workspace": "~/cow",
|
||||
"agent_max_context_tokens": 40000,
|
||||
"agent_max_context_turns": 30,
|
||||
"agent_max_steps": 15
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
| --- | --- | --- |
|
||||
| `agent` | 是否启用 Agent 模式 | `true` |
|
||||
| `agent_workspace` | 工作空间路径 | `~/cow` |
|
||||
| `agent_max_context_tokens` | 最大上下文 token 数 | `40000` |
|
||||
| `agent_max_context_turns` | 最大上下文记忆轮次 | `30` |
|
||||
| `agent_max_steps` | 单次任务最大决策步数 | `15` |
|
||||
105
docs/intro/features.mdx
Normal file
105
docs/intro/features.mdx
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: 功能介绍
|
||||
description: CowAgent 长期记忆、任务规划、技能系统详细说明
|
||||
---
|
||||
|
||||
## 1. 长期记忆
|
||||
|
||||
> 记忆系统让 Agent 能够长期记住重要信息。Agent 会在用户分享偏好、决策、事实等重要信息时主动存储,也会在对话达到一定长度时自动提取摘要。记忆分为核心记忆、天级记忆,支持语义搜索和向量检索的混合检索模式。
|
||||
|
||||
第一次启动 Agent 时,Agent 会主动询问关键信息,并记录至工作空间(默认 `~/cow`)中的智能体设定、用户身份、记忆文件中。
|
||||
|
||||
在后续的长期对话中,Agent 会在需要时智能记录或检索记忆,并对自身设定、用户偏好、记忆文件等进行不断更新,总结和记录经验和教训,真正实现自主思考和不断成长。
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## 2. 任务规划和工具调用
|
||||
|
||||
工具是 Agent 访问操作系统资源的核心,Agent 会根据任务需求智能选择和调用工具,完成文件读写、命令执行、定时任务等各类操作。内置工具的实现在项目的 `agent/tools/` 目录下。
|
||||
|
||||
**主要工具:** 文件读写编辑、Bash 终端、文件发送、定时调度、记忆搜索、联网搜索、环境配置等。
|
||||
|
||||
### 2.1 终端和文件访问
|
||||
|
||||
针对操作系统的终端和文件的访问能力,是最基础和核心的工具,其他很多工具或技能都是基于此进行扩展。用户可通过手机端与 Agent 交互,操作个人电脑或服务器上的资源:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202181130.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 2.2 编程能力
|
||||
|
||||
基于编程能力和系统访问能力,Agent 可以实现从信息搜索、图片等素材生成、编码、测试、部署、Nginx 配置修改、发布的 **Vibecoding 全流程**,通过手机端简单的一句命令完成应用的快速 demo:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203121008.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 2.3 定时任务
|
||||
|
||||
基于 `scheduler` 工具实现动态定时任务,支持**一次性任务、固定时间间隔、Cron 表达式**三种形式,任务触发可选择**固定消息发送**或 **Agent 动态任务**执行两种模式:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202195402.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 2.4 环境变量管理
|
||||
|
||||
技能所需的秘钥存储在环境变量文件中,由 `env_config` 工具进行管理,你可以通过对话的方式更新秘钥,工具内置安全保护和脱敏策略:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234939.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## 3. 技能系统
|
||||
|
||||
技能系统为 Agent 提供无限的扩展性,每个 Skill 由说明文件、运行脚本(可选)、资源(可选)组成,描述如何完成特定类型的任务。通过 Skill 可以让 Agent 遵循说明完成复杂流程、调用各类工具或对接第三方系统。
|
||||
|
||||
- **内置技能:** 在项目的 `skills/` 目录下,包含技能创造器、图像识别、LinkAI 智能体、网页抓取等。内置 Skill 根据依赖条件(API Key、系统命令等)自动判断是否启用。
|
||||
- **自定义技能:** 由用户通过对话创建,存放在工作空间中(`~/cow/skills/`),可实现任何复杂的业务流程和第三方系统对接。
|
||||
|
||||
### 3.1 创建技能
|
||||
|
||||
通过 `skill-creator` 技能可以通过对话的方式快速创建技能。你可以让 Agent 将某个工作流程固化为技能,或者把任意接口文档和示例发送给 Agent,让他直接完成对接:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202202247.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 3.2 搜索和图像识别
|
||||
|
||||
- **联网搜索:** 内置 `web_search` 工具,支持多种搜索引擎,配置 `BOCHA_API_KEY` 或 `LINKAI_API_KEY` 后启用。
|
||||
- **图像识别:** 内置 `openai-image-vision` 技能,可使用 `gpt-4.1-mini`、`gpt-4.1` 等模型,依赖 `OPENAI_API_KEY`。
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202213219.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 3.3 三方知识库和插件
|
||||
|
||||
`linkai-agent` 技能可以将 [LinkAI](https://link-ai.tech/) 上的所有智能体作为 Skill 交给 Agent 使用,实现多智能体决策效果。
|
||||
|
||||
配置方式:通过 `env_config` 配置 `LINKAI_API_KEY`,并在 `skills/linkai-agent/config.json` 中添加智能体说明:
|
||||
|
||||
```json
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"app_code": "G7z6vKwp",
|
||||
"app_name": "LinkAI客服助手",
|
||||
"app_description": "当用户需要了解LinkAI平台相关问题时才选择该助手"
|
||||
},
|
||||
{
|
||||
"app_code": "SFY5x7JR",
|
||||
"app_name": "内容创作助手",
|
||||
"app_description": "当用户需要创作图片或视频时才使用该助手"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234350.png" width="750" />
|
||||
</Frame>
|
||||
62
docs/intro/index.mdx
Normal file
62
docs/intro/index.mdx
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: 项目介绍
|
||||
description: CowAgent - 基于大模型的超级AI助理
|
||||
---
|
||||
|
||||
<img src="https://cdn.link-ai.tech/doc/78c5dd674e2c828642ecc0406669fed7.png" alt="CowAgent" width="600px"/>
|
||||
|
||||
**CowAgent** 是基于大模型的超级AI助理,能够主动思考和任务规划、操作计算机和外部资源、创造和执行Skills、拥有长期记忆并不断成长。
|
||||
|
||||
CowAgent 支持灵活切换多种模型,能处理文本、语音、图片、文件等多模态消息,可接入网页、飞书、钉钉、企业微信应用、微信公众号中使用,7×24小时运行于你的个人电脑或服务器中。
|
||||
|
||||
<Card title="GitHub" icon="github" href="https://github.com/zhayujie/chatgpt-on-wechat">
|
||||
github.com/zhayujie/chatgpt-on-wechat
|
||||
</Card>
|
||||
|
||||
## 核心能力
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="复杂任务规划" icon="brain" href="/intro/architecture">
|
||||
能够理解复杂任务并自主规划执行,持续思考和调用工具直到完成目标,支持通过工具操作访问文件、终端、浏览器、定时任务等系统资源。
|
||||
</Card>
|
||||
<Card title="长期记忆" icon="database" href="/memory">
|
||||
自动将对话记忆持久化至本地文件和数据库中,包括全局记忆和天级记忆,支持关键词及向量检索。
|
||||
</Card>
|
||||
<Card title="技能系统" icon="puzzle-piece" href="/skills/index">
|
||||
实现了Skills创建和运行的引擎,内置多种技能,并支持通过自然语言对话完成自定义Skills开发。
|
||||
</Card>
|
||||
<Card title="多模态消息" icon="image" href="/channels/web">
|
||||
支持对文本、图片、语音、文件等多类型消息进行解析、处理、生成、发送等操作。
|
||||
</Card>
|
||||
<Card title="多模型接入" icon="microchip" href="/models/index">
|
||||
支持 OpenAI, Claude, Gemini, DeepSeek, MiniMax, GLM, Qwen, Kimi, Doubao 等国内外主流模型厂商。
|
||||
</Card>
|
||||
<Card title="多端部署" icon="server" href="/channels/web">
|
||||
支持运行在本地计算机或服务器,可集成到网页、飞书、钉钉、微信公众号、企业微信应用中使用。
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## 快速体验
|
||||
|
||||
在终端执行以下命令,即可一键安装、配置、启动 CowAgent:
|
||||
|
||||
```bash
|
||||
bash <(curl -sS https://cdn.link-ai.tech/code/cow/run.sh)
|
||||
```
|
||||
|
||||
运行后默认会启动 Web 服务,通过访问 `http://localhost:9899/chat` 在网页端对话。
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="快速开始" icon="rocket" href="/guide/quick-start">
|
||||
查看完整的安装和运行指南
|
||||
</Card>
|
||||
<Card title="项目架构" icon="sitemap" href="/intro/architecture">
|
||||
了解 CowAgent 的系统架构设计
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## 社区
|
||||
|
||||
添加小助手微信加入开源项目交流群:
|
||||
|
||||
<img width="140" src="https://img-1317903499.cos.ap-guangzhou.myqcloud.com/docs/open-community.png" />
|
||||
64
docs/memory.mdx
Normal file
64
docs/memory.mdx
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: 长期记忆
|
||||
description: CowAgent 的长期记忆系统
|
||||
---
|
||||
|
||||
记忆系统让 Agent 能够长期记住重要信息,在对话中不断积累经验、理解用户偏好,真正实现自主思考和持续成长。
|
||||
|
||||
## 工作原理
|
||||
|
||||
Agent 会在以下场景主动存储记忆:
|
||||
|
||||
- **用户分享重要信息时** — 自动识别偏好、决策、事实等关键信息并存储
|
||||
- **对话达到一定长度时** — 自动提取摘要,避免信息丢失
|
||||
- **需要检索时** — 智能搜索历史记忆,结合上下文进行回答
|
||||
|
||||
## 记忆类型
|
||||
|
||||
### 核心记忆
|
||||
|
||||
存储在 `~/cow/memory/core.md` 中,包含用户的长期偏好、重要决策、关键事实等不会随时间淡化的信息。
|
||||
|
||||
### 天级记忆
|
||||
|
||||
存储在 `~/cow/memory/daily/` 目录下,按日期组织,记录每天的对话摘要和关键事件。
|
||||
|
||||
## 首次启动
|
||||
|
||||
首次启动 Agent 时,Agent 会主动向用户询问关键信息,并记录至工作空间(默认 `~/cow`)中:
|
||||
|
||||
| 文件 | 说明 |
|
||||
| --- | --- |
|
||||
| `system.md` | Agent 的系统提示词和行为设定 |
|
||||
| `user.md` | 用户身份信息和偏好 |
|
||||
| `memory/core.md` | 核心记忆 |
|
||||
| `memory/daily/` | 天级记忆目录 |
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## 记忆检索
|
||||
|
||||
记忆系统支持混合检索模式:
|
||||
|
||||
- **关键词检索** — 基于关键词匹配历史记忆
|
||||
- **向量检索** — 基于语义相似度搜索,即使表述不同也能找到相关记忆
|
||||
|
||||
Agent 会在对话中根据需要自动触发记忆检索,将相关历史信息纳入上下文。
|
||||
|
||||
## 相关配置
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_workspace": "~/cow",
|
||||
"agent_max_context_tokens": 40000,
|
||||
"agent_max_context_turns": 30
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
| --- | --- | --- |
|
||||
| `agent_workspace` | 工作空间路径,记忆文件存储在此目录下 | `~/cow` |
|
||||
| `agent_max_context_tokens` | 最大上下文 token 数,影响短期记忆容量 | `40000` |
|
||||
| `agent_max_context_turns` | 最大上下文轮次,超出后自动丢弃最早对话 | `30` |
|
||||
17
docs/models/claude.mdx
Normal file
17
docs/models/claude.mdx
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Claude
|
||||
description: Claude 模型配置
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "claude-sonnet-4-6",
|
||||
"claude_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 支持 `claude-sonnet-4-6`、`claude-opus-4-6`、`claude-sonnet-4-5`、`claude-sonnet-4-0`、`claude-3-5-sonnet-latest` 等,参考 [官方模型](https://docs.anthropic.com/en/docs/about-claude/models/overview) |
|
||||
| `claude_api_key` | 在 [Claude 控制台](https://console.anthropic.com/settings/keys) 创建 |
|
||||
| `claude_api_base` | 可选,默认为 `https://api.anthropic.com/v1`,修改可接入第三方代理 |
|
||||
22
docs/models/deepseek.mdx
Normal file
22
docs/models/deepseek.mdx
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: DeepSeek
|
||||
description: DeepSeek 模型配置
|
||||
---
|
||||
|
||||
通过 OpenAI 兼容方式接入:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "deepseek-chat",
|
||||
"open_ai_api_key": "YOUR_API_KEY",
|
||||
"open_ai_api_base": "https://api.deepseek.com/v1",
|
||||
"bot_type": "chatGPT"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | `deepseek-chat`(DeepSeek-V3)、`deepseek-reasoner`(DeepSeek-R1) |
|
||||
| `bot_type` | 固定为 `chatGPT`(OpenAI 兼容方式) |
|
||||
| `open_ai_api_key` | 在 [DeepSeek 平台](https://platform.deepseek.com/api_keys) 创建 |
|
||||
| `open_ai_api_base` | DeepSeek 平台 BASE URL |
|
||||
19
docs/models/doubao.mdx
Normal file
19
docs/models/doubao.mdx
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: 豆包 Doubao
|
||||
description: 豆包 (火山方舟) 模型配置
|
||||
---
|
||||
|
||||
# 豆包 (Doubao)
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "doubao-seed-2-0-code-preview-260215",
|
||||
"ark_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 可填 `doubao-seed-2-0-code-preview-260215`、`doubao-seed-2-0-pro-260215`、`doubao-seed-2-0-lite-260215` 等 |
|
||||
| `ark_api_key` | 在 [火山方舟控制台](https://console.volcengine.com/ark/region:ark+cn-beijing/apikey) 创建 |
|
||||
| `ark_base_url` | 可选,默认为 `https://ark.cn-beijing.volces.com/api/v3` |
|
||||
16
docs/models/gemini.mdx
Normal file
16
docs/models/gemini.mdx
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Gemini
|
||||
description: Google Gemini 模型配置
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gemini-3.1-pro-preview",
|
||||
"gemini_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 支持 `gemini-3.1-pro-preview`、`gemini-3-flash-preview`、`gemini-3-pro-preview`、`gemini-2.5-pro`、`gemini-2.0-flash` 等,参考 [官方文档](https://ai.google.dev/gemini-api/docs/models) |
|
||||
| `gemini_api_key` | 在 [Google AI Studio](https://aistudio.google.com/app/apikey) 创建 |
|
||||
29
docs/models/glm.mdx
Normal file
29
docs/models/glm.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: 智谱 GLM
|
||||
description: 智谱AI GLM 模型配置
|
||||
---
|
||||
|
||||
# 智谱AI (GLM)
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "glm-5",
|
||||
"zhipu_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 可填 `glm-5`、`glm-4.7`、`glm-4-plus`、`glm-4-flash`、`glm-4-air` 等,参考 [模型编码](https://bigmodel.cn/dev/api/normal-model/glm-4) |
|
||||
| `zhipu_ai_api_key` | 在 [智谱AI 控制台](https://www.bigmodel.cn/usercenter/proj-mgmt/apikeys) 创建 |
|
||||
|
||||
也支持 OpenAI 兼容方式接入:
|
||||
|
||||
```json
|
||||
{
|
||||
"bot_type": "chatGPT",
|
||||
"model": "glm-5",
|
||||
"open_ai_api_base": "https://open.bigmodel.cn/api/paas/v4",
|
||||
"open_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
55
docs/models/index.mdx
Normal file
55
docs/models/index.mdx
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: 模型概览
|
||||
description: CowAgent 支持的模型及推荐选择
|
||||
---
|
||||
|
||||
CowAgent 支持国内外主流厂商的大语言模型,模型接口实现在项目的 `models/` 目录下。
|
||||
|
||||
<Note>
|
||||
Agent 模式下推荐使用以下模型,可根据效果及成本综合选择:MiniMax-M2.5、glm-5、kimi-k2.5、qwen3.5-plus、claude-sonnet-4-6、gemini-3.1-pro-preview
|
||||
</Note>
|
||||
|
||||
## 配置方式
|
||||
|
||||
根据所选模型,在 `config.json` 中填写对应的模型名称和 API Key 即可。每个模型也支持 OpenAI 兼容方式接入,将 `bot_type` 设为 `chatGPT`,配置 `open_ai_api_base` 和 `open_ai_api_key`。
|
||||
|
||||
同时支持使用 [LinkAI](https://link-ai.tech) 平台接口,可灵活切换多种模型并支持知识库、工作流等 Agent 能力。
|
||||
|
||||
## 支持的模型
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="MiniMax" href="/models/minimax">
|
||||
MiniMax-M2.5 等系列模型
|
||||
</Card>
|
||||
<Card title="智谱 GLM" href="/models/glm">
|
||||
glm-5、glm-4.7 等系列模型
|
||||
</Card>
|
||||
<Card title="通义千问 Qwen" href="/models/qwen">
|
||||
qwen3.5-plus、qwen3-max 等
|
||||
</Card>
|
||||
<Card title="Kimi" href="/models/kimi">
|
||||
kimi-k2.5、kimi-k2 等
|
||||
</Card>
|
||||
<Card title="豆包 Doubao" href="/models/doubao">
|
||||
doubao-seed 系列模型
|
||||
</Card>
|
||||
<Card title="Claude" href="/models/claude">
|
||||
claude-sonnet-4-6 等
|
||||
</Card>
|
||||
<Card title="Gemini" href="/models/gemini">
|
||||
gemini-3.1-pro-preview 等
|
||||
</Card>
|
||||
<Card title="OpenAI" href="/models/openai">
|
||||
gpt-4.1、o 系列等
|
||||
</Card>
|
||||
<Card title="DeepSeek" href="/models/deepseek">
|
||||
deepseek-chat、deepseek-reasoner
|
||||
</Card>
|
||||
<Card title="LinkAI" href="/models/linkai">
|
||||
多模型统一接口 + 知识库
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Tip>
|
||||
全部模型名称可参考项目 [`common/const.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/common/const.py) 文件。
|
||||
</Tip>
|
||||
29
docs/models/kimi.mdx
Normal file
29
docs/models/kimi.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Kimi
|
||||
description: Kimi (Moonshot) 模型配置
|
||||
---
|
||||
|
||||
# Kimi (Moonshot)
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "kimi-k2.5",
|
||||
"moonshot_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 可填 `kimi-k2.5`、`kimi-k2`、`moonshot-v1-8k`、`moonshot-v1-32k`、`moonshot-v1-128k` |
|
||||
| `moonshot_api_key` | 在 [Moonshot 控制台](https://platform.moonshot.cn/console/api-keys) 创建 |
|
||||
|
||||
也支持 OpenAI 兼容方式接入:
|
||||
|
||||
```json
|
||||
{
|
||||
"bot_type": "chatGPT",
|
||||
"model": "kimi-k2.5",
|
||||
"open_ai_api_base": "https://api.moonshot.cn/v1",
|
||||
"open_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
23
docs/models/linkai.mdx
Normal file
23
docs/models/linkai.mdx
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: LinkAI
|
||||
description: 通过 LinkAI 平台统一接入多种模型
|
||||
---
|
||||
|
||||
通过 [LinkAI](https://link-ai.tech) 平台可灵活切换 OpenAI、Claude、Gemini、DeepSeek、Qwen、Kimi 等多种模型,并支持知识库、工作流、插件等 Agent 能力。
|
||||
|
||||
```json
|
||||
{
|
||||
"use_linkai": true,
|
||||
"linkai_api_key": "YOUR_API_KEY",
|
||||
"linkai_app_code": "YOUR_APP_CODE"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `use_linkai` | 设为 `true` 启用 LinkAI 接口 |
|
||||
| `linkai_api_key` | 在 [控制台](https://link-ai.tech/console/interface) 创建 |
|
||||
| `linkai_app_code` | LinkAI 智能体(应用或工作流)的 code,选填 |
|
||||
| `model` | 留空则使用智能体默认模型,可在平台中灵活切换,[模型列表](https://link-ai.tech/console/models) 中的全部模型均可使用 |
|
||||
|
||||
参考 [接口文档](https://docs.link-ai.tech/platform/api) 了解更多。
|
||||
27
docs/models/minimax.mdx
Normal file
27
docs/models/minimax.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: MiniMax
|
||||
description: MiniMax 模型配置
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "MiniMax-M2.5",
|
||||
"minimax_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 可填 `MiniMax-M2.5`、`MiniMax-M2.1`、`MiniMax-M2.1-lightning`、`MiniMax-M2` 等 |
|
||||
| `minimax_api_key` | 在 [MiniMax 控制台](https://platform.minimaxi.com/user-center/basic-information/interface-key) 创建 |
|
||||
|
||||
也支持 OpenAI 兼容方式接入:
|
||||
|
||||
```json
|
||||
{
|
||||
"bot_type": "chatGPT",
|
||||
"model": "MiniMax-M2.5",
|
||||
"open_ai_api_base": "https://api.minimaxi.com/v1",
|
||||
"open_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
19
docs/models/openai.mdx
Normal file
19
docs/models/openai.mdx
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: OpenAI
|
||||
description: OpenAI 模型配置
|
||||
---
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-4.1-mini",
|
||||
"open_ai_api_key": "YOUR_API_KEY",
|
||||
"open_ai_api_base": "https://api.openai.com/v1"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 与 OpenAI 接口的 [model 参数](https://platform.openai.com/docs/models) 一致,支持 o 系列、gpt-5.2、gpt-5.1、gpt-4.1 等 |
|
||||
| `open_ai_api_key` | 在 [OpenAI 平台](https://platform.openai.com/api-keys) 创建 |
|
||||
| `open_ai_api_base` | 可选,修改可接入第三方代理接口 |
|
||||
| `bot_type` | 使用 OpenAI 官方模型时无需填写。当通过代理接口使用 Claude 等非 OpenAI 模型时,设为 `chatGPT` |
|
||||
29
docs/models/qwen.mdx
Normal file
29
docs/models/qwen.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: 通义千问 Qwen
|
||||
description: 通义千问模型配置
|
||||
---
|
||||
|
||||
# 通义千问 (Qwen)
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "qwen3.5-plus",
|
||||
"dashscope_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `model` | 可填 `qwen3.5-plus`、`qwen3-max`、`qwen-max`、`qwen-plus`、`qwen-turbo`、`qwq-plus` 等 |
|
||||
| `dashscope_api_key` | 在 [百炼控制台](https://bailian.console.aliyun.com/?tab=model#/api-key) 创建,参考 [官方文档](https://bailian.console.aliyun.com/?tab=api#/api) |
|
||||
|
||||
也支持 OpenAI 兼容方式接入:
|
||||
|
||||
```json
|
||||
{
|
||||
"bot_type": "chatGPT",
|
||||
"model": "qwen3.5-plus",
|
||||
"open_ai_api_base": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"open_ai_api_key": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
@@ -1,121 +0,0 @@
|
||||
# CowAgent 2.0
|
||||
|
||||
🚀 CowAgent 2.0 实现了从聊天机器人到**超级智能助理**的全面升级!现在它能够主动思考和规划任务、拥有长期记忆、操作计算机和外部资源、创造和执行技能,真正理解你并和你一起成长。
|
||||
|
||||
### ✨ 重点更新
|
||||
|
||||
- Agent核心能力:
|
||||
- **复杂任务规划**:能够理解复杂任务并自主规划执行,持续思考和调用工具直到完成目标,支持多轮推理和上下文理解。
|
||||
- **长期记忆**:自动将对话记忆持久化至本地文件和数据库中,包括全局记忆和天级记忆,支持关键词及向量检索。
|
||||
- **内置系统工具**:内置实现10+种工具,包括文件操作、bash终端、浏览器、文件发送、定时任务、记忆管理等。
|
||||
- **Skills**:新增Skill运行引擎,内置多种技能,并支持通过自然语言对话完成自定义Skills开发。
|
||||
- **安全和成本**:通过秘钥管理工具、提示词控制、系统权限等手段控制Agent的访问安全;通过最大记忆轮次、最大上下文token、工具执行步数对token成本进行限制。
|
||||
- 其他更新:
|
||||
- 渠道优化:飞书及钉钉接入渠道支持长连接接入(无需公网IP)、支持图片/文件消息的接收和发送。
|
||||
- 模型更新:新增claude-sonnet-4-5、gemini-3-pro-preview、glm-4.7、MiniMax-M2.1、qwen3-max等最新模型。
|
||||
- 部署优化:增加一键安装、配置、运行、管理的脚本,简化部署流程。
|
||||
|
||||
## 一、长期记忆系统
|
||||
|
||||
Agent 会在用户分享重要信息时主动存储,也会在对话达到一定长度时自动提取摘要。支持语义搜索和向量检索的混合检索模式。
|
||||
|
||||
**首次启动**时,Agent 会主动询问关键信息,并记录至工作空间(默认 `~/cow`)中的智能体设定、用户身份、记忆文件中。
|
||||
|
||||
**长期对话**中,Agent 会智能记录或检索记忆,不断更新自身设定、用户偏好,总结经验和教训,真正实现自主思考和持续成长。
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260203000455.png">
|
||||
|
||||
|
||||
## 二、任务规划与工具调用
|
||||
|
||||
Agent 根据任务需求智能选择和调用工具,完成各类复杂操作。
|
||||
|
||||
### 1. 终端和文件访问
|
||||
|
||||
最基础和核心的工具能力,用户可通过手机端与 Agent 交互,操作个人电脑或服务器上的资源:
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202181130.png">
|
||||
|
||||
### 2. 应用编程能力
|
||||
|
||||
基于编程能力和系统访问能力,Agent 可实现从信息搜索、素材生成、编码、测试、部署、Nginx配置、发布的 **Vibecoding 全流程**,通过手机端一句命令完成应用快速 demo。
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260203121008.png">
|
||||
|
||||
### 3. 定时任务
|
||||
|
||||
支持 **一次性任务、固定时间间隔、Cron表达式** 三种形式,任务触发可选择 **固定消息发送** 或 **Agent动态任务执行** 两种模式:
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202195402.png">
|
||||
|
||||
### 4. 环境变量管理
|
||||
|
||||
通过 `env_config` 工具管理技能所需秘钥,支持对话式更新,内置安全保护和脱敏策略:
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202234939.png">
|
||||
|
||||
## 三、技能系统
|
||||
|
||||
每个 Skill 由说明文件、运行脚本(可选)、资源(可选)组成,为 Agent 提供无限扩展性。
|
||||
|
||||
### 1. 技能创造器
|
||||
|
||||
通过对话方式快速创建技能,将工作流程固化或对接任意第三方接口:
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202202247.png">
|
||||
|
||||
### 2. 搜索和图像识别
|
||||
|
||||
- **搜索技能**:内置 `bocha-search`(博查搜索),配置 `BOCHA_SEARCH_API_KEY` 即可使用。
|
||||
- **图像识别**:支持 `gpt-4.1-mini`、`gpt-4.1` 等模型,配置 `OPENAI_API_KEY` 即可使用。
|
||||
|
||||
<img width="800" src="https://cdn.link-ai.tech/doc/20260202213219.png">
|
||||
|
||||
### 3. 三方知识库和插件
|
||||
|
||||
`linkai-agent` 技能可将 [LinkAI](https://link-ai.tech/) 上的所有智能体作为 skill 使用,实现多智能体决策:
|
||||
|
||||
<img width="750" src="https://cdn.link-ai.tech/doc/20260202234350.png">
|
||||
|
||||
|
||||
## 四、快速开始
|
||||
|
||||
### 一键启动
|
||||
|
||||
本次新增了一键下载、配置、运行和管理的脚本,只需命令行中执行:
|
||||
|
||||
```bash
|
||||
bash <(curl -sS https://cdn.link-ai.tech/code/cow/run.sh)
|
||||
```
|
||||
|
||||
详细说明参考:[项目启动脚本](https://github.com/zhayujie/chatgpt-on-wechat/wiki/CowAgentQuickStart)
|
||||
|
||||
### 模型选择
|
||||
|
||||
Agent 模式推荐使用以下模型:
|
||||
|
||||
- **Claude**: `claude-sonnet-4-5`、`claude-sonnet-4-0`
|
||||
- **Gemini**: `gemini-3-flash-preview`、`gemini-3-pro-preview`
|
||||
- **GLM**: `glm-4.7`
|
||||
- **MiniMax**: `MiniMax-M2.1`
|
||||
- **Qwen**: `qwen3-max`
|
||||
|
||||
详细配置方式参考 [README.md 模型说明](../README.md#模型说明)
|
||||
|
||||
|
||||
### 渠道接入
|
||||
|
||||
支持在 Web、飞书、钉钉、企业微信 等多渠道与 Agent 交互,随时随地使用超级助理,只需修改 `config.json` 中的 `channel_type` 配置即可切换:
|
||||
|
||||
- **Web网页**:默认使用该渠道,运行后监听本地端口,通过浏览器访问。
|
||||
- **飞书接入**:[飞书接入文档](https://docs.link-ai.tech/cow/multi-platform/feishu)
|
||||
- **钉钉接入**:[钉钉接入文档](https://docs.link-ai.tech/cow/multi-platform/dingtalk)
|
||||
- **企业微信应用接入**:[企微应用文档](https://docs.link-ai.tech/cow/multi-platform/wechat-com)
|
||||
|
||||
更多渠道配置参考:[通道说明](../README.md#通道说明)
|
||||
|
||||
## 五、参与共建
|
||||
|
||||
2.0版本后,项目将持续升级Agent能力、拓展接入渠道、内置工具、技能系统,降低模型成本和提升安全性。欢迎 [提出反馈](https://github.com/zhayujie/chatgpt-on-wechat/issues) 和 [贡献代码](https://github.com/zhayujie/chatgpt-on-wechat/pulls)。
|
||||
|
||||
**🤖立即体验 CowAgent 2.0,开启你的超级AI助理之旅!**
|
||||
@@ -1,51 +0,0 @@
|
||||
## 更新日志
|
||||
|
||||
>**2025.05.23:** [1.7.6版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.6) 优化web网页channel、新增 [AgentMesh多智能体插件](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/agent/README.md)、百度语音合成优化、企微应用`access_token`获取优化、支持`claude-4-sonnet`和`claude-4-opus`模型
|
||||
|
||||
>**2025.04.11:** [1.7.5版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.5) 新增支持 [wechatferry](https://github.com/zhayujie/chatgpt-on-wechat/pull/2562) 协议、新增 deepseek 模型、新增支持腾讯云语音能力、新增支持 ModelScope 和 Gitee-AI API接口
|
||||
|
||||
>**2024.12.13:** [1.7.4版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.4) 新增 Gemini 2.0 模型、新增web channel、解决内存泄漏问题、解决 `#reloadp` 命令重载不生效问题
|
||||
|
||||
>**2024.10.31:** [1.7.3版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.3) 程序稳定性提升、数据库功能、Claude模型优化、linkai插件优化、离线通知
|
||||
|
||||
>**2024.09.26:** [1.7.2版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.2) 和 [1.7.1版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.1) 新增一键安装和管理脚本、文心,讯飞等模型优化、o1 模型
|
||||
|
||||
>**2024.08.02:** [1.7.0版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.7.0) 新增 讯飞4.0 模型、知识库引用来源展示、相关插件优化
|
||||
|
||||
>**2024.07.19:** [1.6.9版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.6.9) 新增 gpt-4o-mini 模型、阿里语音识别、企微应用渠道路由优化
|
||||
|
||||
>**2024.07.05:** [1.6.8版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.6.8) 和 [1.6.7版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.6.7),Claude3.5, Gemini 1.5 Pro, MiniMax模型、工作流图片输入、模型列表完善
|
||||
|
||||
>**2024.06.04:** [1.6.6版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.6.6) 和 [1.6.5版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.6.5),gpt-4o模型、钉钉流式卡片、讯飞语音识别/合成
|
||||
|
||||
>**2024.04.26:** [1.6.0版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.6.0),新增 Kimi 接入、gpt-4-turbo版本升级、文件总结和语音识别问题修复
|
||||
|
||||
>**2024.03.26:** [1.5.8版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.8) 和 [1.5.7版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.7),新增 GLM-4、Claude-3 模型,edge-tts 语音支持
|
||||
|
||||
>**2024.01.26:** [1.5.6版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.6) 和 [1.5.5版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.5),钉钉接入,tool插件升级,4-turbo模型更新
|
||||
|
||||
>**2023.11.11:** [1.5.3版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.3) 和 [1.5.4版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.4),新增通义千问模型、Google Gemini
|
||||
|
||||
>**2023.11.10:** [1.5.2版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.2),新增飞书通道、图像识别对话、黑名单配置
|
||||
|
||||
>**2023.11.10:** [1.5.0版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.0),新增 `gpt-4-turbo`, `dall-e-3`, `tts` 模型接入,完善图像理解&生成、语音识别&生成的多模态能力
|
||||
|
||||
>**2023.10.16:** 支持通过意图识别使用LinkAI联网搜索、数学计算、网页访问等插件,参考[插件文档](https://docs.link-ai.tech/platform/plugins)
|
||||
|
||||
>**2023.09.26:** 插件增加 文件/文章链接 一键总结和对话的功能,使用参考:[插件说明](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/linkai#3%E6%96%87%E6%A1%A3%E6%80%BB%E7%BB%93%E5%AF%B9%E8%AF%9D%E5%8A%9F%E8%83%BD)
|
||||
|
||||
>**2023.08.08:** 接入百度文心一言模型,通过 [插件](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/linkai) 支持 Midjourney 绘图
|
||||
|
||||
>**2023.06.12:** 接入 [LinkAI](https://link-ai.tech/console) 平台,可在线创建领域知识库,打造专属客服机器人。使用参考 [接入文档](https://link-ai.tech/platform/link-app/wechat)。
|
||||
|
||||
> **2023.04.26:** 支持企业微信应用号部署,兼容插件,并支持语音图片交互,私人助理理想选择,使用文档。(contributed by @lanvent in #944)
|
||||
|
||||
> **2023.04.05:** 支持微信公众号部署,兼容插件,并支持语音图片交互,使用文档。(contributed by @JS00000 in #686)
|
||||
|
||||
> **2023.04.05:** 增加能让ChatGPT使用工具的tool插件,使用文档。工具相关issue可反馈至chatgpt-tool-hub。(contributed by @goldfishh in #663)
|
||||
|
||||
> **2023.03.25:** 支持插件化开发,目前已实现 多角色切换、文字冒险游戏、管理员指令、Stable Diffusion等插件,使用参考 #578。(contributed by @lanvent in #565)
|
||||
|
||||
> **2023.03.09:** 基于 whisper API(后续已接入更多的语音API服务) 实现对语音消息的解析和回复,添加配置项 "speech_recognition":true 即可启用,使用参考 #415。(contributed by wanggang1987 in #385)
|
||||
|
||||
> **2022.12.12:** 项目框架搭建,首次接入ChatGPT模型
|
||||
24
docs/releases/overview.mdx
Normal file
24
docs/releases/overview.mdx
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: 更新日志
|
||||
description: CowAgent 版本更新历史
|
||||
---
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| [2.0.1](/releases/v2.0.1) | 2026.02.27 | 内置 Web Search 工具、智能上下文管理、多项修复 |
|
||||
| [2.0.0](/releases/v2.0.0) | 2026.02.03 | 全面升级为超级 Agent 助理 |
|
||||
| 1.7.6 | 2025.05.23 | Web Channel 优化、AgentMesh 多智能体插件 |
|
||||
| 1.7.5 | 2025.04.11 | DeepSeek 模型 |
|
||||
| 1.7.4 | 2024.12.13 | Gemini 2.0 模型、Web Channel |
|
||||
| 1.7.3 | 2024.10.31 | 稳定性提升、数据库功能 |
|
||||
| 1.7.2 | 2024.09.26 | 一键安装脚本、o1 模型 |
|
||||
| 1.7.0 | 2024.08.02 | 讯飞 4.0 模型、知识库引用 |
|
||||
| 1.6.9 | 2024.07.19 | gpt-4o-mini、阿里语音识别 |
|
||||
| 1.6.8 | 2024.07.05 | Claude 3.5、Gemini 1.5 Pro |
|
||||
| 1.6.0 | 2024.04.26 | Kimi 接入、gpt-4-turbo 升级 |
|
||||
| 1.5.8 | 2024.03.26 | GLM-4、Claude-3、edge-tts |
|
||||
| 1.5.2 | 2023.11.10 | 飞书通道、图像识别对话 |
|
||||
| 1.5.0 | 2023.11.10 | gpt-4-turbo、dall-e-3、tts 多模态 |
|
||||
| 1.0.0 | 2022.12.12 | 项目创建,首次接入 ChatGPT 模型 |
|
||||
|
||||
更多历史版本请查看 [GitHub Releases](https://github.com/zhayujie/chatgpt-on-wechat/releases)。
|
||||
105
docs/releases/v2.0.0.mdx
Normal file
105
docs/releases/v2.0.0.mdx
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: v2.0.0
|
||||
description: CowAgent 2.0 - 从聊天机器人到超级智能助理的全面升级
|
||||
---
|
||||
|
||||
CowAgent 2.0 实现了从聊天机器人到**超级智能助理**的全面升级!现在它能够主动思考和规划任务、拥有长期记忆、操作计算机和外部资源、创造和执行技能,真正理解你并和你一起成长。
|
||||
|
||||
**发布日期**:2026.02.03 | [GitHub Release](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/2.0.0)
|
||||
|
||||
## 重点更新
|
||||
|
||||
### Agent 核心能力
|
||||
|
||||
- **复杂任务规划**:能够理解复杂任务并自主规划执行,持续思考和调用工具直到完成目标,支持多轮推理和上下文理解
|
||||
- **长期记忆**:自动将对话记忆持久化至本地文件和数据库中,包括全局记忆和天级记忆,支持关键词及向量检索
|
||||
- **内置系统工具**:内置实现 10+ 种工具,包括文件操作、Bash 终端、浏览器、文件发送、定时任务、记忆管理等
|
||||
- **Skills**:新增 Skill 运行引擎,内置多种技能,并支持通过自然语言对话完成自定义 Skills 开发
|
||||
- **安全和成本**:通过秘钥管理工具、提示词控制、系统权限等手段控制 Agent 的访问安全;通过最大记忆轮次、最大上下文 token、工具执行步数对 token 成本进行限制
|
||||
|
||||
### 其他更新
|
||||
|
||||
- **渠道优化**:飞书及钉钉接入渠道支持长连接接入(无需公网 IP)、支持图片/文件消息的接收和发送
|
||||
- **模型更新**:新增 claude-sonnet-4-5、gemini-3-pro-preview、glm-4.7、MiniMax-M2.1、qwen3-max 等最新模型
|
||||
- **部署优化**:增加一键安装、配置、运行、管理的脚本,简化部署流程
|
||||
|
||||
## 长期记忆系统
|
||||
|
||||
Agent 会在用户分享重要信息时主动存储,也会在对话达到一定长度时自动提取摘要。支持语义搜索和向量检索的混合检索模式。
|
||||
|
||||
**首次启动**时,Agent 会主动询问关键信息,并记录至工作空间(默认 `~/cow`)中的智能体设定、用户身份、记忆文件中。
|
||||
|
||||
**长期对话**中,Agent 会智能记录或检索记忆,不断更新自身设定、用户偏好,总结经验和教训,真正实现自主思考和持续成长。
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203000455.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## 任务规划与工具调用
|
||||
|
||||
Agent 根据任务需求智能选择和调用工具,完成各类复杂操作。
|
||||
|
||||
### 终端和文件访问
|
||||
|
||||
最基础和核心的工具能力,用户可通过手机端与 Agent 交互,操作个人电脑或服务器上的资源:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202181130.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 应用编程能力
|
||||
|
||||
基于编程能力和系统访问能力,Agent 可实现从信息搜索、素材生成、编码、测试、部署、Nginx 配置、发布的 **Vibecoding 全流程**,通过手机端一句命令完成应用快速 demo。
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203121008.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 定时任务
|
||||
|
||||
支持 **一次性任务、固定时间间隔、Cron 表达式** 三种形式,任务触发可选择 **固定消息发送** 或 **Agent 动态任务执行** 两种模式:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202195402.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 环境变量管理
|
||||
|
||||
通过 `env_config` 工具管理技能所需秘钥,支持对话式更新,内置安全保护和脱敏策略:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234939.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
## 技能系统
|
||||
|
||||
每个 Skill 由说明文件、运行脚本(可选)、资源(可选)组成,为 Agent 提供无限扩展性。
|
||||
|
||||
### 技能创造器
|
||||
|
||||
通过对话方式快速创建技能,将工作流程固化或对接任意第三方接口:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202202247.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 网页搜索和图像识别
|
||||
|
||||
- **网页搜索**:内置 `web_search` 工具,支持多种搜索引擎,配置对应 API Key 即可使用
|
||||
- **图像识别**:支持 `gpt-4.1-mini`、`gpt-4.1` 等模型,配置 `OPENAI_API_KEY` 即可使用
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202213219.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
### 三方知识库和插件
|
||||
|
||||
`linkai-agent` 技能可将 [LinkAI](https://link-ai.tech/) 上的所有智能体作为 Skill 使用,实现多智能体决策:
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234350.png" width="750" />
|
||||
</Frame>
|
||||
|
||||
## 参与共建
|
||||
|
||||
2.0 版本后,项目将持续升级 Agent 能力、拓展接入渠道、内置工具、技能系统,降低模型成本和提升安全性。欢迎 [提出反馈](https://github.com/zhayujie/chatgpt-on-wechat/issues) 和 [贡献代码](https://github.com/zhayujie/chatgpt-on-wechat/pulls)。
|
||||
36
docs/releases/v2.0.1.mdx
Normal file
36
docs/releases/v2.0.1.mdx
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: v2.0.1
|
||||
description: CowAgent 2.0.1 - 内置 Web Search、智能上下文管理、多项修复
|
||||
---
|
||||
|
||||
**发布日期**:2026.02 | [Full Changelog](https://github.com/zhayujie/chatgpt-on-wechat/compare/2.0.0..2.0.1)
|
||||
|
||||
## 新特性
|
||||
|
||||
- **内置 Web Search 工具**:将网络搜索作为 Agent 内置工具集成,降低决策成本 ([4f0ea5d](https://github.com/zhayujie/chatgpt-on-wechat/commit/4f0ea5d7568d61db91ff69c91c429e785fd1b1c2))
|
||||
- **Claude Opus 4.6 模型支持**:新增对 Claude Opus 4.6 模型的支持 ([#2661](https://github.com/zhayujie/chatgpt-on-wechat/pull/2661))
|
||||
- **企业微信图片消息识别**:支持企业微信渠道的图片消息识别功能 ([#2667](https://github.com/zhayujie/chatgpt-on-wechat/pull/2667))
|
||||
|
||||
## 优化
|
||||
|
||||
- **智能上下文管理**:解决聊天上下文溢出问题,新增智能上下文裁剪策略,防止 token 超限 ([cea7fb7](https://github.com/zhayujie/chatgpt-on-wechat/commit/cea7fb7490c53454602bf05955a0e9f059bcf0fd), [8acf2db](https://github.com/zhayujie/chatgpt-on-wechat/commit/8acf2dbdfe713b84ad74b761b7f86674b1c1904d)) [#2663](https://github.com/zhayujie/chatgpt-on-wechat/issues/2663)
|
||||
- **运行时信息动态更新**:通过动态函数方案实现系统提示词中时间戳等运行时信息的自动更新 ([#2655](https://github.com/zhayujie/chatgpt-on-wechat/pull/2655), [#2657](https://github.com/zhayujie/chatgpt-on-wechat/pull/2657))
|
||||
- **Skill 提示词优化**:改进 Skill 系统提示词生成逻辑,简化工具描述,提升 Agent 表现 ([6c21833](https://github.com/zhayujie/chatgpt-on-wechat/commit/6c218331b1f1208ea8be6bf226936d3b556ade3e))
|
||||
- **智谱 AI 自定义 API Base URL**:支持智谱 AI 配置自定义 API Base URL ([#2660](https://github.com/zhayujie/chatgpt-on-wechat/pull/2660))
|
||||
- **启动脚本优化**:改进 `run.sh` 脚本的交互体验和配置流程 ([#2656](https://github.com/zhayujie/chatgpt-on-wechat/pull/2656))
|
||||
- **决策轮次日志**:新增 Agent 决策轮次的日志记录,便于调试 ([cb303e6](https://github.com/zhayujie/chatgpt-on-wechat/commit/cb303e6109c50c8dfef1f5e6c1ec47223bf3cd11))
|
||||
|
||||
## 问题修复
|
||||
|
||||
- **定时任务记忆丢失**:修复 Scheduler 调度器导致的记忆丢失问题 ([a77a874](https://github.com/zhayujie/chatgpt-on-wechat/commit/a77a8741b500a408c6f5c8868856fb4b018fe9db))
|
||||
- **空工具调用与超长结果**:修复空 tool calls 及过长工具返回结果的异常处理 ([0542700](https://github.com/zhayujie/chatgpt-on-wechat/commit/0542700f9091ebb08c1a56103b0f0f45f24aa621))
|
||||
- **OpenAI Function Call**:修复 OpenAI 模型的 function call 调用兼容性问题 ([158c87a](https://github.com/zhayujie/chatgpt-on-wechat/commit/158c87ab8b05bae054cc1b4eacdbb64fc1062ba9))
|
||||
- **Claude 工具名字段**:移除 Claude 模型响应中多余的 tool name 字段 ([eec10cb](https://github.com/zhayujie/chatgpt-on-wechat/commit/eec10cb5db6a3d5bc12ef606606532237d2c5f6e))
|
||||
- **MiniMax 推理优化**:优化 MiniMax 模型 reasoning content 处理,隐藏思考过程输出 ([c72cda3](https://github.com/zhayujie/chatgpt-on-wechat/commit/c72cda33864bd1542012ee6e0a8bd8c6c88cb5ed), [72b1cac](https://github.com/zhayujie/chatgpt-on-wechat/commit/72b1cacea1ba0d1f3dedacbab2e088e98fd7e172))
|
||||
- **智谱 AI 思考过程**:隐藏智谱 AI 模型的思考过程展示 ([72b1cac](https://github.com/zhayujie/chatgpt-on-wechat/commit/72b1cacea1ba0d1f3dedacbab2e088e98fd7e172))
|
||||
- **飞书连接与证书**:修复飞书渠道的 SSL 证书错误和连接异常问题 ([229b14b](https://github.com/zhayujie/chatgpt-on-wechat/commit/229b14b6fcabe7123d53cab1dea39f38dab26d6d), [8674421](https://github.com/zhayujie/chatgpt-on-wechat/commit/867442155e7f095b4f38b0856f8c1d8312b5fcf7))
|
||||
- **model_type 类型校验**:修复非字符串 `model_type` 导致的 `AttributeError` ([#2666](https://github.com/zhayujie/chatgpt-on-wechat/pull/2666))
|
||||
|
||||
## 平台兼容
|
||||
|
||||
- **Windows 兼容性适配**:修复 Windows 平台下路径处理、文件编码及 `os.getuid()` 不可用等问题,涉及多个工具模块 ([051ffd7](https://github.com/zhayujie/chatgpt-on-wechat/commit/051ffd78a372f71a967fd3259e37fe19131f83cf), [5264f7c](https://github.com/zhayujie/chatgpt-on-wechat/commit/5264f7ce18360ee4db5dcb4ebe67307977d40014))
|
||||
33
docs/skills/image-vision.mdx
Normal file
33
docs/skills/image-vision.mdx
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: 图像识别
|
||||
description: 使用 OpenAI 视觉模型识别图片
|
||||
---
|
||||
|
||||
# openai-image-vision
|
||||
|
||||
使用 OpenAI 的 GPT-4 Vision API 分析图片内容,理解图像中的物体、文字、颜色等元素。
|
||||
|
||||
## 依赖
|
||||
|
||||
| 依赖 | 说明 |
|
||||
| --- | --- |
|
||||
| `OPENAI_API_KEY` | OpenAI API 密钥 |
|
||||
| `curl`、`base64` | 系统命令(通常已预装) |
|
||||
|
||||
配置方式:
|
||||
|
||||
- 通过 `env_config` 工具配置 `OPENAI_API_KEY`
|
||||
- 或在 `config.json` 中填写 `open_ai_api_key`
|
||||
|
||||
## 支持的模型
|
||||
|
||||
- `gpt-4.1-mini`(推荐,性价比高)
|
||||
- `gpt-4.1`
|
||||
|
||||
## 使用方式
|
||||
|
||||
配置完成后,向 Agent 发送图片即可自动触发图像识别。
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202213219.png" width="800" />
|
||||
</Frame>
|
||||
67
docs/skills/index.mdx
Normal file
67
docs/skills/index.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: 技能概览
|
||||
description: CowAgent 技能系统介绍
|
||||
---
|
||||
|
||||
技能(Skill)为 Agent 提供无限的扩展性。每个 Skill 由说明文件(`SKILL.md`)、运行脚本(可选)、资源(可选)组成,描述如何完成特定类型的任务。
|
||||
|
||||
Skill 与 Tool 的区别:Tool 是由代码实现的原子操作(如读写文件、执行命令),Skill 则是基于说明文件的高级工作流,可以组合调用多个 Tool 来完成复杂任务。
|
||||
|
||||
## 内置技能
|
||||
|
||||
位于项目 `skills/` 目录下,根据依赖条件自动判断是否启用:
|
||||
|
||||
| 技能 | 说明 | 依赖 |
|
||||
| --- | --- | --- |
|
||||
| [`skill-creator`](/skills/skill-creator) | 通过对话创建自定义技能 | 无 |
|
||||
| [`openai-image-vision`](/skills/image-vision) | 使用 OpenAI 视觉模型识别图片 | `OPENAI_API_KEY` |
|
||||
| [`linkai-agent`](/skills/linkai-agent) | 对接 LinkAI 平台智能体 | `LINKAI_API_KEY` |
|
||||
| [`web-fetch`](/skills/web-fetch) | 抓取网页文本内容 | `curl`(默认启用) |
|
||||
|
||||
## 自定义技能
|
||||
|
||||
由用户通过对话创建,存放在工作空间中(`~/cow/skills/`),可实现任何复杂的业务流程和第三方系统对接。
|
||||
|
||||
## 技能加载优先级
|
||||
|
||||
1. **工作空间技能**(最高):`~/cow/skills/`
|
||||
2. **项目内置技能**(最低):`skills/`
|
||||
|
||||
同名技能按优先级覆盖。
|
||||
|
||||
## 技能文件结构
|
||||
|
||||
```
|
||||
skills/
|
||||
├── my-skill/
|
||||
│ ├── SKILL.md # Skill description (frontmatter + instructions)
|
||||
│ ├── scripts/ # Execution scripts (optional)
|
||||
│ └── resources/ # Additional resources (optional)
|
||||
```
|
||||
|
||||
### SKILL.md 格式
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Brief description of the skill
|
||||
metadata:
|
||||
emoji: 🔧
|
||||
requires:
|
||||
bins: ["curl"]
|
||||
env: ["MY_API_KEY"]
|
||||
primaryEnv: "MY_API_KEY"
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
Detailed instructions...
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `name` | 技能名称,需与目录名一致 |
|
||||
| `description` | 技能描述,Agent 据此决定是否调用 |
|
||||
| `metadata.requires.bins` | 依赖的系统命令 |
|
||||
| `metadata.requires.env` | 依赖的环境变量 |
|
||||
| `metadata.always` | 是否始终加载(默认 false) |
|
||||
49
docs/skills/linkai-agent.mdx
Normal file
49
docs/skills/linkai-agent.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: LinkAI 智能体
|
||||
description: 对接 LinkAI 平台的多智能体技能
|
||||
---
|
||||
|
||||
# linkai-agent
|
||||
|
||||
将 [LinkAI](https://link-ai.tech/) 平台上的智能体作为 Skill 使用,实现多智能体决策。Agent 根据智能体的名称和描述智能选择,通过 `app_code` 调用对应的应用或工作流。
|
||||
|
||||
## 依赖
|
||||
|
||||
| 依赖 | 说明 |
|
||||
| --- | --- |
|
||||
| `LINKAI_API_KEY` | LinkAI 平台 API 密钥,在 [控制台](https://link-ai.tech/console/interface) 创建 |
|
||||
| `curl` | 系统命令(通常已预装) |
|
||||
|
||||
配置方式:
|
||||
|
||||
- 通过 `env_config` 工具配置 `LINKAI_API_KEY`
|
||||
- 或在 `config.json` 中填写 `linkai_api_key`
|
||||
|
||||
## 配置智能体
|
||||
|
||||
在 `skills/linkai-agent/config.json` 中添加可用的智能体:
|
||||
|
||||
```json
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"app_code": "G7z6vKwp",
|
||||
"app_name": "LinkAI客服助手",
|
||||
"app_description": "当用户需要了解LinkAI平台相关问题时才选择该助手"
|
||||
},
|
||||
{
|
||||
"app_code": "SFY5x7JR",
|
||||
"app_name": "内容创作助手",
|
||||
"app_description": "当用户需要创作图片或视频时才使用该助手"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
配置完成后,Agent 会根据用户的问题自动选择合适的 LinkAI 智能体进行回答。
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234350.png" width="750" />
|
||||
</Frame>
|
||||
33
docs/skills/skill-creator.mdx
Normal file
33
docs/skills/skill-creator.mdx
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: 创建技能
|
||||
description: 通过对话创建自定义技能
|
||||
---
|
||||
|
||||
# skill-creator
|
||||
|
||||
通过自然语言对话快速创建、安装或更新技能。
|
||||
|
||||
## 依赖
|
||||
|
||||
无额外依赖,始终可用。
|
||||
|
||||
## 使用方式
|
||||
|
||||
- 将工作流程固化为技能:"帮我把这个部署流程创建为一个技能"
|
||||
- 对接第三方 API:"根据这个接口文档创建一个技能"
|
||||
- 安装远程技能:"帮我安装 xxx 技能"
|
||||
|
||||
## 创建流程
|
||||
|
||||
1. 告诉 Agent 你想创建的技能功能
|
||||
2. Agent 自动生成 `SKILL.md` 说明文件和运行脚本
|
||||
3. 技能保存到工作空间的 `~/cow/skills/` 目录
|
||||
4. 后续对话中 Agent 会自动识别并使用该技能
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202202247.png" width="800" />
|
||||
</Frame>
|
||||
|
||||
<Tip>
|
||||
详细开发文档可参考 [Skill 创造器说明](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/skills/skill-creator/SKILL.md)。
|
||||
</Tip>
|
||||
33
docs/skills/web-fetch.mdx
Normal file
33
docs/skills/web-fetch.mdx
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: 网页抓取
|
||||
description: 抓取网页文本内容
|
||||
---
|
||||
|
||||
# web-fetch
|
||||
|
||||
使用 curl 抓取网页并提取可读文本内容,轻量级的网页访问方式,无需浏览器自动化。
|
||||
|
||||
## 依赖
|
||||
|
||||
| 依赖 | 说明 |
|
||||
| --- | --- |
|
||||
| `curl` | 系统命令(通常已预装) |
|
||||
|
||||
该技能设置了 `always: true`,只要系统有 `curl` 命令即默认启用。
|
||||
|
||||
## 使用方式
|
||||
|
||||
当 Agent 需要获取某个 URL 的网页内容时会自动调用,无需额外配置。
|
||||
|
||||
## 与 browser 工具的区别
|
||||
|
||||
| 特性 | web-fetch(技能) | browser(工具) |
|
||||
| --- | --- | --- |
|
||||
| 依赖 | 仅 curl | browser-use + playwright |
|
||||
| JS 渲染 | 不支持 | 支持 |
|
||||
| 页面交互 | 不支持 | 支持点击、输入等 |
|
||||
| 适用场景 | 获取静态页面文本 | 操作动态网页 |
|
||||
|
||||
<Tip>
|
||||
对于大多数网页内容获取场景,web-fetch 就够用了。只有需要 JS 渲染或页面交互时才需要 browser 工具。
|
||||
</Tip>
|
||||
30
docs/tools/bash.mdx
Normal file
30
docs/tools/bash.mdx
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: bash - 终端
|
||||
description: 执行系统命令
|
||||
---
|
||||
|
||||
# bash
|
||||
|
||||
在当前工作目录执行 Bash 命令,返回 stdout 和 stderr。`env_config` 中配置的 API Key 会自动注入到环境变量中。
|
||||
|
||||
## 依赖
|
||||
|
||||
无额外依赖,默认可用。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `command` | string | 是 | 要执行的命令 |
|
||||
| `timeout` | integer | 否 | 超时时间(秒) |
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 安装软件包和依赖
|
||||
- 运行代码和测试
|
||||
- 部署应用和服务(Nginx 配置、进程管理等)
|
||||
- 系统运维和排查
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260203121008.png" width="800" />
|
||||
</Frame>
|
||||
27
docs/tools/browser.mdx
Normal file
27
docs/tools/browser.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: browser - 浏览器
|
||||
description: 访问和操作网页
|
||||
---
|
||||
|
||||
# browser
|
||||
|
||||
使用浏览器访问和操作网页,支持 JavaScript 渲染的动态页面。
|
||||
|
||||
## 依赖
|
||||
|
||||
| 依赖 | 安装命令 |
|
||||
| --- | --- |
|
||||
| `browser-use` ≥ 0.1.40 | `pip install browser-use` |
|
||||
| `markdownify` | `pip install markdownify` |
|
||||
| `playwright` + chromium | `pip install playwright && playwright install chromium` |
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 访问指定 URL 获取页面内容
|
||||
- 操作网页元素(点击、输入等)
|
||||
- 验证部署后的网页效果
|
||||
- 抓取需要 JS 渲染的动态内容
|
||||
|
||||
<Note>
|
||||
浏览器工具依赖较重,如不需要可不安装。轻量的网页内容获取可使用 `web-fetch` 技能。
|
||||
</Note>
|
||||
26
docs/tools/edit.mdx
Normal file
26
docs/tools/edit.mdx
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: edit - 文件编辑
|
||||
description: 通过精确文本替换编辑文件
|
||||
---
|
||||
|
||||
# edit
|
||||
|
||||
通过精确文本替换编辑文件。如果 `oldText` 为空则追加到文件末尾。
|
||||
|
||||
## 依赖
|
||||
|
||||
无额外依赖,默认可用。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | 是 | 文件路径 |
|
||||
| `oldText` | string | 是 | 要替换的原始文本(为空时追加到末尾) |
|
||||
| `newText` | string | 是 | 替换后的文本 |
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 修改配置文件中的特定参数
|
||||
- 修复代码中的 bug
|
||||
- 在文件指定位置插入内容
|
||||
38
docs/tools/env-config.mdx
Normal file
38
docs/tools/env-config.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: env_config - 环境变量
|
||||
description: 管理 API Key 等秘钥配置
|
||||
---
|
||||
|
||||
# env_config
|
||||
|
||||
管理工作空间 `.env` 文件中的环境变量(API Key 等秘钥),支持通过对话安全地添加和更新。内置安全保护和脱敏策略。
|
||||
|
||||
## 依赖
|
||||
|
||||
| 依赖 | 安装命令 |
|
||||
| --- | --- |
|
||||
| `python-dotenv` ≥ 1.0.0 | `pip install python-dotenv>=1.0.0` |
|
||||
|
||||
安装扩展依赖时已包含:`pip3 install -r requirements-optional.txt`
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `action` | string | 是 | 操作类型:`get`、`set`、`list`、`delete` |
|
||||
| `key` | string | 否 | 环境变量名称 |
|
||||
| `value` | string | 否 | 环境变量值(仅 `set` 时需要) |
|
||||
|
||||
## 使用方式
|
||||
|
||||
直接告诉 Agent 需要配置的秘钥,Agent 会自动调用该工具:
|
||||
|
||||
- "帮我配置 BOCHA_API_KEY"
|
||||
- "设置 OPENAI_API_KEY 为 sk-xxx"
|
||||
- "查看已配置的环境变量"
|
||||
|
||||
配置的秘钥会自动注入到 `bash` 工具的执行环境中。
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202234939.png" width="800" />
|
||||
</Frame>
|
||||
50
docs/tools/index.mdx
Normal file
50
docs/tools/index.mdx
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: 工具概览
|
||||
description: CowAgent 内置工具系统
|
||||
---
|
||||
|
||||
工具是 Agent 访问操作系统资源的核心能力。Agent 会根据任务需求智能选择和调用工具,完成文件操作、命令执行、联网搜索、定时任务等各类操作。工具实现在项目的 `agent/tools/` 目录下。
|
||||
|
||||
## 内置工具
|
||||
|
||||
以下工具默认可用,无需额外配置:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="read - 文件读取" icon="file" href="/tools/read">
|
||||
读取文件内容,支持文本、图片、PDF
|
||||
</Card>
|
||||
<Card title="write - 文件写入" icon="pen" href="/tools/write">
|
||||
创建或覆盖写入文件
|
||||
</Card>
|
||||
<Card title="edit - 文件编辑" icon="pen-to-square" href="/tools/edit">
|
||||
通过精确文本替换编辑文件
|
||||
</Card>
|
||||
<Card title="ls - 目录列表" icon="folder-open" href="/tools/ls">
|
||||
列出目录内容
|
||||
</Card>
|
||||
<Card title="bash - 终端" icon="terminal" href="/tools/bash">
|
||||
执行系统命令
|
||||
</Card>
|
||||
<Card title="send - 文件发送" icon="paper-plane" href="/tools/send">
|
||||
向用户发送文件或图片
|
||||
</Card>
|
||||
<Card title="memory - 记忆" icon="brain" href="/tools/memory">
|
||||
搜索和读取长期记忆
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## 可选工具
|
||||
|
||||
以下工具需要安装额外依赖或配置 API Key 后启用:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="env_config - 环境变量" icon="key" href="/tools/env-config">
|
||||
管理 API Key 等秘钥配置
|
||||
</Card>
|
||||
<Card title="scheduler - 定时任务" icon="clock" href="/tools/scheduler">
|
||||
创建和管理定时任务
|
||||
</Card>
|
||||
<Card title="web_search - 联网搜索" icon="magnifying-glass" href="/tools/web-search">
|
||||
搜索互联网获取实时信息
|
||||
</Card>
|
||||
</CardGroup>
|
||||
25
docs/tools/ls.mdx
Normal file
25
docs/tools/ls.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: ls - 目录列表
|
||||
description: 列出目录内容
|
||||
---
|
||||
|
||||
# ls
|
||||
|
||||
列出目录内容,按字母排序,目录名带 `/` 后缀,包含隐藏文件。
|
||||
|
||||
## 依赖
|
||||
|
||||
无额外依赖,默认可用。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | 是 | 目录路径,相对路径基于工作空间目录 |
|
||||
| `limit` | integer | 否 | 最大返回条目数,默认 500 |
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 浏览项目结构
|
||||
- 查找特定文件
|
||||
- 检查目录是否存在
|
||||
38
docs/tools/memory.mdx
Normal file
38
docs/tools/memory.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: memory - 记忆
|
||||
description: 搜索和读取长期记忆
|
||||
---
|
||||
|
||||
# memory
|
||||
|
||||
记忆工具包含两个子工具:`memory_search`(搜索记忆)和 `memory_get`(读取记忆文件)。
|
||||
|
||||
## 依赖
|
||||
|
||||
无额外依赖,默认可用。由 Agent Core 的记忆系统管理。
|
||||
|
||||
## memory_search
|
||||
|
||||
搜索历史记忆,支持关键词和向量混合检索。
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `query` | string | 是 | 搜索查询 |
|
||||
|
||||
## memory_get
|
||||
|
||||
读取特定记忆文件的内容。
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | 是 | 记忆文件的相对路径(如 `MEMORY.md`、`memory/2026-01-01.md`) |
|
||||
| `start_line` | integer | 否 | 起始行号 |
|
||||
| `end_line` | integer | 否 | 结束行号 |
|
||||
|
||||
## 工作方式
|
||||
|
||||
Agent 会在以下场景自动调用记忆工具:
|
||||
|
||||
- 用户分享重要信息时 → 存储到记忆
|
||||
- 需要参考历史信息时 → 搜索相关记忆
|
||||
- 对话达到一定长度时 → 提取摘要存储
|
||||
26
docs/tools/read.mdx
Normal file
26
docs/tools/read.mdx
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: read - 文件读取
|
||||
description: 读取文件内容
|
||||
---
|
||||
|
||||
# read
|
||||
|
||||
读取文件内容。支持文本文件、PDF 文件、图片(返回元数据)等格式。
|
||||
|
||||
## 依赖
|
||||
|
||||
无额外依赖,默认可用。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | 是 | 文件路径,相对路径基于工作空间目录 |
|
||||
| `offset` | integer | 否 | 起始行号(1-indexed),负值表示从末尾读取 |
|
||||
| `limit` | integer | 否 | 读取行数 |
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 查看配置文件、日志文件
|
||||
- 读取代码文件进行分析
|
||||
- 检查图片/视频的文件信息
|
||||
42
docs/tools/scheduler.mdx
Normal file
42
docs/tools/scheduler.mdx
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: scheduler - 定时任务
|
||||
description: 创建和管理定时任务
|
||||
---
|
||||
|
||||
# scheduler
|
||||
|
||||
创建和管理动态定时任务,支持灵活的调度方式和执行模式。
|
||||
|
||||
## 依赖
|
||||
|
||||
| 依赖 | 安装命令 |
|
||||
| --- | --- |
|
||||
| `croniter` ≥ 2.0.0 | `pip install croniter>=2.0.0` |
|
||||
|
||||
安装核心依赖时已包含:`pip3 install -r requirements.txt`
|
||||
|
||||
## 调度方式
|
||||
|
||||
| 方式 | 说明 |
|
||||
| --- | --- |
|
||||
| 一次性任务 | 在指定时间执行一次 |
|
||||
| 固定间隔 | 按固定时间间隔重复执行 |
|
||||
| Cron 表达式 | 使用 Cron 语法定义复杂调度规则 |
|
||||
|
||||
## 执行模式
|
||||
|
||||
- **固定消息发送**:到达触发时间时发送预设消息
|
||||
- **Agent 动态任务**:到达触发时间时由 Agent 智能执行任务
|
||||
|
||||
## 使用方式
|
||||
|
||||
通过自然语言即可创建和管理定时任务:
|
||||
|
||||
- "每天早上 9 点给我发天气预报"
|
||||
- "每隔 2 小时检查一下服务器状态"
|
||||
- "明天下午 3 点提醒我开会"
|
||||
- "查看所有定时任务"
|
||||
|
||||
<Frame>
|
||||
<img src="https://cdn.link-ai.tech/doc/20260202195402.png" width="800" />
|
||||
</Frame>
|
||||
25
docs/tools/send.mdx
Normal file
25
docs/tools/send.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: send - 文件发送
|
||||
description: 向用户发送文件
|
||||
---
|
||||
|
||||
# send
|
||||
|
||||
向用户发送文件(图片、视频、音频、文档等),当用户明确要求发送/分享文件时使用。
|
||||
|
||||
## 依赖
|
||||
|
||||
无额外依赖,默认可用。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | 是 | 文件路径,可以是绝对路径或相对于工作空间的路径 |
|
||||
| `message` | string | 否 | 附带的消息说明 |
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 将生成的代码或文档发送给用户
|
||||
- 发送截图、图表
|
||||
- 分享下载的文件
|
||||
34
docs/tools/web-search.mdx
Normal file
34
docs/tools/web-search.mdx
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: web_search - 联网搜索
|
||||
description: 搜索互联网获取实时信息
|
||||
---
|
||||
|
||||
# web_search
|
||||
|
||||
搜索互联网获取实时信息、新闻、研究等内容。支持两个搜索后端,自动选择可用的后端。
|
||||
|
||||
## 依赖
|
||||
|
||||
需要配置至少一个搜索 API Key(通过 `env_config` 工具或工作空间 `.env` 文件配置):
|
||||
|
||||
| 后端 | 环境变量 | 优先级 | 获取方式 |
|
||||
| --- | --- | --- | --- |
|
||||
| 博查搜索 | `BOCHA_API_KEY` | 优先使用 | [博查开放平台](https://open.bochaai.com/) |
|
||||
| LinkAI 搜索 | `LINKAI_API_KEY` | 可选 | [LinkAI 控制台](https://link-ai.tech/console/interface) |
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `query` | string | 是 | 搜索关键词 |
|
||||
| `count` | integer | 否 | 返回结果数量(1-50,默认 10) |
|
||||
| `freshness` | string | 否 | 时间范围:`noLimit`、`oneDay`、`oneWeek`、`oneMonth`、`oneYear`,或日期范围如 `2025-01-01..2025-02-01` |
|
||||
| `summary` | boolean | 否 | 是否返回页面摘要(默认 false) |
|
||||
|
||||
## 使用场景
|
||||
|
||||
当用户询问最新信息、需要事实核查或获取实时数据时,Agent 会自动调用此工具。
|
||||
|
||||
<Note>
|
||||
如果未配置任何搜索 API Key,该工具不会被加载。
|
||||
</Note>
|
||||
29
docs/tools/write.mdx
Normal file
29
docs/tools/write.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: write - 文件写入
|
||||
description: 创建或覆盖写入文件
|
||||
---
|
||||
|
||||
# write
|
||||
|
||||
写入内容到文件。文件不存在则自动创建,已存在则覆盖。自动创建父目录。
|
||||
|
||||
## 依赖
|
||||
|
||||
无额外依赖,默认可用。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `path` | string | 是 | 文件路径 |
|
||||
| `content` | string | 是 | 要写入的内容 |
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 创建新的代码文件或脚本
|
||||
- 生成配置文件
|
||||
- 保存处理结果
|
||||
|
||||
<Note>
|
||||
单次写入不应超过 10KB。对于大文件,建议先创建骨架,再使用 edit 工具分块添加内容。
|
||||
</Note>
|
||||
Reference in New Issue
Block a user