├── go.mod ├── go.sum ├── vercel.json ├── README.md └── api └── main.go /go.mod: -------------------------------------------------------------------------------- 1 | module jullus2api 2 | 3 | go 1.22.2 4 | 5 | require github.com/google/uuid v1.6.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 2 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 3 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "api/*.go", 6 | "use": "@vercel/go" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/api/main.go" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 开始使用 2 | 3 | 1. 点击右侧按钮开始部署: 4 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3a%2f%2fgithub.com%2fSMNETSTUDIO%2fJullus2Api&project-name=jullus2api&repository-name=jullus2api) 5 | 2. 在环境变量页填入AUTH_TOKEN 6 | 3. 部署完毕后,即可开始使用 7 | -------------------------------------------------------------------------------- /api/main.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "os" 11 | "strings" 12 | "time" 13 | 14 | "github.com/google/uuid" 15 | ) 16 | 17 | var modelMapping = map[string]string{ 18 | "gpt-4o-mini": "GPT-4o mini", 19 | "claude-haiku": "Claude Haiku", 20 | "llama-3": "Llama 3", 21 | "gemini-1.5": "Gemini 1.5", 22 | "gemini-flash": "Gemini Flash", 23 | "command-r": "Command R", 24 | } 25 | 26 | func getCurrentTimestamp() int64 { 27 | return time.Now().Unix() 28 | } 29 | 30 | type OpenAIRequest struct { 31 | Messages []Message `json:"messages"` 32 | Model string `json:"model"` 33 | Stream bool `json:"stream"` 34 | } 35 | 36 | type Message struct { 37 | Role string `json:"role"` 38 | Content string `json:"content"` 39 | } 40 | 41 | type OpenAIResponse struct { 42 | Id string `json:"id"` 43 | Object string `json:"object"` 44 | Created int64 `json:"created"` 45 | Model string `json:"model"` 46 | Choices []Choice `json:"choices"` 47 | } 48 | 49 | type Choice struct { 50 | Index int `json:"index"` 51 | Message Message `json:"message"` 52 | FinishReason string `json:"finish_reason"` 53 | } 54 | 55 | type ChatCompletionStreamResponse struct { 56 | ID string `json:"id"` 57 | Object string `json:"object"` 58 | Created int64 `json:"created"` 59 | Model string `json:"model"` 60 | Choices []struct { 61 | Delta struct { 62 | Content string `json:"content"` 63 | Role string `json:"role,omitempty"` 64 | } `json:"delta"` 65 | Index int `json:"index"` 66 | FinishReason string `json:"finish_reason,omitempty"` 67 | } `json:"choices"` 68 | } 69 | 70 | func Handler(w http.ResponseWriter, r *http.Request) { 71 | authToken := os.Getenv("AUTH_TOKEN") 72 | if authToken != "" { 73 | requestToken := r.Header.Get("Authorization") 74 | if requestToken == "" { 75 | http.Error(w, "Access Denied", http.StatusUnauthorized) 76 | return 77 | } 78 | requestToken = strings.TrimPrefix(requestToken, "Bearer ") 79 | if requestToken != authToken { 80 | http.Error(w, "Access Denied", http.StatusUnauthorized) 81 | return 82 | } 83 | } 84 | 85 | if r.URL.Path != "/v1/chat/completions" { 86 | w.Header().Set("Content-Type", "application/json") 87 | response := map[string]string{ 88 | "status": "Julius2Api Service Running...", 89 | "message": "MoLoveSze...", 90 | } 91 | json.NewEncoder(w).Encode(response) 92 | return 93 | } 94 | 95 | if r.Method != http.MethodPost { 96 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 97 | return 98 | } 99 | 100 | var openAIReq OpenAIRequest 101 | if err := json.NewDecoder(r.Body).Decode(&openAIReq); err != nil { 102 | http.Error(w, err.Error(), http.StatusBadRequest) 103 | return 104 | } 105 | 106 | if mappedModel, exists := modelMapping[openAIReq.Model]; exists { 107 | openAIReq.Model = mappedModel 108 | } else { 109 | openAIReq.Model = "GPT-4o mini" 110 | } 111 | 112 | tempUserID, err := getTempUserID() 113 | if err != nil { 114 | http.Error(w, err.Error(), http.StatusInternalServerError) 115 | return 116 | } 117 | 118 | juliusResp, err := sendToJulius(tempUserID, openAIReq.Messages[len(openAIReq.Messages)-1].Content, openAIReq.Model) 119 | if err != nil { 120 | http.Error(w, err.Error(), http.StatusInternalServerError) 121 | return 122 | } 123 | 124 | isStream := openAIReq.Stream 125 | 126 | respId := "chatcmpl-" + tempUserID 127 | created := getCurrentTimestamp() 128 | 129 | if isStream { 130 | w.Header().Set("Content-Type", "text/event-stream") 131 | w.Header().Set("Cache-Control", "no-cache") 132 | w.Header().Set("Connection", "keep-alive") 133 | 134 | chunks := splitIntoChunks(juliusResp, 50) 135 | firstResponse := ChatCompletionStreamResponse{ 136 | ID: respId, 137 | Object: "chat.completion.chunk", 138 | Created: created, 139 | Model: openAIReq.Model, 140 | Choices: []struct { 141 | Delta struct { 142 | Content string `json:"content"` 143 | Role string `json:"role,omitempty"` 144 | } `json:"delta"` 145 | Index int `json:"index"` 146 | FinishReason string `json:"finish_reason,omitempty"` 147 | }{ 148 | { 149 | Delta: struct { 150 | Content string `json:"content"` 151 | Role string `json:"role,omitempty"` 152 | }{ 153 | Role: "assistant", 154 | }, 155 | Index: 0, 156 | }, 157 | }, 158 | } 159 | 160 | data, err := json.Marshal(firstResponse) 161 | if err != nil { 162 | http.Error(w, err.Error(), http.StatusInternalServerError) 163 | return 164 | } 165 | fmt.Fprintf(w, "data: %s\n\n", string(data)) 166 | 167 | for i, chunk := range chunks { 168 | response := ChatCompletionStreamResponse{ 169 | ID: respId, 170 | Object: "chat.completion.chunk", 171 | Created: created, 172 | Model: openAIReq.Model, 173 | Choices: []struct { 174 | Delta struct { 175 | Content string `json:"content"` 176 | Role string `json:"role,omitempty"` 177 | } `json:"delta"` 178 | Index int `json:"index"` 179 | FinishReason string `json:"finish_reason,omitempty"` 180 | }{ 181 | { 182 | Delta: struct { 183 | Content string `json:"content"` 184 | Role string `json:"role,omitempty"` 185 | }{ 186 | Content: chunk, 187 | }, 188 | Index: 0, 189 | FinishReason: func() string { 190 | if i == len(chunks)-1 { 191 | return "stop" 192 | } 193 | return "" 194 | }(), 195 | }, 196 | }, 197 | } 198 | 199 | data, err := json.Marshal(response) 200 | if err != nil { 201 | http.Error(w, err.Error(), http.StatusInternalServerError) 202 | return 203 | } 204 | fmt.Fprintf(w, "data: %s\n\n", string(data)) 205 | } 206 | 207 | fmt.Fprintf(w, "data: [DONE]\n\n") 208 | } else { 209 | w.Header().Set("Content-Type", "application/json") 210 | response := OpenAIResponse{ 211 | Id: respId, 212 | Object: "chat.completion", 213 | Created: created, 214 | Model: openAIReq.Model, 215 | Choices: []Choice{ 216 | { 217 | Index: 0, 218 | Message: Message{ 219 | Role: "assistant", 220 | Content: juliusResp, 221 | }, 222 | FinishReason: "stop", 223 | }, 224 | }, 225 | } 226 | json.NewEncoder(w).Encode(response) 227 | } 228 | } 229 | 230 | func splitIntoChunks(text string, chunkSize int) []string { 231 | var chunks []string 232 | runes := []rune(text) 233 | for i := 0; i < len(runes); i += chunkSize { 234 | end := i + chunkSize 235 | if end > len(runes) { 236 | end = len(runes) 237 | } 238 | chunks = append(chunks, string(runes[i:end])) 239 | } 240 | return chunks 241 | } 242 | 243 | func getTempUserID() (string, error) { 244 | resp, err := http.Get("https://playground.julius.ai/api/temp_user_id") 245 | if err != nil { 246 | return "", err 247 | } 248 | defer resp.Body.Close() 249 | 250 | var result struct { 251 | Status string `json:"status"` 252 | TempUserID string `json:"temp_user_id"` 253 | } 254 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 255 | return "", err 256 | } 257 | return result.TempUserID, nil 258 | //return "自定义ID", nil 259 | } 260 | 261 | func sendToJulius(tempUserID, message string, model string) (string, error) { 262 | conversationID := uuid.New().String() 263 | 264 | juliusReq := map[string]interface{}{ 265 | "message": map[string]interface{}{ 266 | "content": message, 267 | "role": "user", 268 | }, 269 | "provider": "default", 270 | "chat_mode": "auto", 271 | "client_version": "20240130", 272 | "theme": "dark", 273 | "new_images": nil, 274 | "new_attachments": nil, 275 | "dataframe_format": "json", 276 | "selectedModels": []string{ 277 | model, 278 | }, 279 | } 280 | 281 | reqBody, err := json.Marshal(juliusReq) 282 | if err != nil { 283 | return "", err 284 | } 285 | 286 | req, err := http.NewRequest("POST", "https://playground.julius.ai/api/chat/message", bytes.NewBuffer(reqBody)) 287 | if err != nil { 288 | return "", err 289 | } 290 | req.Header.Set("is-demo", tempUserID) 291 | req.Header.Set("Content-Type", "application/json") 292 | req.Header.Set("Platform", "web") 293 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36") 294 | req.Header.Set("conversation-id", conversationID) 295 | req.Header.Set("interactive-charts", "true") 296 | req.Header.Set("use-dict", "true") 297 | req.Header.Set("Gcs", "true") 298 | req.Header.Set("Is-Native", "false") 299 | req.Header.Set("sec-ch-ua-platform", "Windows") 300 | req.Header.Set("Accept", "*/*") 301 | req.Header.Set("Sec-Fetch-Site", "same-site") 302 | req.Header.Set("Sec-Fetch-Mode", "cors") 303 | req.Header.Set("Sec-Fetch-Dest", "empty") 304 | 305 | client := &http.Client{} 306 | resp, err := client.Do(req) 307 | if err != nil { 308 | return "", err 309 | } 310 | defer resp.Body.Close() 311 | 312 | var fullResponse strings.Builder 313 | reader := bufio.NewReader(resp.Body) 314 | 315 | for { 316 | line, err := reader.ReadString('\n') 317 | if err == io.EOF { 318 | break 319 | } 320 | if err != nil { 321 | return "", err 322 | } 323 | var jsonResp map[string]interface{} 324 | if err := json.Unmarshal([]byte(line), &jsonResp); err != nil { 325 | continue 326 | } 327 | if content, ok := jsonResp["content"].(string); ok { 328 | fullResponse.WriteString(content) 329 | } 330 | } 331 | return fullResponse.String(), nil 332 | } 333 | --------------------------------------------------------------------------------