+
+
+
+
+
+
{{ name }} {{ config?.mode === 'completions' ? '(无上下文)' : '' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.message }}
+
+
-
-
+
+
+
+
+ {{ item.message ? item.message : '...' }}
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/js/config.js b/src/js/config.js
new file mode 100644
index 0000000..cdd5a36
--- /dev/null
+++ b/src/js/config.js
@@ -0,0 +1,16 @@
+export default {
+ getData () {
+ return {
+ "code": 200,
+ "data": {
+ "id": 128897,
+ "name": "小羊驼",
+ "create_time": 1684920670901,
+ "app": "llm_rep",
+ "resource_type": "app",
+ "ext": "{\"mode\": \"chat\", \"model\": \"chatglm2-6b\", \"api_url\": \"https://gpu-pod647d498393e106496a046e94-8000.node.inscode.run/v1\", \"api_type\": \"openai\", \"robot_img\": null, \"api_max_token\": \"2048\", \"default_prompt\": \"请翻译成英文:你是谁?\", \"max_request_len\": \"10000\", \"prompt_template\": \"\", \"api_prompt_prefix\": \"\", \"show_profile_setting\": false}"
+ },
+ "message": "success"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/js/openai.js b/src/js/openai.js
new file mode 100644
index 0000000..733b496
--- /dev/null
+++ b/src/js/openai.js
@@ -0,0 +1,127 @@
+import { fetchEventSource } from '@microsoft/fetch-event-source';
+import Prompt from './prompt.js'
+
+class OpenAI {
+
+ constructor(config) {
+ this.config = config
+ this.abortController = null
+ this.callback = null
+ this.temperature = parseFloat(config?.temperature??0.7)
+ }
+
+ createCompletion (prompt, history, context, callback) {
+ const config = this.config
+
+ const abortController = new AbortController();
+ const signal = abortController.signal;
+ this.abortController = abortController
+
+ this.callback = callback
+
+ const mode = config?.mode??'chat'
+ const token = config?.token??'empty'
+ const url = config.api_url + (mode === 'chat' ? '/chat/completions' : '/completions')
+ const stop = config?.stop??'[DONE]'
+ const max_tokens = config?.api_max_token??512
+ const model = config?.model??'vicuna-13b-all-v1.1'
+ const temperature = this.temperature
+ const top_p = config?.top_p??1.0
+ let stop_key = config?.stop_key??null
+ if (stop_key !== null && stop_key !== '') {
+ stop_key = stop_key.split(';;')
+ }
+ const data = {
+ model: model,
+ max_tokens: parseInt(max_tokens),
+ temperature: parseFloat(temperature),
+ top_p: parseFloat(top_p),
+ stream: true,
+ stop: stop_key
+ // prefix: prefix
+ }
+ if (mode === 'chat') {
+ data.messages = Prompt.getPromptByChatMode(config, context, history)
+ } else {
+ data.prompt = Prompt.getPromptByTemplate(config, context, prompt)
+ }
+ // const prefix = config?.prompt_prefix??''
+ const fetcher = fetchEventSource(url, {
+ method: 'POST',
+ signal: signal,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ onmessage(msg) {
+ // if the server emits an error message, throw an exception
+ // so it gets handled by the onerror callback below:
+ if (msg && msg?.data) {
+ if (msg?.data === stop) {
+ if (callback?.onclose) {
+ callback?.onclose()
+ }
+ abortController.abort();
+ return
+
+ } else {
+ console.info(msg.data)
+ const jsonData = JSON.parse(msg.data)
+ // 和上面重复触发,只留一个
+ // if (jsonData.choices[0].finish_reason === 'stop') {
+ // if (callback?.onclose) {
+ // callback?.onclose()
+ // }
+ // return
+ // }
+ let message = null
+ if (mode === 'chat') {
+ message = jsonData?.choices[0]?.message?.content
+ if (typeof message === 'undefined') {
+ message = jsonData?.choices[0]?.delta?.content
+ }
+ if (typeof message === 'undefined') {
+ message = ''
+ }
+ } else {
+ message = jsonData?.choices[0]?.text
+ }
+
+ callback?.onmessage(message, true)
+ }
+
+ }
+
+
+ // if (msg.event === 'FatalError') {
+ // throw new FatalError(msg.data);
+ // }
+ },
+ onclose() {
+ if (callback?.onclose) {
+ callback?.onclose()
+ }
+ // if the server closes the connection unexpectedly, retry:
+ },
+ onerror(err) {
+ if (callback?.onerror) {
+ callback?.onerror(err)
+ }
+ }
+ });
+
+ }
+
+ close () {
+ if (this.abortController) {
+ this.abortController.abort()
+
+ if (this.callback && this.callback?.onclose) {
+ this.callback.onclose()
+ }
+ }
+ }
+
+}
+
+export default OpenAI
\ No newline at end of file
diff --git a/src/js/prompt.js b/src/js/prompt.js
new file mode 100644
index 0000000..ef95387
--- /dev/null
+++ b/src/js/prompt.js
@@ -0,0 +1,59 @@
+const default_max_token = 1024
+
+const getContextContent = (context, max_token=default_max_token) => {
+ if (context && context.length > 0) {
+ let id = 0
+ let len = context.length
+ let contextContent = ''
+ while(contextContent.length < max_token && id < len) {
+ if (context[id].page_content.length + contextContent.length < max_token) {
+ contextContent = contextContent + '\n' + context[id].page_content
+ }
+ id++
+ }
+ return contextContent
+ } else {
+ return ''
+ }
+}
+
+export default {
+
+ getPromptByTemplate: (config, context, prompt, history) => {
+
+ if (config.api_prompt_prefix) {
+ prompt = config?.api_prompt_prefix + ' ' + prompt
+ }
+ if (config?.prompt_template) {
+
+ const contextContent = getContextContent(context, config?.max_request_len??1024)
+ return config?.prompt_template.replace(/\{question\}/ig, prompt).replace(/\{context\}/ig, contextContent)
+ } else {
+ return prompt
+ }
+ },
+ getPromptByChatMode (config, context, history) {
+
+ const history_length = Math.min(Math.max(parseInt(config?.history_length??4), 4), 10)
+ let message = []
+ if (history && history.length >= 2) {
+ const end = history.length - 2 // 结束位置
+ const start = Math.max(history.length - 2 - history_length + 1, 0) // 开始位置
+ for(let id = start; id <= end; id++) {
+ const item = history[id]
+ message.push({
+ "role": item.user === 'AI' ? "system" : "user",
+ "content": item.message
+ })
+ }
+ }
+ if (config?.prompt_template) {
+ const contextContent = getContextContent(context, config?.max_request_len??1024)
+ message.unshift({
+ "role": "user",
+ "content": config.prompt_template.replace(/\{question\}/ig, '').replace(/\{context\}/ig, contextContent).replace(/\{user_call_name\}/ig, config.user_call_name)
+ })
+ }
+ return message
+ }
+}
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 90e6400..c84568c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -2,5 +2,6 @@ import { createApp } from 'vue'
import App from './App.vue'
import './assets/main.css'
+import './style.css'
createApp(App).mount('#app')
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 0000000..bd6213e
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
\ No newline at end of file
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..55d1fb9
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,12 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ "./index.html",
+ "./src/**/*.{vue,js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
+
--
GitLab