├── README.md └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # Sider2API 2 | 3 | 一个用 Go 语言编写的现代化 API 服务。 4 | 5 | ## 项目描述 6 | 7 | Sider2API 是一个基于 Go 语言开发的高性能 API 服务框架。该项目旨在提供一个简单、高效、可扩展的 API 开发解决方案。 8 | 9 | ## 功能特点 10 | 11 | - 高性能的 Go 语言实现 12 | - RESTful API 设计 13 | - 简单易用的配置系统 14 | - 完善的错误处理机制 15 | - 内置日志系统 16 | 17 | ## 安装要求 18 | 19 | - Go 1.16 或更高版本 20 | - 其他依赖将通过 Go modules 自动安装 21 | 22 | ## 快速开始 23 | 24 | 1. 克隆项目 25 | ```bash 26 | git clone https://github.com/yourusername/sider2api.git 27 | cd sider2api 28 | ``` 29 | 30 | 2. 安装依赖 31 | ```bash 32 | go mod tidy 33 | ``` 34 | 35 | 3. 运行服务 36 | ```bash 37 | go run main.go 38 | ``` 39 | 40 | ## 配置说明 41 | 42 | 项目配置文件位于 `config` 目录下,支持以下配置项: 43 | 44 | - 服务端口 45 | - 数据库连接 46 | - 日志级别 47 | - 其他自定义配置 48 | 49 | ## API 文档 50 | 51 | API 文档正在完善中,将很快提供详细的接口说明。 52 | 53 | ## 贡献指南 54 | 55 | 欢迎提交 Pull Request 或创建 Issue! 56 | 57 | ## 许可证 58 | 59 | 本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件 60 | 61 | ## 联系方式 62 | 63 | 如有任何问题或建议,请通过 Issue 与我们联系。 -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // 用户请求的结构 15 | type UserRequest struct { 16 | Messages []Message `json:"messages"` 17 | Model string `json:"model"` 18 | Stream bool `json:"stream"` 19 | MaxTokens int `json:"max_tokens"` 20 | } 21 | 22 | type Message struct { 23 | Role string `json:"role"` 24 | Content string `json:"content"` 25 | } 26 | 27 | var defaultJsonTemplate = []byte(`{ 28 | "app_name": "ChitChat_Edge_Ext", 29 | "app_version": "4.40.0", 30 | "tz_name": "Asia/Shanghai", 31 | "cid": "", 32 | "search": false, 33 | "auto_search": false, 34 | "filter_search_history": false, 35 | "from": "chat", 36 | "group_id": "default", 37 | "chat_models": [], 38 | "files": [], 39 | "prompt_templates": [ 40 | {"key": "artifacts", "attributes": {"lang": "original"}}, 41 | {"key": "thinking_mode", "attributes": {}} 42 | ], 43 | "tools": { 44 | "auto": ["search", "text_to_image", "data_analysis"] 45 | }, 46 | "extra_info": { 47 | "origin_url": "chrome-extension://dhoenijjpgpeimemopealfcbiecgceod/standalone.html?from=sidebar", 48 | "origin_title": "Sider" 49 | }, 50 | "branch": true 51 | }`) 52 | 53 | // Sider响应结构 54 | type SiderResponse struct { 55 | Code int `json:"code"` 56 | Msg string `json:"msg"` 57 | Data struct { 58 | Type string `json:"type"` 59 | Text string `json:"text"` 60 | ChatModel string `json:"chat_model"` 61 | } `json:"data"` 62 | } 63 | 64 | // OpenAI响应结构 65 | type OpenAIResponse struct { 66 | ID string `json:"id"` 67 | Object string `json:"object"` 68 | Created int64 `json:"created"` 69 | Model string `json:"model"` 70 | Choices []struct { 71 | Message struct { 72 | Role string `json:"role"` 73 | Content string `json:"content"` 74 | } `json:"message"` 75 | FinishReason string `json:"finish_reason"` 76 | Index int `json:"index"` 77 | } `json:"choices"` 78 | Usage struct { 79 | PromptTokens int `json:"prompt_tokens"` 80 | CompletionTokens int `json:"completion_tokens"` 81 | TotalTokens int `json:"total_tokens"` 82 | } `json:"usage"` 83 | } 84 | 85 | // OpenAI流式响应结构 86 | type OpenAIStreamResponse struct { 87 | ID string `json:"id"` 88 | Object string `json:"object"` 89 | Created int64 `json:"created"` 90 | Model string `json:"model"` 91 | Choices []struct { 92 | Delta struct { 93 | Content string `json:"content"` 94 | } `json:"delta"` 95 | FinishReason string `json:"finish_reason"` 96 | Index int `json:"index"` 97 | } `json:"choices"` 98 | } 99 | 100 | func handleOptions(w http.ResponseWriter, r *http.Request) { 101 | w.Header().Set("Access-Control-Allow-Origin", "*") 102 | w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") 103 | w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") 104 | w.WriteHeader(http.StatusOK) 105 | } 106 | 107 | func forwardToSider(w http.ResponseWriter, r *http.Request) { 108 | fmt.Printf("收到新请求: %s %s\n", r.Method, r.URL.Path) 109 | 110 | // 设置CORS头 111 | w.Header().Set("Access-Control-Allow-Origin", "*") 112 | w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") 113 | w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") 114 | 115 | // 读取请求体 116 | body, err := io.ReadAll(r.Body) 117 | if err != nil { 118 | fmt.Printf("读取请求体失败: %v\n", err) 119 | http.Error(w, "读取请求失败", http.StatusBadRequest) 120 | return 121 | } 122 | defer r.Body.Close() 123 | 124 | fmt.Printf("收到请求体: %s\n", string(body)) 125 | 126 | // 解析用户请求 127 | var userReq UserRequest 128 | if err := json.Unmarshal(body, &userReq); err != nil { 129 | fmt.Printf("解析请求体失败: %v\n", err) 130 | http.Error(w, "解析请求失败", http.StatusBadRequest) 131 | return 132 | } 133 | 134 | // 解析默认模板 135 | var defaultConfig map[string]interface{} 136 | if err := json.Unmarshal(defaultJsonTemplate, &defaultConfig); err != nil { 137 | fmt.Printf("解析默认配置失败: %v\n", err) 138 | http.Error(w, "服务器配置错误", http.StatusInternalServerError) 139 | return 140 | } 141 | 142 | // 获取用户消息内容 143 | prompt := "你好" // 默认提示词 144 | if len(userReq.Messages) > 0 { 145 | prompt = userReq.Messages[len(userReq.Messages)-1].Content 146 | } 147 | fmt.Printf("处理的prompt: %s\n", prompt) 148 | 149 | // 添加prompt到配置中 150 | defaultConfig["prompt"] = prompt 151 | 152 | // 添加model到配置中 153 | if userReq.Model != "" { 154 | defaultConfig["model"] = userReq.Model 155 | } else { 156 | defaultConfig["model"] = "gpt-4o" // 默认模型 157 | } 158 | 159 | // 设置stream参数 160 | defaultConfig["stream"] = userReq.Stream 161 | 162 | fmt.Printf("使用的模型: %s\n", defaultConfig["model"]) 163 | 164 | // 转换回JSON 165 | finalBody, err := json.Marshal(defaultConfig) 166 | if err != nil { 167 | fmt.Printf("生成最终请求体失败: %v\n", err) 168 | http.Error(w, "处理请求失败", http.StatusInternalServerError) 169 | return 170 | } 171 | 172 | fmt.Printf("发送到Sider的请求体: %s\n", string(finalBody)) 173 | 174 | // 创建转发到Sider的请求 175 | url := "https://api2.sider.ai/api/v3/completion/text" 176 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(finalBody)) 177 | if err != nil { 178 | fmt.Printf("创建Sider请求失败: %v\n", err) 179 | http.Error(w, "创建请求失败", http.StatusInternalServerError) 180 | return 181 | } 182 | 183 | // 设置请求头 184 | req.Header.Set("accept", "*/*") 185 | req.Header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") 186 | req.Header.Set("authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMDQ3ODEyNywicmVnaXN0ZXJfdHlwZSI6Im9hdXRoMiIsImFwcF9uYW1lIjoiQ2hpdENoYXRfRWRnZV9FeHQiLCJ0b2tlbl9pZCI6IjMyMTRiMDc0LTU2MTMtNDI1ZC04YjM2LTQzNGU4YjBjYjRkOSIsImlzcyI6InNpZGVyLmFpIiwiYXVkIjpbIiJdLCJleHAiOjE3NTA0NzIxMTEsIm5iZiI6MTcxOTM2ODExMSwiaWF0IjoxNzE5MzY4MTExfQ.glb9636RPBhoL0v3F0YzGPKoRaVv4FmTeDW-Swk-JWA") 187 | req.Header.Set("content-type", "application/json") 188 | req.Header.Set("origin", "chrome-extension://dhoenijjpgpeimemopealfcbiecgceod") 189 | req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0") 190 | 191 | // 发送请求到Sider 192 | client := &http.Client{} 193 | resp, err := client.Do(req) 194 | if err != nil { 195 | fmt.Printf("发送到Sider请求失败: %v\n", err) 196 | http.Error(w, "发送请求失败", http.StatusInternalServerError) 197 | return 198 | } 199 | defer resp.Body.Close() 200 | 201 | fmt.Printf("Sider响应状态码: %d\n", resp.StatusCode) 202 | 203 | if !userReq.Stream { 204 | // 非流式响应 205 | w.Header().Set("Content-Type", "application/json") 206 | fullResponse := "" 207 | reader := bufio.NewReader(resp.Body) 208 | 209 | for { 210 | line, err := reader.ReadString('\n') 211 | if err != nil { 212 | if err == io.EOF { 213 | break 214 | } 215 | http.Error(w, "读取响应失败", http.StatusInternalServerError) 216 | return 217 | } 218 | 219 | line = strings.TrimSpace(line) 220 | line = strings.TrimPrefix(line, "data:") 221 | 222 | if line == "" || line == "[DONE]" { 223 | continue 224 | } 225 | 226 | var siderResp SiderResponse 227 | if err := json.Unmarshal([]byte(line), &siderResp); err != nil { 228 | continue 229 | } 230 | 231 | fullResponse += siderResp.Data.Text 232 | } 233 | 234 | openAIResp := OpenAIResponse{ 235 | ID: "chatcmpl-" + time.Now().Format("20060102150405"), 236 | Object: "chat.completion", 237 | Created: time.Now().Unix(), 238 | Model: userReq.Model, 239 | Choices: []struct { 240 | Message struct { 241 | Role string `json:"role"` 242 | Content string `json:"content"` 243 | } `json:"message"` 244 | FinishReason string `json:"finish_reason"` 245 | Index int `json:"index"` 246 | }{ 247 | { 248 | Message: struct { 249 | Role string `json:"role"` 250 | Content string `json:"content"` 251 | }{ 252 | Role: "assistant", 253 | Content: fullResponse, 254 | }, 255 | FinishReason: "stop", 256 | Index: 0, 257 | }, 258 | }, 259 | Usage: struct { 260 | PromptTokens int `json:"prompt_tokens"` 261 | CompletionTokens int `json:"completion_tokens"` 262 | TotalTokens int `json:"total_tokens"` 263 | }{ 264 | PromptTokens: len(prompt), 265 | CompletionTokens: len(fullResponse), 266 | TotalTokens: len(prompt) + len(fullResponse), 267 | }, 268 | } 269 | 270 | json.NewEncoder(w).Encode(openAIResp) 271 | return 272 | } 273 | 274 | // 流式响应 275 | w.Header().Set("Content-Type", "text/event-stream") 276 | w.Header().Set("Cache-Control", "no-cache") 277 | w.Header().Set("Connection", "keep-alive") 278 | w.WriteHeader(http.StatusOK) 279 | 280 | // 使用bufio.Reader来读取流式响应 281 | reader := bufio.NewReader(resp.Body) 282 | 283 | for { 284 | line, err := reader.ReadString('\n') 285 | if err != nil { 286 | if err == io.EOF { 287 | fmt.Println("响应结束") 288 | break 289 | } 290 | fmt.Printf("读取响应失败: %v\n", err) 291 | return 292 | } 293 | 294 | // 去除前缀和空白字符 295 | line = strings.TrimSpace(line) 296 | line = strings.TrimPrefix(line, "data:") 297 | 298 | // 跳过空行 299 | if line == "" { 300 | continue 301 | } 302 | 303 | // 如果是[DONE],发送OpenAI格式的[DONE] 304 | if line == "[DONE]" { 305 | _, err = w.Write([]byte("data: [DONE]\n\n")) 306 | if err != nil { 307 | fmt.Printf("写入DONE失败: %v\n", err) 308 | } 309 | w.(http.Flusher).Flush() 310 | break 311 | } 312 | 313 | // 解析Sider响应 314 | var siderResp SiderResponse 315 | if err := json.Unmarshal([]byte(line), &siderResp); err != nil { 316 | fmt.Printf("解析Sider响应失败: %v\n", err) 317 | continue 318 | } 319 | 320 | // 转换为OpenAI格式 321 | openAIResp := OpenAIStreamResponse{ 322 | ID: "chatcmpl-" + siderResp.Data.ChatModel, 323 | Object: "chat.completion.chunk", 324 | Created: time.Now().Unix(), 325 | Model: siderResp.Data.ChatModel, 326 | Choices: []struct { 327 | Delta struct { 328 | Content string `json:"content"` 329 | } `json:"delta"` 330 | FinishReason string `json:"finish_reason"` 331 | Index int `json:"index"` 332 | }{ 333 | { 334 | Delta: struct { 335 | Content string `json:"content"` 336 | }{ 337 | Content: siderResp.Data.Text, 338 | }, 339 | FinishReason: "", 340 | Index: 0, 341 | }, 342 | }, 343 | } 344 | 345 | // 转换为JSON 346 | openAIJSON, err := json.Marshal(openAIResp) 347 | if err != nil { 348 | fmt.Printf("转换OpenAI格式失败: %v\n", err) 349 | continue 350 | } 351 | 352 | // 发送OpenAI格式的响应 353 | _, err = w.Write([]byte("data: " + string(openAIJSON) + "\n\n")) 354 | if err != nil { 355 | fmt.Printf("写入响应失败: %v\n", err) 356 | return 357 | } 358 | w.(http.Flusher).Flush() 359 | } 360 | } 361 | 362 | func main() { 363 | // 注册路由处理函数 364 | http.HandleFunc("/v1/chat/completions", func(w http.ResponseWriter, r *http.Request) { 365 | if r.Method == "OPTIONS" { 366 | handleOptions(w, r) 367 | return 368 | } 369 | forwardToSider(w, r) 370 | }) 371 | 372 | fmt.Println("服务器启动在 http://127.0.0.1:7055") 373 | fmt.Println("支持的模型: gpt-4o, claude-3.5-sonnet, deepseek-reasoner,o3-mini,o1,llama-3.1-405b,gemini-2.0-pro") 374 | if err := http.ListenAndServe("127.0.0.1:7055", nil); err != nil { 375 | fmt.Printf("服务器启动失败: %v\n", err) 376 | } 377 | } 378 | --------------------------------------------------------------------------------