forked from 0xJacky/nginx-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
3,269 additions
and
601 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import http from '@/lib/http' | ||
|
||
const openai = { | ||
store_record(data: any) { | ||
return http.post('/chat_gpt_record', data) | ||
} | ||
} | ||
|
||
export default openai |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
<script setup lang="ts"> | ||
import {computed, ref, watch} from 'vue' | ||
import {useGettext} from 'vue3-gettext' | ||
import {useUserStore} from '@/pinia' | ||
import {storeToRefs} from 'pinia' | ||
import {urlJoin} from '@/lib/helper' | ||
import {marked} from 'marked' | ||
import hljs from 'highlight.js' | ||
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' | ||
const {$gettext} = useGettext() | ||
const props = defineProps(['content', 'path', 'history_messages']) | ||
watch(computed(() => props.history_messages), () => { | ||
messages.value = props.history_messages | ||
}) | ||
const {current} = useGettext() | ||
const messages: any = ref([]) | ||
const loading = ref(false) | ||
const ask_buffer = ref('') | ||
async function send() { | ||
if (messages.value.length === 0) { | ||
messages.value.push({ | ||
role: 'user', | ||
content: props.content + '\n\nCurrent Language Code: ' + current | ||
}) | ||
} else { | ||
messages.value.push({ | ||
role: 'user', | ||
content: ask_buffer.value | ||
}) | ||
ask_buffer.value = '' | ||
} | ||
loading.value = true | ||
const t = ref({ | ||
role: 'assistant', | ||
content: '' | ||
}) | ||
const user = useUserStore() | ||
const {token} = storeToRefs(user) | ||
console.log('fetching...') | ||
let res = await fetch(urlJoin(window.location.pathname, '/api/chat_gpt'), { | ||
method: 'POST', | ||
headers: {'Accept': 'text/event-stream', Authorization: token.value}, | ||
body: JSON.stringify({messages: messages.value}) | ||
}) | ||
messages.value.push(t.value) | ||
// read body as stream | ||
console.log('reading...') | ||
let reader = res.body!.getReader() | ||
// read stream | ||
console.log('reading stream...') | ||
let buffer = '' | ||
while (true) { | ||
let {done, value} = await reader.read() | ||
if (done) { | ||
console.log('done') | ||
loading.value = false | ||
store_record() | ||
break | ||
} | ||
apply(value) | ||
} | ||
function apply(input: any) { | ||
const decoder = new TextDecoder('utf-8') | ||
const raw = decoder.decode(input) | ||
const regex = /{"content":"(.+?)"}/g | ||
const matches = raw.match(regex) | ||
matches?.forEach(v => { | ||
const content = JSON.parse(v).content | ||
for (let c of content) { | ||
buffer += c | ||
if (isCodeBlockComplete(buffer)) { | ||
t.value.content = buffer | ||
} else { | ||
t.value.content = buffer + '\n```' | ||
} | ||
} | ||
}) | ||
} | ||
function isCodeBlockComplete(text: string) { | ||
const codeBlockRegex = /```/g | ||
const matches = text.match(codeBlockRegex) | ||
if (matches) { | ||
return matches.length % 2 === 0 | ||
} else { | ||
return true | ||
} | ||
} | ||
} | ||
const renderer = new marked.Renderer() | ||
renderer.code = (code, lang: string) => { | ||
const language = hljs.getLanguage(lang) ? lang : 'nginx' | ||
const highlightedCode = hljs.highlight(code, {language}).value | ||
return `<pre><code class="hljs ${language}">${highlightedCode}</code></pre>` | ||
} | ||
marked.setOptions({ | ||
renderer: renderer, | ||
langPrefix: 'hljs language-', // highlight.js css expects a top-level 'hljs' class. | ||
pedantic: false, | ||
gfm: true, | ||
breaks: false, | ||
sanitize: false, | ||
smartypants: true, | ||
xhtml: false | ||
}) | ||
function store_record() { | ||
openai.store_record({ | ||
file_name: props.path, | ||
messages: messages.value | ||
}) | ||
} | ||
</script> | ||
|
||
<template> | ||
<a-card title="ChatGPT"> | ||
<div class="chatgpt-container"> | ||
<template v-if="messages?.length>0"> | ||
<a-list | ||
class="chatgpt-log" | ||
item-layout="horizontal" | ||
:data-source="messages" | ||
> | ||
<template #renderItem="{ item }"> | ||
<a-list-item> | ||
<a-comment :author="item.role" :avatar="item.avatar"> | ||
<template #content> | ||
<div class="content" v-html="marked.parse(item.content)"></div> | ||
</template> | ||
</a-comment> | ||
</a-list-item> | ||
</template> | ||
</a-list> | ||
<div class="input-msg"> | ||
<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> | ||
</template> | ||
<template v-else> | ||
<a-button @click="send">{{ $gettext('Chat with ChatGPT') }}</a-button> | ||
</template> | ||
</div> | ||
</a-card> | ||
</template> | ||
|
||
<style lang="less" scoped> | ||
.chatgpt-container { | ||
margin: 0 auto; | ||
max-width: 800px; | ||
.chatgpt-log { | ||
.content { | ||
width: 100%; | ||
:deep(.hljs) { | ||
border-radius: 5px; | ||
} | ||
} | ||
:deep(.ant-comment-content) { | ||
width: 100%; | ||
} | ||
:deep(.ant-comment) { | ||
width: 100%; | ||
} | ||
:deep(.ant-comment-content-detail) { | ||
width: 100%; | ||
p { | ||
margin-bottom: 10px; | ||
} | ||
} | ||
:deep(.ant-list-item:first-child) { | ||
display: none; | ||
} | ||
} | ||
.input-msg { | ||
position: relative; | ||
.sned-btn { | ||
position: absolute; | ||
right: 0; | ||
bottom: 3px; | ||
} | ||
} | ||
} | ||
</style> |
Oops, something went wrong.