wip: entrance of ChatGPT

This commit is contained in:
0xJacky 2023-03-22 00:10:37 +08:00
parent af1be98575
commit 8c1cdda305
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
8 changed files with 407 additions and 227 deletions

View file

@ -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"
}
}

View file

@ -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>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -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>
</template>
<template v-else>
<a-button @click="send">{{ $gettext('Chat with ChatGPT') }}</a-button>
</template>
<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>
</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;

View file

@ -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 {

View file

@ -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

View file

@ -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"

View file

@ -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"`
}
var json struct {
Messages []openai.ChatCompletionMessage `json:"messages"`
}
if !BindAndValid(c, &json) {
return
}
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)
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)
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.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
}
if settings.OpenAISettings.BaseUrl != "" {
config.BaseURL = settings.OpenAISettings.BaseUrl
}
openaiClient := openai.NewClientWithConfig(config)
ctx := context.Background()
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
}
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
}
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()
// 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
}
}()
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
})
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"`
}
var json struct {
FileName string `json:"file_name"`
Messages []openai.ChatCompletionMessage `json:"messages"`
}
if !BindAndValid(c, &json) {
return
}
if !BindAndValid(c, &json) {
return
}
name := json.FileName
g := query.ChatGPTLog
_, err := g.Where(g.Name.Eq(name)).FirstOrCreate()
name := json.FileName
g := query.ChatGPTLog
_, err := g.Where(g.Name.Eq(name)).FirstOrCreate()
if err != nil {
ErrHandler(c, err)
return
}
if err != nil {
ErrHandler(c, err)
return
}
_, err = g.Where(g.Name.Eq(name)).Updates(&model.ChatGPTLog{
Name: name,
Content: json.Messages,
})
_, err = g.Where(g.Name.Eq(name)).Updates(&model.ChatGPTLog{
Name: name,
Content: json.Messages,
})
if err != nil {
ErrHandler(c, err)
return
}
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}

View file

@ -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"`
}
var json struct {
Server settings.Server `json:"server"`
NginxLog settings.NginxLog `json:"nginx_log"`
Openai settings.OpenAI `json:"openai"`
}
if !BindAndValid(c, &json) {
return
}
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("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)
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
}
err := settings.Save()
if err != nil {
ErrHandler(c, err)
return
}
GetSettings(c)
GetSettings(c)
}