Skip to content

Commit

Permalink
feat: 添加角色设定预留API 设定页(Chanzhaoyu#768)
Browse files Browse the repository at this point in the history
* add systemMessage

* perf: 优化代码和类型

* perf: 补全翻译和为以后做准备

---------

Co-authored-by: ChenZhaoYu <[email protected]>
  • Loading branch information
2 people authored and Equim-chan committed Mar 22, 2023
1 parent 824b7a9 commit adc3eca
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 22 deletions.
25 changes: 14 additions & 11 deletions service/src/chatgpt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import axios from 'axios'
import { sendResponse } from '../utils'
import { isNotEmptyString } from '../utils/is'
import type { ApiModel, ChatContext, ChatGPTUnofficialProxyAPIOptions, ModelConfig } from '../types'
import type { RequestOptions } from './types'

dotenv.config()

const ErrorCodeMessage: Record<string, string> = {
401: '[OpenAI] 提供错误的API密钥 | Incorrect API key provided',
Expand All @@ -19,21 +22,19 @@ const ErrorCodeMessage: Record<string, string> = {
500: '[OpenAI] 服务器繁忙,请稍后再试 | Internal Server Error',
}

dotenv.config()

const timeoutMs: number = !isNaN(+process.env.TIMEOUT_MS) ? +process.env.TIMEOUT_MS : 30 * 1000

let apiModel: ApiModel

if (!process.env.OPENAI_API_KEY && !process.env.OPENAI_ACCESS_TOKEN)
if (!isNotEmptyString(process.env.OPENAI_API_KEY) && !isNotEmptyString(process.env.OPENAI_ACCESS_TOKEN))
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable')

let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI

(async () => {
// More Info: https://github.com/transitive-bullshit/chatgpt-api

if (process.env.OPENAI_API_KEY) {
if (isNotEmptyString(process.env.OPENAI_API_KEY)) {
const OPENAI_API_MODEL = process.env.OPENAI_API_MODEL
const model = isNotEmptyString(OPENAI_API_MODEL) ? OPENAI_API_MODEL : 'gpt-3.5-turbo'

Expand Down Expand Up @@ -80,17 +81,19 @@ let api: ChatGPTAPI | ChatGPTUnofficialProxyAPI
}
})()

async function chatReplyProcess(
message: string,
lastContext?: { conversationId?: string; parentMessageId?: string },
process?: (chat: ChatMessage) => void,
) {
async function chatReplyProcess(options: RequestOptions) {
const { message, lastContext, process, systemMessage } = options
try {
let options: SendMessageOptions = { timeoutMs }

if (lastContext) {
if (apiModel === 'ChatGPTAPI') {
if (isNotEmptyString(systemMessage))
options.systemMessage = systemMessage
}

if (lastContext != null) {
if (apiModel === 'ChatGPTAPI')
options = { parentMessageId: lastContext.parentMessageId }
options.parentMessageId = lastContext.parentMessageId
else
options = { ...lastContext }
}
Expand Down
8 changes: 8 additions & 0 deletions service/src/chatgpt/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ChatMessage } from 'chatgpt'

export interface RequestOptions {
message: string
lastContext?: { conversationId?: string; parentMessageId?: string }
process?: (chat: ChatMessage) => void
systemMessage?: string
}
16 changes: 11 additions & 5 deletions service/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from 'express'
import type { ChatContext, ChatMessage } from './chatgpt'
import type { RequestProps } from './types'
import type { ChatMessage } from './chatgpt'
import { chatConfig, chatReplyProcess, currentModel } from './chatgpt'
import { auth } from './middleware/auth'
import { limiter } from './middleware/limiter'
Expand All @@ -22,11 +23,16 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
res.setHeader('Content-type', 'text/event-stream; charset=utf-8')

try {
const { prompt, options = {} } = req.body as { prompt: string; options?: ChatContext }
const { prompt, options = {}, systemMessage } = req.body as RequestProps
let firstChunk = true
await chatReplyProcess(prompt, options, (chat: ChatMessage) => {
res.write(firstChunk ? JSON.stringify(chat) : `\n${JSON.stringify(chat)}`)
firstChunk = false
await chatReplyProcess({
message: prompt,
lastContext: options,
process: (chat: ChatMessage) => {
res.write(firstChunk ? JSON.stringify(chat) : `\n${JSON.stringify(chat)}`)
firstChunk = false
},
systemMessage,
})
}
catch (error) {
Expand Down
6 changes: 6 additions & 0 deletions service/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { FetchFn } from 'chatgpt'

export interface RequestProps {
prompt: string
options?: ChatContext
systemMessage: string
}

export interface ChatContext {
conversationId?: string
parentMessageId?: string
Expand Down
5 changes: 4 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
import { post } from '@/utils/request'
import { useSettingStore } from '@/store'

export function fetchChatAPI<T = any>(
prompt: string,
Expand All @@ -26,9 +27,11 @@ export function fetchChatAPIProcess<T = any>(
signal?: GenericAbortSignal
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void },
) {
const settingStore = useSettingStore()

return post<T>({
url: '/chat-process',
data: { prompt: params.prompt, options: params.options },
data: { prompt: params.prompt, options: params.options, systemMessage: settingStore.systemMessage },
signal: params.signal,
onDownloadProgress: params.onDownloadProgress,
})
Expand Down
46 changes: 46 additions & 0 deletions src/components/common/Setting/Advanced.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { NButton, NInput, useMessage } from 'naive-ui'
import { useSettingStore } from '@/store'
import type { SettingsState } from '@/store/modules/settings/helper'
import { t } from '@/locales'
const settingStore = useSettingStore()
const ms = useMessage()
const systemMessage = ref(settingStore.systemMessage ?? '')
function updateSettings(options: Partial<SettingsState>) {
settingStore.updateSetting(options)
ms.success(t('common.success'))
}
function handleReset() {
settingStore.resetSetting()
ms.success(t('common.success'))
window.location.reload()
}
</script>

<template>
<div class="p-4 space-y-5 min-h-[200px]">
<div class="space-y-6">
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.role') }}</span>
<div class="flex-1">
<NInput v-model:value="systemMessage" placeholder="" />
</div>
<NButton size="tiny" text type="primary" @click="updateSettings({ systemMessage })">
{{ $t('common.save') }}
</NButton>
</div>
<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">&nbsp;</span>
<NButton size="small" @click="handleReset">
{{ $t('common.reset') }}
</NButton>
</div>
</div>
</div>
</template>
1 change: 0 additions & 1 deletion src/components/common/Setting/General.vue
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ function handleImportButtonClick(): void {
{{ $t('common.save') }}
</NButton>
</div>

<div
class="flex items-center space-x-4"
:class="isMobile && 'items-start'"
Expand Down
23 changes: 19 additions & 4 deletions src/components/common/Setting/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
import { computed, ref } from 'vue'
import { NModal, NTabPane, NTabs } from 'naive-ui'
import General from './General.vue'
import Advanced from './Advanced.vue'
import About from './About.vue'
import { useAuthStore } from '@/store'
import { SvgIcon } from '@/components/common'
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
interface Props {
visible: boolean
}
Expand All @@ -17,6 +15,14 @@ interface Emit {
(e: 'update:visible', visible: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const authStore = useAuthStore()
const isChatGPTAPI = computed<boolean>(() => !!authStore.isChatGPTAPI)
const active = ref('General')
const show = computed({
Expand All @@ -42,6 +48,15 @@ const show = computed({
<General />
</div>
</NTabPane>
<NTabPane v-if="isChatGPTAPI" name="Advanced" tab="Advanced">
<template #tab>
<SvgIcon class="text-lg" icon="ri:equalizer-line" />
<span class="ml-2">{{ $t('setting.advanced') }}</span>
</template>
<div class="min-h-[100px]">
<Advanced />
</div>
</NTabPane>
<NTabPane name="Config" tab="Config">
<template #tab>
<SvgIcon class="text-lg" icon="ri:list-settings-line" />
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ export default {
setting: {
setting: 'Setting',
general: 'General',
advanced: 'Advanced',
config: 'Config',
avatarLink: 'Avatar Link',
name: 'Name',
description: 'Description',
role: 'Role',
resetUserInfo: 'Reset UserInfo',
chatHistory: 'ChatHistory',
theme: 'Theme',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ export default {
setting: {
setting: '设置',
general: '总览',
advanced: '高级',
config: '配置',
avatarLink: '头像链接',
name: '名称',
description: '描述',
role: '角色设定',
resetUserInfo: '重置用户信息',
chatHistory: '聊天记录',
theme: '主题',
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ export default {
setting: {
setting: '設定',
general: '總覽',
advanced: '高級',
config: '設定',
avatarLink: '頭貼連結',
name: '名稱',
description: '描述',
role: '角色設定',
resetUserInfo: '重設使用者資訊',
chatHistory: '紀錄',
theme: '主題',
Expand Down
1 change: 1 addition & 0 deletions src/store/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './app'
export * from './chat'
export * from './user'
export * from './prompt'
export * from './settings'
export * from './auth'
23 changes: 23 additions & 0 deletions src/store/modules/settings/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ss } from '@/utils/storage'

const LOCAL_NAME = 'settingsStorage'

export interface SettingsState {
systemMessage: string
}

export function defaultSetting(): SettingsState {
const currentDate = new Date().toISOString().split('T')[0]
return {
systemMessage: `You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.\nKnowledge cutoff: 2021-09-01\nCurrent date: ${currentDate}`,
}
}

export function getLocalState(): SettingsState {
const localSetting: SettingsState | undefined = ss.get(LOCAL_NAME)
return { ...defaultSetting(), ...localSetting }
}

export function setLocalState(setting: SettingsState): void {
ss.set(LOCAL_NAME, setting)
}
22 changes: 22 additions & 0 deletions src/store/modules/settings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineStore } from 'pinia'
import type { SettingsState } from './helper'
import { defaultSetting, getLocalState, setLocalState } from './helper'

export const useSettingStore = defineStore('setting-store', {
state: (): SettingsState => getLocalState(),
actions: {
updateSetting(settings: Partial<SettingsState>) {
this.$state = { ...this.$state, ...settings }
this.recordState()
},

resetSetting() {
this.$state = defaultSetting()
this.recordState()
},

recordState() {
setLocalState(this.$state)
},
},
})

0 comments on commit adc3eca

Please sign in to comment.