ilhooq commited on
Commit
b95ac18
1 Parent(s): 7fa08be
.editorconfig ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ indent_size = 4
6
+ indent_style = space
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
13
+
14
+ [*.js]
15
+ indent_size = 2
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ghcr.io/ggerganov/llama.cpp:server
2
+
3
+ RUN apt update && apt install -y curl
4
+
5
+ RUN mkdir /models
6
+
7
+ RUN curl -L https://huggingface.co/TheBloke/deepseek-coder-1.3b-instruct-GGUF/resolve/main/deepseek-coder-1.3b-instruct.Q5_K_M.gguf --output /models/deepseek-coder-1.3b-instruct.Q5_K_M.gguf
8
+
9
+ COPY ./public /webui
10
+
11
+ ENTRYPOINT [ "/server" ]
12
+
13
+ CMD [ "--host", "0.0.0.0", "--port", "7860", "--model", "/models/deepseek-coder-1.3b-instruct.Q5_K_M.gguf", "-c", "4096", "--chat-template", "deepseek", "--path", "/webui" ]
public/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Siyu Long
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
public/README.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # chatgpt-api-frontend
2
+ Frontend of ChatGPT API, with an optional Cloudflare Pages Functions backend to proxy requests and store tokens.
3
+
4
+ Code from https://github.com/longern/chatgpt-api-frontend
5
+
6
+ ## Features
7
+ - [x] Chat with gpt-3.5-turbo model
8
+ - [x] Stream HTTP responses
9
+ - [x] Markdown support
10
+ - [x] Code highlighting
11
+ - [x] Save chat history
12
+ - [x] Store token in backend or require user to provide it
13
+
14
+ ## Usage
15
+
16
+ Open `index.html`. That's it!
public/assets/css/style.css ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+ html,
5
+ body,
6
+ #app {
7
+ height: 100%;
8
+ }
9
+ body {
10
+ margin: 0;
11
+ font-family: -apple-system, "system-ui", "Segoe UI Adjusted", "Segoe UI",
12
+ "Liberation Sans", sans-serif;
13
+ }
14
+ pre {
15
+ overflow-x: auto;
16
+ border-radius: 6px;
17
+ }
18
+ button {
19
+ border: none;
20
+ background-color: transparent;
21
+ color: inherit;
22
+ padding: 0;
23
+ cursor: pointer;
24
+ }
25
+ ul {
26
+ list-style: none;
27
+ padding: 0;
28
+ margin: 0;
29
+ }
30
+ #app {
31
+ display: flex;
32
+ flex-direction: column;
33
+ }
34
+ header {
35
+ position: relative;
36
+ width: 100%;
37
+ height: 48px;
38
+ flex-shrink: 0;
39
+ display: flex;
40
+ justify-content: center;
41
+ align-items: center;
42
+ background-color: #333;
43
+ color: white;
44
+ }
45
+ header > button {
46
+ position: absolute;
47
+ padding: 12px;
48
+ }
49
+ button.menu {
50
+ left: 0;
51
+ }
52
+ button.clear {
53
+ right: 0;
54
+ }
55
+ .hidden {
56
+ display: none;
57
+ }
58
+ aside {
59
+ position: fixed;
60
+ top: 0;
61
+ width: 100%;
62
+ height: 100%;
63
+ z-index: 1;
64
+ display: flex;
65
+ }
66
+ .sidebar-container {
67
+ background-color: #333;
68
+ color: white;
69
+ width: 300px;
70
+ overflow-y: auto;
71
+ }
72
+ .sidebar-modal {
73
+ flex: 1 0;
74
+ min-width: 64px;
75
+ background-color: rgba(0, 0, 0, 0.3);
76
+ }
77
+ li > button {
78
+ width: 100%;
79
+ height: 48px;
80
+ text-align: left;
81
+ padding: 0 16px;
82
+ transition: background-color 0.2s;
83
+ }
84
+ li > button:hover {
85
+ background-color: rgba(255, 255, 255, 0.1);
86
+ }
87
+ main {
88
+ flex-grow: 1;
89
+ overflow-y: auto;
90
+ }
91
+ .chat-container {
92
+ display: flex;
93
+ flex-direction: column;
94
+ width: 100%;
95
+ max-width: 768px;
96
+ margin: 0 auto;
97
+ overflow-wrap: break-word;
98
+ }
99
+ .chat-container .message {
100
+ padding: 2px 16px;
101
+ }
102
+
103
+ .chat-container code:not(.hljs) {
104
+ color: #4d4d4d;
105
+ padding: 0 5px;
106
+ display: inline-block;
107
+ }
108
+
109
+ .chat-container .assistant {
110
+ background-color: #f0f0f0;
111
+ }
112
+ footer {
113
+ width: 100%;
114
+ padding: 8px;
115
+ display: flex;
116
+ justify-content: center;
117
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
118
+ }
119
+ #input-box {
120
+ width: 100%;
121
+ max-width: 768px;
122
+ padding: 8px;
123
+ border-width: 1px;
124
+ border-color: rgba(0, 0, 0, 0.1);
125
+ border-radius: 6px;
126
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
127
+ }
128
+
129
+ .hljs-code-header {
130
+ display: flex;
131
+ justify-content: space-between;
132
+ padding: 3px 6px;
133
+ background-color: #9b9b9b;
134
+ color: #fff;
135
+ }
public/assets/img/cleaning_services.svg ADDED
public/assets/img/menu.svg ADDED
public/assets/js/app.js ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ const md = window.markdownit({
3
+ linkify: true,
4
+ highlight(code, lang) {
5
+ const language = hljs.getLanguage(lang) ? lang : 'plaintext';
6
+ const html = hljs.highlight(code, {language: language, ignoreIllegals: true }).value
7
+ return `<pre class="hljs-code-container my-3"><div class="hljs-code-header"><span>${language}</span><button class="hljs-copy-button">Copy</button></div><code class="hljs language-${language}">${html}</code></pre>`
8
+ },
9
+ });
10
+
11
+ new ClipboardJS('.hljs-copy-button', {
12
+ target: function(trigger) {
13
+ console.log(trigger.parentNode.nextElementSibling)
14
+ return trigger.parentNode.nextElementSibling;
15
+ }
16
+ });
17
+
18
+ async function getApiUrl() {
19
+ if (getApiUrl.url) return getApiUrl.url;
20
+ try {
21
+ const response = await fetch("/v1/chat/completions", {
22
+ method: "OPTIONS",
23
+ });
24
+ if (response.status !== 200) throw new Error();
25
+ getApiUrl.url = "/v1/chat/completions";
26
+ const corsHeaders = (
27
+ response.headers.get("Access-Control-Allow-Headers") || ""
28
+ ).toLowerCase();
29
+ getApiUrl.tokenRequired = corsHeaders.includes("authorization");
30
+ } catch (e) {
31
+ getApiUrl.url = "https://api.openai.com/v1/chat/completions";
32
+ getApiUrl.tokenRequired = true;
33
+ }
34
+ return getApiUrl.url;
35
+ }
36
+
37
+ async function postRequest(url, headers, body) {
38
+ const response = await fetch(url, {
39
+ method: "POST",
40
+ headers: headers,
41
+ body: JSON.stringify(body),
42
+ });
43
+ if (!response.ok) {
44
+ throw new Error(await response.text());
45
+ }
46
+ return response;
47
+ }
48
+
49
+ async function readStream(stream, progressCallback) {
50
+ const reader = stream.getReader();
51
+ const textDecoder = new TextDecoder('utf-8');
52
+ let responseObj = {};
53
+
54
+ while (true) {
55
+ const { done, value } = await reader.read();
56
+ if (done) break;
57
+
58
+ const lines = textDecoder.decode(value).split("\n");
59
+ processLines(lines, responseObj, progressCallback);
60
+ }
61
+
62
+ return responseObj;
63
+ }
64
+
65
+ function processLines(lines, responseObj, progressCallback) {
66
+ for (const line of lines) {
67
+ if (line.startsWith("data: ")) {
68
+ if (line.includes("[DONE]")) {
69
+ return responseObj;
70
+ }
71
+ try {
72
+ const data = JSON.parse(line.slice(6));
73
+ const delta = data.choices[0].delta;
74
+
75
+ Object.keys(delta).forEach(key => {
76
+ responseObj[key] = (responseObj[key] || "") + delta[key];
77
+ progressCallback(responseObj);
78
+ });
79
+
80
+ } catch (e) {
81
+ console.log("Error parsing line:", line);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ async function complete(messages, token, progressCallback) {
88
+ const apiUrl = await getApiUrl();
89
+ const headers = { "Content-Type": "application/json" };
90
+ if (getApiUrl.tokenRequired) {
91
+ headers.Authorization = `Bearer ${token}`;
92
+ }
93
+
94
+ const body = {
95
+ model: "gpt-3.5-turbo",
96
+ messages: messages,
97
+ stream: true,
98
+ };
99
+
100
+ const response = await postRequest(apiUrl, headers, body);
101
+ return readStream(response.body, progressCallback);
102
+ }
103
+
104
+ function chatMessage(message) {
105
+ return {
106
+ scrollToBottom() {
107
+ const chatContainer = document.getElementById('chatContainer');
108
+ const main = chatContainer.parentElement;
109
+ main.scrollTop = main.scrollHeight;
110
+ },
111
+ watchEffect(message)
112
+ {
113
+ this.$nextTick(() => {
114
+ this.$el.innerHTML = md.render(message);
115
+ this.scrollToBottom()
116
+ });
117
+ }
118
+ }
119
+ }
120
+
121
+ function chatApp() {
122
+ return {
123
+ messages: [],
124
+ newMessage: '',
125
+
126
+ init() {
127
+ this.messages = [
128
+ { role: 'system', content: 'You are a programing assistant. Answer using markdown.' }
129
+ ];
130
+
131
+ hljs.configure({
132
+ 'cssSelector' : 'pre code'
133
+ });
134
+ },
135
+
136
+ sendMessage() {
137
+ if (this.newMessage.trim() === '') return;
138
+
139
+ const userMessage = { role: 'user', content: this.newMessage };
140
+
141
+
142
+ this.messages.push(userMessage);
143
+ this.messages.push({ role: 'assistant', content: '' });
144
+ const lastMsgIndex = this.messages.length - 1;
145
+
146
+ try {
147
+ complete(
148
+ this.messages,
149
+ 'no-token',
150
+ (message) => {
151
+ if (message.content)
152
+ this.messages[lastMsgIndex].content = message.content;
153
+ }
154
+ );
155
+
156
+ } catch (error) {
157
+ console.log(error.message);
158
+ return;
159
+ } finally {
160
+ this.newMessage = '';
161
+ }
162
+ },
163
+
164
+ clearMessages() {
165
+ this.messages = [];
166
+ },
167
+ };
168
+ }
169
+
170
+
public/index.html ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="description" content="ChatGPT API Demo with Alpine.js">
6
+ <meta name="viewport" content="width=device-width, height=device-height">
7
+ <title>OpenAI Chat with Alpine.js</title>
8
+ <link rel="stylesheet" href="assets/css/style.css">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
10
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js"></script>
11
+ </head>
12
+ <body>
13
+
14
+ <div x-data="chatApp()" x-init="init()" id="app">
15
+ <header>
16
+ <span>OpenAI Chat with Alpine.js</span>
17
+ <button @click="clearMessages" class="clear"><img
18
+ src="assets/img/cleaning_services.svg"
19
+ width="24"
20
+ height="24"
21
+ title="Clear chat"
22
+ alt="Clear"></button>
23
+ </header>
24
+
25
+ <main>
26
+ <div class="chat-container" id="chatContainer">
27
+ <template x-for="(message, index) in messages" :key="index">
28
+ <template x-if="message.role != 'system'">
29
+ <div x-data="chatMessage()"
30
+ :class="[message.role, 'message'].join(' ')"
31
+ x-effect="watchEffect(message.content)"></div>
32
+ </template>
33
+ </template>
34
+ </div>
35
+ </main>
36
+
37
+ <footer>
38
+ <input id="input-box" type="text"
39
+ x-model="newMessage"
40
+ @keyup.enter="sendMessage"
41
+ placeholder="Type your message here..."
42
+ aria-label="Chat with AI">
43
+ </footer>
44
+ </div>
45
+
46
+ <script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
47
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
48
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"></script>
49
+ <script src="assets/js/app.js"></script>
50
+
51
+ </body>
52
+ </html>