Browse Source

wip: entrance of ChatGPT

0xJacky 2 years ago
parent
commit
8c1cdda305

+ 1 - 0
frontend/package.json

@@ -46,6 +46,7 @@
         "unplugin-vue-components": "^0.22.12",
         "vite": "^4.1.4",
         "vite-plugin-html": "^3.2.0",
+        "vite-svg-loader": "^4.0.0",
         "vue-tsc": "^1.0.24"
     }
 }

+ 1 - 0
frontend/src/assets/svg/ChatGPT_logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2406 2406"><path d="M1 578.4C1 259.5 259.5 1 578.4 1h1249.1c319 0 577.5 258.5 577.5 577.4V2406H578.4C259.5 2406 1 2147.5 1 1828.6V578.4z" fill="#74aa9c"/><path d="M1107.3 299.1c-198 0-373.9 127.3-435.2 315.3C544.8 640.6 434.9 720.2 370.5 833c-99.3 171.4-76.6 386.9 56.4 533.8-41.1 123.1-27 257.7 38.6 369.2 98.7 172 297.3 260.2 491.6 219.2 86.1 97 209.8 152.3 339.6 151.8 198 0 373.9-127.3 435.3-315.3 127.5-26.3 237.2-105.9 301-218.5 99.9-171.4 77.2-386.9-55.8-533.9v-.6c41.1-123.1 27-257.8-38.6-369.8-98.7-171.4-297.3-259.6-491-218.6-86.6-96.8-210.5-151.8-340.3-151.2zm0 117.5-.6.6c79.7 0 156.3 27.5 217.6 78.4-2.5 1.2-7.4 4.3-11 6.1L952.8 709.3c-18.4 10.4-29.4 30-29.4 51.4V1248l-155.1-89.4V755.8c-.1-187.1 151.6-338.9 339-339.2zm434.2 141.9c121.6-.2 234 64.5 294.7 169.8 39.2 68.6 53.9 148.8 40.4 226.5-2.5-1.8-7.3-4.3-10.4-6.1l-360.4-208.2c-18.4-10.4-41-10.4-59.4 0L1024 984.2V805.4L1372.7 604c51.3-29.7 109.5-45.4 168.8-45.5zM650 743.5v427.9c0 21.4 11 40.4 29.4 51.4l421.7 243-155.7 90L597.2 1355c-162-93.8-217.4-300.9-123.8-462.8C513.1 823.6 575.5 771 650 743.5zm807.9 106 348.8 200.8c162.5 93.7 217.6 300.6 123.8 462.8l.6.6c-39.8 68.6-102.4 121.2-176.5 148.2v-428c0-21.4-11-41-29.4-51.4l-422.3-243.7 155-89.3zM1201.7 997l177.8 102.8v205.1l-177.8 102.8-177.8-102.8v-205.1L1201.7 997zm279.5 161.6 155.1 89.4v402.2c0 187.3-152 339.2-339 339.2v-.6c-79.1 0-156.3-27.6-217-78.4 2.5-1.2 8-4.3 11-6.1l360.4-207.5c18.4-10.4 30-30 29.4-51.4l.1-486.8zM1380 1421.9v178.8l-348.8 200.8c-162.5 93.1-369.6 38-463.4-123.7h.6c-39.8-68-54-148.8-40.5-226.5 2.5 1.8 7.4 4.3 10.4 6.1l360.4 208.2c18.4 10.4 41 10.4 59.4 0l421.9-243.7z" fill="white"/></svg>

+ 80 - 53
frontend/src/components/ChatGPT/ChatGPT.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import {computed, ref, watch} from 'vue'
+import {computed, onMounted, ref, watch} from 'vue'
 import {useGettext} from 'vue3-gettext'
 import {useUserStore} from '@/pinia'
 import {storeToRefs} from 'pinia'
@@ -10,12 +10,20 @@ import 'highlight.js/styles/vs2015.css'
 import {SendOutlined} from '@ant-design/icons-vue'
 import Template from '@/views/template/Template.vue'
 import openai from '@/api/openai'
+import ChatGPT_logo from '@/assets/svg/ChatGPT_logo.svg'
+import Icon from '@ant-design/icons-vue'
 
 const {$gettext} = useGettext()
 
 const props = defineProps(['content', 'path', 'history_messages'])
+const emit = defineEmits(['update:history_messages'])
+const history_messages = computed(() => props.history_messages)
 
-watch(computed(() => props.history_messages), () => {
+onMounted(() => {
+    messages.value = props.history_messages
+})
+
+watch(history_messages, () => {
     messages.value = props.history_messages
 })
 
@@ -99,6 +107,9 @@ async function request() {
 }
 
 async function send() {
+    if (!messages.value) {
+        messages.value = []
+    }
     if (messages.value.length === 0) {
         messages.value.push({
             role: 'user',
@@ -145,6 +156,7 @@ function clear_record() {
         messages: []
     })
     messages.value = []
+    emit('update:history_messages', [])
 }
 
 async function regenerate(index: number) {
@@ -154,73 +166,88 @@ async function regenerate(index: number) {
 }
 
 const editing_idx = ref(-1)
+
+const button_shape = computed(() => loading.value ? 'square' : 'circle')
 </script>
 
 <template>
-    <a-card title="ChatGPT">
+    <a-card class="chatgpt" title="ChatGPT" v-if="messages?.length>1">
         <div class="chatgpt-container">
-            <template v-if="messages?.length>0">
-                <a-list
-                    class="chatgpt-log"
-                    item-layout="horizontal"
-                    :data-source="messages"
-                >
-                    <template #renderItem="{ item, index }">
-                        <a-list-item>
-                            <a-comment :author="item.role" :avatar="item.avatar">
-                                <template #content>
-                                    <div class="content" v-if="item.role==='assistant'||editing_idx!=index"
-                                         v-html="marked.parse(item.content)"></div>
-                                    <a-input style="padding: 0" v-else v-model:value="item.content"
-                                             :bordered="false"/>
-                                </template>
-                                <template #actions>
+            <a-list
+                class="chatgpt-log"
+                item-layout="horizontal"
+                :data-source="messages"
+            >
+                <template #renderItem="{ item, index }">
+                    <a-list-item>
+                        <a-comment :author="item.role" :avatar="item.avatar">
+                            <template #content>
+                                <div class="content" v-if="item.role==='assistant'||editing_idx!=index"
+                                     v-html="marked.parse(item.content)"></div>
+                                <a-input style="padding: 0" v-else v-model:value="item.content"
+                                         :bordered="false"/>
+                            </template>
+                            <template #actions>
                                     <span v-if="item.role==='user'&&editing_idx!==index" @click="editing_idx=index">
                                         {{ $gettext('Modify') }}
                                     </span>
-                                    <template v-else-if="editing_idx==index">
-                                        <span @click="regenerate(index+1)">{{ $gettext('Save') }}</span>
-                                        <span @click="editing_idx=-1">{{ $gettext('Cancel') }}</span>
-                                    </template>
-                                    <span v-else-if="!loading" @click="regenerate(index)" :disabled="loading">
+                                <template v-else-if="editing_idx==index">
+                                    <span @click="regenerate(index+1)">{{ $gettext('Save') }}</span>
+                                    <span @click="editing_idx=-1">{{ $gettext('Cancel') }}</span>
+                                </template>
+                                <span v-else-if="!loading" @click="regenerate(index)" :disabled="loading">
                                         {{ $gettext('Reload') }}
                                     </span>
-                                </template>
-                            </a-comment>
-                        </a-list-item>
-                    </template>
-                </a-list>
-                <div class="input-msg">
-                    <div class="control-btn">
-                        <a-space v-show="!loading">
-                            <a-popconfirm
-                                :cancelText="$gettext('No')"
-                                :okText="$gettext('OK')"
-                                :title="$gettext('Are you sure you want to clear the record of chat?')"
-                                @confirm="clear_record">
-                                <a-button type="text">{{ $gettext('Clear') }}</a-button>
-                            </a-popconfirm>
-                            <a-button type="text" @click="regenerate(messages?.length-1)">
-                                {{ $gettext('Regenerate response') }}
-                            </a-button>
-                        </a-space>
-                    </div>
-                    <a-textarea auto-size v-model:value="ask_buffer"/>
-                    <div class="sned-btn">
-                        <a-button size="small" type="text" :loading="loading" @click="send">
-                            <send-outlined/>
+                            </template>
+                        </a-comment>
+                    </a-list-item>
+                </template>
+            </a-list>
+            <div class="input-msg">
+                <div class="control-btn">
+                    <a-space v-show="!loading">
+                        <a-popconfirm
+                            :cancelText="$gettext('No')"
+                            :okText="$gettext('OK')"
+                            :title="$gettext('Are you sure you want to clear the record of chat?')"
+                            @confirm="clear_record">
+                            <a-button type="text">{{ $gettext('Clear') }}</a-button>
+                        </a-popconfirm>
+                        <a-button type="text" @click="regenerate(messages?.length-1)">
+                            {{ $gettext('Regenerate response') }}
                         </a-button>
-                    </div>
+                    </a-space>
+                </div>
+                <a-textarea auto-size v-model:value="ask_buffer"/>
+                <div class="sned-btn">
+                    <a-button size="small" type="text" :loading="loading" @click="send">
+                        <send-outlined/>
+                    </a-button>
                 </div>
-            </template>
-            <template v-else>
-                <a-button @click="send">{{ $gettext('Chat with ChatGPT') }}</a-button>
-            </template>
+            </div>
         </div>
     </a-card>
+    <template v-else>
+        <div class="chat-start">
+            <a-button size="large" shape="circle" @click="send" :loading="loading">
+                <Icon v-if="!loading" :component="ChatGPT_logo"/>
+            </a-button>
+        </div>
+    </template>
 </template>
 
 <style lang="less" scoped>
+.chatgpt {
+    position: sticky;
+    top: 78px;
+}
+
+.chat-start {
+    position: fixed !important;
+    right: 36px;
+    bottom: 78px;
+}
+
 .chatgpt-container {
     margin: 0 auto;
     max-width: 800px;

+ 7 - 9
frontend/src/views/domain/DomainEdit.vue

@@ -162,10 +162,13 @@ function on_change_enabled(checked: boolean) {
         disable()
     }
 }
+
+const editor_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 16 : 24)
+const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 24)
 </script>
 <template>
     <a-row :gutter="16">
-        <a-col :xs="24" :sm="24" :md="16">
+        <a-col :xs="24" :sm="24" :md="editor_md">
             <a-card :bordered="false">
                 <template #title>
                     <span style="margin-right: 10px">{{ interpolate($gettext('Edit %{n}'), {n: name}) }}</span>
@@ -225,9 +228,9 @@ function on_change_enabled(checked: boolean) {
             </a-card>
         </a-col>
 
-        <a-col class="col-right" :xs="24" :sm="24" :md="8">
-            <chat-g-p-t class="chatgpt" :content="configText" :path="ngx_config.file_name"
-                        :history_messages="history_chatgpt_record"/>
+        <a-col class="col-right" :xs="24" :sm="24" :md="chat_md">
+            <chat-g-p-t :content="configText" :path="ngx_config.file_name"
+                        v-model:history_messages="history_chatgpt_record"/>
         </a-col>
 
         <footer-tool-bar>
@@ -250,11 +253,6 @@ function on_change_enabled(checked: boolean) {
 <style lang="less" scoped>
 .col-right {
     position: relative;
-
-    .chatgpt {
-        position: sticky;
-        top: 78px;
-    }
 }
 
 .ant-card {

+ 2 - 1
frontend/vite.config.ts

@@ -6,6 +6,7 @@ import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers'
 import {fileURLToPath, URL} from 'url'
 import vueJsx from '@vitejs/plugin-vue-jsx'
 import vitePluginBuildId from 'vite-plugin-build-id'
+import svgLoader from 'vite-svg-loader'
 
 // https://vitejs.dev/config/
 export default defineConfig({
@@ -25,7 +26,7 @@ export default defineConfig({
             '.less'
         ]
     },
-    plugins: [vue(), vueJsx(), vitePluginBuildId(),
+    plugins: [vue(), vueJsx(), vitePluginBuildId(), svgLoader(),
         Components({
             resolvers: [AntDesignVueResolver({importStyle: false})],
             directoryAsNamespace: true

+ 155 - 3
frontend/yarn.lock

@@ -864,6 +864,16 @@
     estree-walker "^2.0.2"
     source-map "^0.6.1"
 
+"@vue/compiler-core@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8"
+  integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    source-map "^0.6.1"
+
 "@vue/compiler-dom@3.2.37":
   version "3.2.37"
   resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5"
@@ -880,6 +890,14 @@
     "@vue/compiler-core" "3.2.45"
     "@vue/shared" "3.2.45"
 
+"@vue/compiler-dom@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
+  integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==
+  dependencies:
+    "@vue/compiler-core" "3.2.47"
+    "@vue/shared" "3.2.47"
+
 "@vue/compiler-sfc@3.2.37":
   version "3.2.37"
   resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4"
@@ -912,6 +930,22 @@
     postcss "^8.1.10"
     source-map "^0.6.1"
 
+"@vue/compiler-sfc@^3.2.20":
+  version "3.2.47"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d"
+  integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.47"
+    "@vue/compiler-dom" "3.2.47"
+    "@vue/compiler-ssr" "3.2.47"
+    "@vue/reactivity-transform" "3.2.47"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+    postcss "^8.1.10"
+    source-map "^0.6.1"
+
 "@vue/compiler-ssr@3.2.37":
   version "3.2.37"
   resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff"
@@ -928,6 +962,14 @@
     "@vue/compiler-dom" "3.2.45"
     "@vue/shared" "3.2.45"
 
+"@vue/compiler-ssr@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee"
+  integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==
+  dependencies:
+    "@vue/compiler-dom" "3.2.47"
+    "@vue/shared" "3.2.47"
+
 "@vue/devtools-api@^6.1.4":
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.2.1.tgz#6f2948ff002ec46df01420dfeff91de16c5b4092"
@@ -960,6 +1002,17 @@
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
 
+"@vue/reactivity-transform@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
+  integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.47"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+
 "@vue/reactivity@3.2.37":
   version "3.2.37"
   resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.37.tgz#5bc3847ac58828e2b78526e08219e0a1089f8848"
@@ -1034,6 +1087,11 @@
   resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.45.tgz#a3fffa7489eafff38d984e23d0236e230c818bc2"
   integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==
 
+"@vue/shared@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
+  integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
+
 "@zougt/some-loader-utils@^1.4.3":
   version "1.4.3"
   resolved "https://registry.yarnpkg.com/@zougt/some-loader-utils/-/some-loader-utils-1.4.3.tgz#41cf762b291ab9697f8c008bdeebaf80eaee4714"
@@ -1462,6 +1520,17 @@ css-select@^4.1.3, css-select@^4.2.1:
     domutils "^2.8.0"
     nth-check "^2.0.1"
 
+css-select@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
+  integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
+  dependencies:
+    boolbase "^1.0.0"
+    css-what "^6.1.0"
+    domhandler "^5.0.2"
+    domutils "^3.0.1"
+    nth-check "^2.0.1"
+
 css-selector-parser@^1.3:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.4.1.tgz#03f9cb8a81c3e5ab2c51684557d5aaf6d2569759"
@@ -1475,7 +1544,23 @@ css-tree@^1.1.2, css-tree@^1.1.3:
     mdn-data "2.0.14"
     source-map "^0.6.1"
 
-css-what@^6.0.1:
+css-tree@^2.2.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
+  integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==
+  dependencies:
+    mdn-data "2.0.30"
+    source-map-js "^1.0.1"
+
+css-tree@~2.2.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032"
+  integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==
+  dependencies:
+    mdn-data "2.0.28"
+    source-map-js "^1.0.1"
+
+css-what@^6.0.1, css-what@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
   integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
@@ -1551,6 +1636,13 @@ csso@^4.2.0:
   dependencies:
     css-tree "^1.1.2"
 
+csso@^5.0.5:
+  version "5.0.5"
+  resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6"
+  integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==
+  dependencies:
+    css-tree "~2.2.0"
+
 csstype@^2.6.8:
   version "2.6.20"
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda"
@@ -1609,7 +1701,16 @@ dom-serializer@^1.0.1:
     domhandler "^4.2.0"
     entities "^2.0.0"
 
-domelementtype@^2.0.1, domelementtype@^2.2.0:
+dom-serializer@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+  integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+  dependencies:
+    domelementtype "^2.3.0"
+    domhandler "^5.0.2"
+    entities "^4.2.0"
+
+domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
   integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@@ -1621,6 +1722,13 @@ domhandler@^4.2.0, domhandler@^4.3.1:
   dependencies:
     domelementtype "^2.2.0"
 
+domhandler@^5.0.1, domhandler@^5.0.2:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+  integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+  dependencies:
+    domelementtype "^2.3.0"
+
 domutils@^2.8.0:
   version "2.8.0"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
@@ -1630,6 +1738,15 @@ domutils@^2.8.0:
     domelementtype "^2.2.0"
     domhandler "^4.2.0"
 
+domutils@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
+  integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==
+  dependencies:
+    dom-serializer "^2.0.0"
+    domelementtype "^2.3.0"
+    domhandler "^5.0.1"
+
 dot-case@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
@@ -1670,6 +1787,11 @@ entities@^2.0.0:
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
   integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
 
+entities@^4.2.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
+  integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
+
 errno@^0.1.1:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
@@ -2168,6 +2290,16 @@ mdn-data@2.0.14:
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
   integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
 
+mdn-data@2.0.28:
+  version "2.0.28"
+  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba"
+  integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==
+
+mdn-data@2.0.30:
+  version "2.0.30"
+  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
+  integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
+
 merge2@^1.3.0:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@@ -2791,7 +2923,7 @@ sortablejs@1.14.0:
   resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
   integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
 
-source-map-js@^1.0.2:
+source-map-js@^1.0.1, source-map-js@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
   integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@@ -2924,6 +3056,18 @@ svgo@^2.7.0:
     picocolors "^1.0.0"
     stable "^0.1.8"
 
+svgo@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.0.2.tgz#5e99eeea42c68ee0dc46aa16da093838c262fe0a"
+  integrity sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==
+  dependencies:
+    "@trysound/sax" "0.2.0"
+    commander "^7.2.0"
+    css-select "^5.1.0"
+    css-tree "^2.2.1"
+    csso "^5.0.5"
+    picocolors "^1.0.0"
+
 terser@^5.10.0:
   version "5.14.2"
   resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
@@ -3051,6 +3195,14 @@ vite-plugin-html@^3.2.0:
     node-html-parser "^5.3.3"
     pathe "^0.2.0"
 
+vite-svg-loader@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/vite-svg-loader/-/vite-svg-loader-4.0.0.tgz#1cec4337dba3c23ab13bcabb111896e251b047ac"
+  integrity sha512-0MMf1yzzSYlV4MGePsLVAOqXsbF5IVxbn4EEzqRnWxTQl8BJg/cfwIzfQNmNQxZp5XXwd4kyRKF1LytuHZTnqA==
+  dependencies:
+    "@vue/compiler-sfc" "^3.2.20"
+    svgo "^3.0.2"
+
 vite@^4.0.4:
   version "4.0.4"
   resolved "https://registry.npmmirror.com/vite/-/vite-4.0.4.tgz#4612ce0b47bbb233a887a54a4ae0c6e240a0da31"

+ 148 - 148
server/api/openai.go

@@ -1,160 +1,160 @@
 package api
 
 import (
-    "context"
-    "fmt"
-    "github.com/0xJacky/Nginx-UI/server/model"
-    "github.com/0xJacky/Nginx-UI/server/query"
-    "github.com/0xJacky/Nginx-UI/server/settings"
-    "github.com/gin-gonic/gin"
-    "github.com/pkg/errors"
-    "github.com/sashabaranov/go-openai"
-    "io"
-    "log"
-    "net/http"
-    "net/url"
-    "os"
+	"context"
+	"fmt"
+	"github.com/0xJacky/Nginx-UI/server/model"
+	"github.com/0xJacky/Nginx-UI/server/query"
+	"github.com/0xJacky/Nginx-UI/server/settings"
+	"github.com/gin-gonic/gin"
+	"github.com/pkg/errors"
+	"github.com/sashabaranov/go-openai"
+	"io"
+	"log"
+	"net/http"
+	"net/url"
+	"os"
 )
 
 const ChatGPTInitPrompt = "You are a assistant who can help users write and optimise the configurations of Nginx, the first user message contains the content of the configuration file which is currently opened by the user and the current language code(CLC). You suppose to use the language corresponding to the CLC to give the first reply. Later the language environment depends on the user message. The first reply should involve the key information of the file and ask user what can you help them."
 
 func MakeChatCompletionRequest(c *gin.Context) {
-    var json struct {
-        Messages []openai.ChatCompletionMessage `json:"messages"`
-    }
-
-    if !BindAndValid(c, &json) {
-        return
-    }
-
-    messages := []openai.ChatCompletionMessage{
-        {
-            Role:    openai.ChatMessageRoleSystem,
-            Content: ChatGPTInitPrompt,
-        },
-    }
-    messages = append(messages, json.Messages...)
-    // sse server
-    c.Writer.Header().Set("Content-Type", "text/event-stream")
-    c.Writer.Header().Set("Cache-Control", "no-cache")
-    c.Writer.Header().Set("Connection", "keep-alive")
-    c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
-    log.Println(settings.OpenAISettings.Token)
-
-    config := openai.DefaultConfig(settings.OpenAISettings.Token)
-
-    if settings.OpenAISettings.Proxy != "" {
-        proxyUrl, err := url.Parse(settings.OpenAISettings.Proxy)
-        if err != nil {
-            c.Stream(func(w io.Writer) bool {
-                c.SSEvent("message", gin.H{
-                    "type":    "error",
-                    "content": err.Error(),
-                })
-                return false
-            })
-            return
-        }
-        transport := &http.Transport{
-            Proxy: http.ProxyURL(proxyUrl),
-        }
-        config.HTTPClient = &http.Client{
-            Transport: transport,
-        }
-    }
-
-    if settings.OpenAISettings.BaseUrl != "" {
-        config.BaseURL = settings.OpenAISettings.BaseUrl
-    }
-
-    openaiClient := openai.NewClientWithConfig(config)
-    ctx := context.Background()
-
-    req := openai.ChatCompletionRequest{
-        Model:    settings.OpenAISettings.Model,
-        Messages: messages,
-        Stream:   true,
-    }
-    stream, err := openaiClient.CreateChatCompletionStream(ctx, req)
-    if err != nil {
-        fmt.Printf("CompletionStream error: %v\n", err)
-        c.Stream(func(w io.Writer) bool {
-            c.SSEvent("message", gin.H{
-                "type":    "error",
-                "content": err.Error(),
-            })
-            return false
-        })
-        return
-    }
-    defer stream.Close()
-    msgChan := make(chan string)
-    go func() {
-        for {
-            response, err := stream.Recv()
-            if errors.Is(err, io.EOF) {
-                close(msgChan)
-                fmt.Println()
-                return
-            }
-
-            if err != nil {
-                fmt.Printf("Stream error: %v\n", err)
-                close(msgChan)
-                return
-            }
-
-            // Send SSE to client
-            message := fmt.Sprintf("%s", response.Choices[0].Delta.Content)
-            fmt.Printf("%s", response.Choices[0].Delta.Content)
-            _ = os.Stdout.Sync()
-
-            msgChan <- message
-        }
-    }()
-
-    c.Stream(func(w io.Writer) bool {
-        if m, ok := <-msgChan; ok {
-            c.SSEvent("message", gin.H{
-                "type":    "message",
-                "content": m,
-            })
-            return true
-        }
-        return false
-    })
+	var json struct {
+		Messages []openai.ChatCompletionMessage `json:"messages"`
+	}
+
+	if !BindAndValid(c, &json) {
+		return
+	}
+
+	messages := []openai.ChatCompletionMessage{
+		{
+			Role:    openai.ChatMessageRoleSystem,
+			Content: ChatGPTInitPrompt,
+		},
+	}
+	messages = append(messages, json.Messages...)
+	// sse server
+	c.Writer.Header().Set("Content-Type", "text/event-stream")
+	c.Writer.Header().Set("Cache-Control", "no-cache")
+	c.Writer.Header().Set("Connection", "keep-alive")
+	c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+	log.Println(settings.OpenAISettings.Token)
+
+	config := openai.DefaultConfig(settings.OpenAISettings.Token)
+
+	if settings.OpenAISettings.Proxy != "" {
+		proxyUrl, err := url.Parse(settings.OpenAISettings.Proxy)
+		if err != nil {
+			c.Stream(func(w io.Writer) bool {
+				c.SSEvent("message", gin.H{
+					"type":    "error",
+					"content": err.Error(),
+				})
+				return false
+			})
+			return
+		}
+		transport := &http.Transport{
+			Proxy: http.ProxyURL(proxyUrl),
+		}
+		config.HTTPClient = &http.Client{
+			Transport: transport,
+		}
+	}
+
+	if settings.OpenAISettings.BaseUrl != "" {
+		config.BaseURL = settings.OpenAISettings.BaseUrl
+	}
+
+	openaiClient := openai.NewClientWithConfig(config)
+	ctx := context.Background()
+
+	req := openai.ChatCompletionRequest{
+		Model:    settings.OpenAISettings.Model,
+		Messages: messages,
+		Stream:   true,
+	}
+	stream, err := openaiClient.CreateChatCompletionStream(ctx, req)
+	if err != nil {
+		fmt.Printf("CompletionStream error: %v\n", err)
+		c.Stream(func(w io.Writer) bool {
+			c.SSEvent("message", gin.H{
+				"type":    "error",
+				"content": err.Error(),
+			})
+			return false
+		})
+		return
+	}
+	defer stream.Close()
+	msgChan := make(chan string)
+	go func() {
+		for {
+			response, err := stream.Recv()
+			if errors.Is(err, io.EOF) {
+				close(msgChan)
+				fmt.Println()
+				return
+			}
+
+			if err != nil {
+				fmt.Printf("Stream error: %v\n", err)
+				close(msgChan)
+				return
+			}
+
+			// Send SSE to client
+			message := fmt.Sprintf("%s", response.Choices[0].Delta.Content)
+			fmt.Printf("%s", response.Choices[0].Delta.Content)
+			_ = os.Stdout.Sync()
+
+			msgChan <- message
+		}
+	}()
+
+	c.Stream(func(w io.Writer) bool {
+		if m, ok := <-msgChan; ok {
+			c.SSEvent("message", gin.H{
+				"type":    "message",
+				"content": m,
+			})
+			return true
+		}
+		return false
+	})
 }
 
 func StoreChatGPTRecord(c *gin.Context) {
-    var json struct {
-        FileName string                         `json:"file_name"`
-        Messages []openai.ChatCompletionMessage `json:"messages"`
-    }
-
-    if !BindAndValid(c, &json) {
-        return
-    }
-
-    name := json.FileName
-    g := query.ChatGPTLog
-    _, err := g.Where(g.Name.Eq(name)).FirstOrCreate()
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    _, err = g.Where(g.Name.Eq(name)).Updates(&model.ChatGPTLog{
-        Name:    name,
-        Content: json.Messages,
-    })
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-    })
+	var json struct {
+		FileName string                         `json:"file_name"`
+		Messages []openai.ChatCompletionMessage `json:"messages"`
+	}
+
+	if !BindAndValid(c, &json) {
+		return
+	}
+
+	name := json.FileName
+	g := query.ChatGPTLog
+	_, err := g.Where(g.Name.Eq(name)).FirstOrCreate()
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	_, err = g.Where(g.Name.Eq(name)).Updates(&model.ChatGPTLog{
+		Name:    name,
+		Content: json.Messages,
+	})
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+	})
 }

+ 35 - 35
server/api/settings.go

@@ -1,45 +1,45 @@
 package api
 
 import (
-    "github.com/0xJacky/Nginx-UI/server/settings"
-    "github.com/gin-gonic/gin"
-    "net/http"
+	"github.com/0xJacky/Nginx-UI/server/settings"
+	"github.com/gin-gonic/gin"
+	"net/http"
 )
 
 func GetSettings(c *gin.Context) {
-    c.JSON(http.StatusOK, gin.H{
-        "server":    settings.ServerSettings,
-        "nginx_log": settings.NginxLogSettings,
-        "openai":    settings.OpenAISettings,
-    })
+	c.JSON(http.StatusOK, gin.H{
+		"server":    settings.ServerSettings,
+		"nginx_log": settings.NginxLogSettings,
+		"openai":    settings.OpenAISettings,
+	})
 }
 
 func SaveSettings(c *gin.Context) {
-    var json struct {
-        Server   settings.Server   `json:"server"`
-        NginxLog settings.NginxLog `json:"nginx_log"`
-        Openai   settings.OpenAI   `json:"openai"`
-    }
-
-    if !BindAndValid(c, &json) {
-        return
-    }
-
-    settings.Conf.Section("server").Key("Email").SetValue(json.Server.Email)
-    settings.Conf.Section("server").Key("HTTPChallengePort").SetValue(json.Server.HTTPChallengePort)
-    settings.Conf.Section("nginx_log").Key("AccessLogPath").SetValue(json.NginxLog.AccessLogPath)
-    settings.Conf.Section("nginx_log").Key("ErrorLogPath").SetValue(json.NginxLog.ErrorLogPath)
-
-    settings.Conf.Section("openai").Key("Model").SetValue(json.Openai.Model)
-    settings.Conf.Section("openai").Key("BaseUrl").SetValue(json.Openai.BaseUrl)
-    settings.Conf.Section("openai").Key("Proxy").SetValue(json.Openai.Proxy)
-    settings.Conf.Section("openai").Key("Token").SetValue(json.Openai.Token)
-
-    err := settings.Save()
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    GetSettings(c)
+	var json struct {
+		Server   settings.Server   `json:"server"`
+		NginxLog settings.NginxLog `json:"nginx_log"`
+		Openai   settings.OpenAI   `json:"openai"`
+	}
+
+	if !BindAndValid(c, &json) {
+		return
+	}
+
+	settings.Conf.Section("server").Key("Email").SetValue(json.Server.Email)
+	settings.Conf.Section("server").Key("HTTPChallengePort").SetValue(json.Server.HTTPChallengePort)
+	settings.Conf.Section("nginx_log").Key("AccessLogPath").SetValue(json.NginxLog.AccessLogPath)
+	settings.Conf.Section("nginx_log").Key("ErrorLogPath").SetValue(json.NginxLog.ErrorLogPath)
+
+	settings.Conf.Section("openai").Key("Model").SetValue(json.Openai.Model)
+	settings.Conf.Section("openai").Key("BaseUrl").SetValue(json.Openai.BaseUrl)
+	settings.Conf.Section("openai").Key("Proxy").SetValue(json.Openai.Proxy)
+	settings.Conf.Section("openai").Key("Token").SetValue(json.Openai.Token)
+
+	err := settings.Save()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	GetSettings(c)
 }