mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Implement IM adapters allowing users to chat with Claude Code from Telegram and Feishu/Lark. Includes persistent session management (chatId→sessionId mapping), project selection via /projects command, and a web UI settings page for configuring bot tokens, allowed users, and default project directory. Key changes: - adapters/: Telegram and Feishu adapter scripts with shared common modules (WsBridge, MessageBuffer, SessionStore, HttpClient, config, formatting) - Backend: adapterService + REST API (GET/PUT /api/adapters) with secret masking - Frontend: AdapterSettings page in Settings tab with i18n support - DirectoryPicker: use React Portal for dropdown to fix overflow clipping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
60 lines
1.9 KiB
TypeScript
60 lines
1.9 KiB
TypeScript
/**
|
|
* 消息格式化工具
|
|
*/
|
|
|
|
/** Split text into chunks that fit within a character limit, respecting paragraph/sentence boundaries. */
|
|
export function splitMessage(text: string, limit: number): string[] {
|
|
if (text.length <= limit) return [text]
|
|
|
|
const chunks: string[] = []
|
|
let remaining = text
|
|
|
|
while (remaining.length > 0) {
|
|
if (remaining.length <= limit) {
|
|
chunks.push(remaining)
|
|
break
|
|
}
|
|
|
|
let splitAt = remaining.lastIndexOf('\n\n', limit)
|
|
if (splitAt <= 0) splitAt = remaining.lastIndexOf('\n', limit)
|
|
if (splitAt <= 0) splitAt = remaining.lastIndexOf('. ', limit)
|
|
if (splitAt <= 0) splitAt = remaining.lastIndexOf(' ', limit)
|
|
if (splitAt <= 0) splitAt = limit
|
|
|
|
// Include the delimiter for paragraph/sentence breaks
|
|
if (remaining[splitAt] === '\n' || remaining[splitAt] === '.') splitAt += 1
|
|
|
|
chunks.push(remaining.slice(0, splitAt).trimEnd())
|
|
remaining = remaining.slice(splitAt).trimStart()
|
|
}
|
|
|
|
return chunks
|
|
}
|
|
|
|
/** Format tool use info for display in IM. */
|
|
export function formatToolUse(toolName: string, input: unknown): string {
|
|
const preview = truncateInput(input, 200)
|
|
return `🔧 ${toolName}\n${preview}`
|
|
}
|
|
|
|
/** Format a permission request for display in IM. */
|
|
export function formatPermissionRequest(toolName: string, input: unknown, requestId: string): string {
|
|
const preview = truncateInput(input, 300)
|
|
return `🔐 需要权限确认 [${requestId}]\n工具: ${toolName}\n${preview}`
|
|
}
|
|
|
|
/** Truncate tool input to a preview string. */
|
|
export function truncateInput(input: unknown, maxLen: number): string {
|
|
try {
|
|
const s = typeof input === 'string' ? input : JSON.stringify(input, null, 2)
|
|
return s.length > maxLen ? s.slice(0, maxLen) + '…' : s
|
|
} catch {
|
|
return '(unserializable)'
|
|
}
|
|
}
|
|
|
|
/** Escape special characters for Telegram MarkdownV2. */
|
|
export function escapeMarkdownV2(text: string): string {
|
|
return text.replace(/([_*\[\]()~`>#+\-=|{}.!\\])/g, '\\$1')
|
|
}
|