ainz commited on
Commit
688d352
·
1 Parent(s): 1331a43
Files changed (7) hide show
  1. Dockerfile +11 -0
  2. README.md +68 -11
  3. docker-compose.yml +7 -0
  4. package.json +20 -0
  5. src/lib/chat.js +136 -0
  6. src/lib/tools.js +20 -0
  7. src/server.js +202 -0
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:lts-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . .
6
+
7
+ RUN npm i
8
+
9
+ EXPOSE 3000
10
+
11
+ CMD ["npm", "start"]
README.md CHANGED
@@ -1,11 +1,68 @@
1
- ---
2
- title: Hi
3
- emoji: 🏆
4
- colorFrom: green
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
8
- short_description: x
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 安装与运行
2
+
3
+ ### 使用 Docker 运行
4
+
5
+ 1. 使用 Docker 命令:
6
+
7
+ ```bash
8
+ docker run -d -p 8999:8999 --name hixai2api rfym21/hixai2api:latest
9
+ ```
10
+
11
+ 2. 使用 docker-compose 运行服务:
12
+
13
+ ```bash
14
+ curl -o docker-compose.yml https://raw.githubusercontent.com/Rfym21/Hixai2API/refs/heads/main/docker-compose.yml
15
+ docker compose pull && docker compose up -d
16
+ ```
17
+
18
+ ### 本地运行
19
+
20
+ 1. 下载仓库文件:
21
+
22
+ ```bash
23
+ git clone https://github.com/Rfym21/Hixai2API
24
+ cd Hixai2API
25
+ ```
26
+
27
+ 2. 安装依赖:
28
+
29
+ ```bash
30
+ npm install
31
+ ```
32
+
33
+ 3. 运行服务:
34
+
35
+ ```bash
36
+ npm start
37
+ ```
38
+
39
+ ## 获取 Token
40
+ ![2025-02-23_18-29-17.png](https://s2.loli.net/2025/02/23/my1SVoaqWvbTuRA.png)
41
+
42
+ ## API 端点
43
+
44
+ ### 获取模型列表
45
+
46
+ - **请求方式**: `GET`
47
+ - **URL**: `/v1/models`
48
+
49
+ ### 聊天完成
50
+
51
+ - **请求方式**: `POST`
52
+ - **URL**: `/v1/chat/completions`
53
+ - **Headers**:
54
+ - `Authorization`: 必须提供有效的授权令牌。
55
+ - **请求体**:
56
+
57
+ ```json
58
+ {
59
+ "model": "模型名称",
60
+ "messages": [
61
+ {
62
+ "role": "user",
63
+ "content": "用户消息"
64
+ }
65
+ ],
66
+ "stream": false
67
+ }
68
+ ```
docker-compose.yml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ services:
2
+ hixai2api:
3
+ container_name: hixai2api
4
+ image: rfym21/hixai2api:latest
5
+ restart: always
6
+ ports:
7
+ - "8999:8999"
package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "qwen2api",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "start": "node src/server.js",
7
+ "dev": "nodemon src/server.js"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "description": "",
13
+ "dependencies": {
14
+ "axios": "^1.7.9",
15
+ "body-parser": "^1.20.3",
16
+ "dotenv": "^16.4.7",
17
+ "express": "^4.21.2",
18
+ "nodemon": "^3.1.7"
19
+ }
20
+ }
src/lib/chat.js ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const axios = require('axios')
2
+
3
+ class Chat {
4
+ constructor() {
5
+ }
6
+
7
+ async createChat(token) {
8
+
9
+ try {
10
+ const response = await axios.post(`https://hix.ai/api/trpc/hixChat.createChat?batch=1`,
11
+ {
12
+ "0": {
13
+ "json": {
14
+ "title": `${new Date().toLocaleString()} New Chat`,
15
+ "botId": 85426
16
+ }
17
+ }
18
+ },
19
+ {
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ 'Cookie': `__Secure-next-auth.session-token=${token}`,
23
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
24
+ 'Accept': 'application/json',
25
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
26
+ 'Referer': 'https://hix.ai/',
27
+ 'Origin': 'https://hix.ai',
28
+ 'Sec-Fetch-Dest': 'empty',
29
+ 'Sec-Fetch-Mode': 'cors',
30
+ 'Sec-Fetch-Site': 'cross-site'
31
+ }
32
+ }
33
+ )
34
+ return response.data[0].result.data.json.id
35
+ } catch (error) {
36
+ console.log("Token可能已失效,创建聊天失败!!!")
37
+ return null
38
+ }
39
+ }
40
+
41
+ async parserMessagesMode(messages) {
42
+ try {
43
+ const userMessages = messages.filter(item => item.role === 'user' || item.role === 'assistant')
44
+ const assistantMessages = messages.filter(item => item.role === 'assistant')
45
+ const systemMessages = messages.filter(item => item.role === 'system').map(item => item.content).join('\n')
46
+ if (userMessages.length === 1) {
47
+ let newMessage = `
48
+ ${systemMessages ? `<system>\n${systemMessages}</system>\n` : ''}
49
+
50
+ ${JSON.stringify(userMessages[userMessages.length - 1].content)}
51
+ `
52
+ return {
53
+ status: 200,
54
+ message: newMessage,
55
+ chatId: null,
56
+ }
57
+ } else {
58
+ const signRegex = /\[ChatID: (.*?)\]/
59
+ const sign = assistantMessages[assistantMessages.length - 1].content.match(signRegex)
60
+ if (sign) {
61
+ const chatId = sign[1].replace('[ChatID: ', '').replace(']', '')
62
+ return {
63
+ status: 200,
64
+ message: userMessages[userMessages.length - 1].content,
65
+ chatId: chatId,
66
+ }
67
+ } else {
68
+ return this.createForgeChat(messages)
69
+ }
70
+ }
71
+ } catch (error) {
72
+ return {
73
+ status: 500,
74
+ message: null,
75
+ chatId: null,
76
+ }
77
+ }
78
+ }
79
+
80
+ async createForgeChat(messages) {
81
+ let newMessage = `
82
+ ${JSON.stringify(messages.filter(item => item.role === 'system').map(item => item.content).join('\n')) ? `<system>\n${JSON.stringify(messages.filter(item => item.role === 'system').map(item => item.content).join('\n'))}\n</system>\n` : ''}
83
+
84
+ <history>
85
+ ${JSON.stringify(messages.filter(item => item.role === 'user' || item.role === 'assistant').map(item => item.content).join('\n'))}
86
+ </history>
87
+
88
+ ${messages[messages.length - 1].content}
89
+ `
90
+ return {
91
+ status: 200,
92
+ message: newMessage,
93
+ chatId: null,
94
+ }
95
+ }
96
+
97
+ async sendMessage(chatId, message, token) {
98
+ console.log(chatId)
99
+ try {
100
+ const response = await axios.post(`https://hix.ai/api/hix/chat`,
101
+ {
102
+ "chatId": chatId,
103
+ "question": message,
104
+ "fileUrl": "",
105
+ "answer": "",
106
+ "relatedQuestions": []
107
+ },
108
+ {
109
+ headers: {
110
+ 'Content-Type': 'application/json',
111
+ 'Cookie': `__Secure-next-auth.session-token=${token}`
112
+ },
113
+ responseType: 'stream'
114
+ }
115
+ )
116
+ return {
117
+ response: response.data,
118
+ status: 200,
119
+ }
120
+ } catch (error) {
121
+ if (error.response.status === 403) {
122
+ return {
123
+ response: null,
124
+ status: 403
125
+ }
126
+ }
127
+ return {
128
+ response: null,
129
+ status: error.response.status
130
+ }
131
+ }
132
+ }
133
+
134
+ }
135
+
136
+ module.exports = Chat
src/lib/tools.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const crypto = require('crypto')
2
+
3
+ const uuid = () => {
4
+ return crypto.randomUUID()
5
+ }
6
+
7
+ const isJson = (str) => {
8
+ try {
9
+ JSON.parse(str)
10
+ return true
11
+ } catch (error) {
12
+ return false
13
+ }
14
+ }
15
+
16
+ const sleep = async (ms) => {
17
+ return new Promise(resolve => setTimeout(resolve, ms))
18
+ }
19
+
20
+ module.exports = { uuid, isJson, sleep }
src/server.js ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express')
2
+ const app = express()
3
+ const bodyParser = require('body-parser')
4
+ const Chat = require('./lib/chat.js')
5
+ const { uuid, isJson } = require('./lib/tools')
6
+
7
+
8
+ const ChatManager = new Chat()
9
+
10
+ app.use(bodyParser.json({ limit: '32mb' }))
11
+ app.use(bodyParser.urlencoded({ extended: true, limit: '32mb' }))
12
+
13
+ app.get("/v1/models", async (req, res) => {
14
+ res.json({
15
+ object: "list",
16
+ data: [{
17
+ "id": "deepseek-reasoner",
18
+ "object": "model",
19
+ "created": 1686935002,
20
+ "owned_by": "hixai"
21
+ }],
22
+ object: "list"
23
+ })
24
+ })
25
+
26
+ app.post("/v1/chat/completions", async (req, res) => {
27
+
28
+ const token = req.headers.authorization?.replace("Bearer ", "")
29
+ if (!token) {
30
+ res.status(401).json({
31
+ error: "未提供Token!!!"
32
+ })
33
+ return
34
+ }
35
+
36
+ const stream = req.body.stream || false
37
+ let { message, chatId, status } = await ChatManager.parserMessagesMode(req.body.messages)
38
+ if (status === 500) {
39
+ res.status(500).json({
40
+ error: "服务器服务错误!!!"
41
+ })
42
+ return
43
+ }
44
+
45
+ if (stream) {
46
+ res.setHeader('Content-Type', 'text/event-stream')
47
+ res.setHeader('Cache-Control', 'no-cache')
48
+ res.setHeader('Connection', 'keep-alive')
49
+ } else {
50
+ res.setHeader('Content-Type', 'application/json')
51
+ }
52
+
53
+ const returnResponse = async (response, req, res, stream, chatId) => {
54
+ let reasoningStatus = false
55
+ let notStreamContent = ''
56
+ const decoder = new TextDecoder('utf-8')
57
+
58
+ const signText = `\n\n\n[ChatID: ${chatId}]\n`
59
+
60
+ const StreamTemplate = {
61
+ "id": `chatcmpl-${uuid()}`,
62
+ "object": "chat.completion.chunk",
63
+ "created": new Date().getTime(),
64
+ "choices": [
65
+ {
66
+ "index": 0,
67
+ "delta": {
68
+ "content": null
69
+ },
70
+ "finish_reason": null
71
+ }
72
+ ]
73
+ }
74
+
75
+ response.on('data', (chunk) => {
76
+ const decodeText = decoder.decode(chunk)
77
+
78
+ const lists = decodeText.split('\n').filter(item => item.trim() !== '')
79
+
80
+ for (const item of lists) {
81
+ try {
82
+ if (!item.includes('data')) {
83
+ continue
84
+ }
85
+
86
+ const decodeJson = isJson(item.replace(/^data: /, '')) ? JSON.parse(item.replace(/^data: /, '')) : null
87
+ let content = ''
88
+ if (decodeJson === null || (decodeJson.reasoning_content === undefined && decodeJson.content === undefined && decodeJson.thinking_time === undefined)) {
89
+ continue
90
+ }
91
+ content = decodeJson.content || decodeJson.reasoning_content
92
+ if (reasoningStatus === false && decodeJson.reasoning_content) {
93
+ content = `<think>\n${decodeJson.reasoning_content}`
94
+ reasoningStatus = true
95
+ }
96
+ if (decodeJson.thinking_time) {
97
+ content = `\n</think>\n`
98
+ }
99
+ if (content === undefined) {
100
+ continue
101
+ }
102
+
103
+ if (stream) {
104
+ StreamTemplate.choices[0].delta.content = content
105
+ res.write(`data: ${JSON.stringify(StreamTemplate)}\n\n`)
106
+ } else {
107
+ notStreamContent += content
108
+ }
109
+
110
+ } catch (error) {
111
+ // console.log(error)
112
+ res.status(500)
113
+ .json({
114
+ error: "服务错误!!!"
115
+ })
116
+ }
117
+ }
118
+ })
119
+
120
+ response.on('end', () => {
121
+ if (stream) {
122
+ StreamTemplate.choices[0].delta.content = signText
123
+ res.write(`data: ${JSON.stringify(StreamTemplate)}\n\n`)
124
+ res.write(`data: [DONE]\n\n`)
125
+ res.end()
126
+ } else {
127
+ notStreamContent += signText
128
+ const bodyTemplate = {
129
+ "id": `chatcmpl-${uuid()}`,
130
+ "object": "chat.completion",
131
+ "created": new Date().getTime(),
132
+ "model": req.body.model,
133
+ "choices": [
134
+ {
135
+ "index": 0,
136
+ "message": {
137
+ "role": "assistant",
138
+ "content": notStreamContent
139
+ },
140
+ "finish_reason": "stop"
141
+ }
142
+ ],
143
+ "usage": {
144
+ "prompt_tokens": 1024,
145
+ "completion_tokens": notStreamContent.length,
146
+ "total_tokens": 1024 + notStreamContent.length
147
+ }
148
+ }
149
+ res.json(bodyTemplate)
150
+ }
151
+ })
152
+ }
153
+
154
+ for (let i = 0; i < 3; i++) {
155
+ try {
156
+
157
+ if (!chatId) {
158
+ chatId = await ChatManager.createChat(token)
159
+ if (!chatId) {
160
+ res.status(500).json({
161
+ error: "创建聊天失败!!!"
162
+ })
163
+ return
164
+ }
165
+ }
166
+
167
+ let { response, status } = await ChatManager.sendMessage(chatId, message, token, stream)
168
+ if (status === 200 && response) {
169
+ await returnResponse(response, req, res, stream, chatId)
170
+ return
171
+ } else if (status === 403) {
172
+ message = await ChatManager.createForgeChat(req.body.messages)
173
+ chatId = null
174
+ } else {
175
+ res.status(500).json({
176
+ error: "请求发送失败!!!"
177
+ })
178
+ return
179
+ }
180
+
181
+ }
182
+ catch (error) {
183
+ res.status(500).json({
184
+ error: "服务器服务错误!!!"
185
+ })
186
+ return
187
+ }
188
+
189
+ }
190
+
191
+ res.status(500).json({
192
+ error: "多次尝试后依旧失败!!!"
193
+ })
194
+
195
+ })
196
+
197
+
198
+ app.listen(8999, () => {
199
+ console.log('Server is running on port 8999')
200
+ })
201
+
202
+