Skip to content

Commit

Permalink
feat: 新增配置 Api Key 、Access Token 和 Host 列表
Browse files Browse the repository at this point in the history
  • Loading branch information
huangzonggui committed Mar 29, 2023
1 parent d340fd6 commit 5f5c4df
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 44 deletions.
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ChatGPT Tauri
ChatGPT 桌面客户端,基于 [chatgpt-web](https://github.com/Chanzhaoyu/chatgpt-web)

__该客户端仅支持 open-api 的方式使用__
__该客户端支持 open-api 和 access token 的方式使用__

## 预览

Expand All @@ -26,12 +26,7 @@ pnpm tauri build
## 感谢

- 感谢 [chatgpt-web](https://github.com/Chanzhaoyu/chatgpt-web) 项目作者以及所有该项目的贡献者。

## 赞助

如果您觉得该项目对你有帮助,可以给我打赏一杯咖啡,感谢!

<img width="320" src="docs/pay.jpg" alt="install">
- 感谢 [ChatGPT-T](https://github.com/pljhonglu/ChatGPT-T) 项目作者以及所有该项目的贡献者。

# License
MIT
MIT
3 changes: 2 additions & 1 deletion src-tauri/src/app/cmd/gpt_access_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub struct FetchOption {
pub proxy: Option<String>,
pub host: String,
pub apiKey: String,
pub accessToken: String,
pub model: String,
pub temperature: f32,

Expand Down Expand Up @@ -132,7 +133,7 @@ pub async fn fetch_chat_api_by_access_token(
.post(option.host)
.header("Accept", "text/event-stream")
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", option.apiKey))
.header("Authorization", format!("Bearer {}", option.accessToken))
.header(
reqwest::header::USER_AGENT,
format!("ChatGPT-Tauri ({})", OS),
Expand Down
6 changes: 3 additions & 3 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"distDir": "../dist"
},
"package": {
"productName": "ChatGPT-Tauri",
"productName": "ChatGPT-Tauri-App",
"version": "0.1.4"
},
"tauri": {
Expand Down Expand Up @@ -67,7 +67,7 @@
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "me.honglu.chatgpt",
"identifier": "me.hzg.chatgpt",
"longDescription": "ChatGPT Desktop Application",
"macOS": {
"entitlements": null,
Expand All @@ -77,7 +77,7 @@
"signingIdentity": null
},
"resources": [],
"shortDescription": "ChatGPT-Tauri",
"shortDescription": "ChatGPT-Tauri-App",
"targets": "all",
"windows": {
"certificateThumbprint": null,
Expand Down
2 changes: 1 addition & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function fetchChatAPIProcess(
}
}
let acceess_type = 'fetch_chat_api_by_access_token'
if (option.apiKey.startsWith('sk-'))
if (option.accessType === '0')
acceess_type = 'fetch_chat_api_by_api_key'

await invoke(acceess_type, {
Expand Down
90 changes: 88 additions & 2 deletions src/components/common/Setting/General.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { NButton, NPopconfirm, NSelect, useMessage } from 'naive-ui'
import { NButton, NInput, NInputGroup, NList, NListItem, NPopconfirm, NSelect, NTag, useMessage } from 'naive-ui'
import { checkUpdate, installUpdate } from '@tauri-apps/api/updater'
import { relaunch } from '@tauri-apps/api/process'
import type { Language, Theme } from '@/store/modules/app/helper'
import { SvgIcon } from '@/components/common'
import { useAppStore } from '@/store'
import { useAppStore, useUserStore } from '@/store'
import { getCurrentDate } from '@/utils/functions'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { t } from '@/locales'
const appStore = useAppStore()
const userStore = useUserStore()
const model = ref({
apiKey: '',
accessToken: '',
host: '',
})
const { isMobile } = useBasicLayout()
Expand Down Expand Up @@ -94,6 +101,11 @@ function clearData(): void {
location.reload()
}
function resetHost(): void {
userStore.resetHost()
model.value.host = ''
}
function handleImportButtonClick(): void {
const fileInput = document.getElementById('fileInput') as HTMLElement
if (fileInput)
Expand Down Expand Up @@ -159,6 +171,18 @@ async function checkAppUpdate() {
</template>
{{ $t('chat.clearHistoryConfirm') }}
</NPopconfirm>

<NPopconfirm placement="bottom" @positive-click="resetHost">
<template #trigger>
<NButton size="small">
<template #icon>
<SvgIcon icon="ri:close-circle-line" />
</template>
{{ '恢复默认Host设置' }}
</NButton>
</template>
{{ '确定?' }}
</NPopconfirm>
</div>
</div>
<div class="flex items-center space-x-4">
Expand Down Expand Up @@ -199,6 +223,68 @@ async function checkAppUpdate() {
</NButton>
</div>
</div>

<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">Api Key 列表</span>
<div class="flex flex-wrap items-center gap-4">
<NInputGroup>
<NInput v-model:value="model.apiKey" />
<NButton @click="userStore.addApiKey(model.apiKey)">
添加
</NButton>
</NInputGroup>
<NList>
<NListItem v-for="item in userStore.userConfig.apiKeyList" :key="item">
<NTag> {{ item }} </NTag>
<NButton @click="userStore.deleteApiKey(item)">
删除
</NButton>
</NListItem>
</NList>
</div>
</div>

<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">Access Token 列表</span>
<div class="flex flex-wrap items-center gap-4">
<NInputGroup>
<NInput v-model:value="model.accessToken" />
<NButton @click="userStore.addAccessToken(model.accessToken)">
添加
</NButton>
</NInputGroup>
<NList>
<NListItem v-for="item in userStore.userConfig.accessTokenList" :key="item">
<NTag type="info" class="mr-2">
{{ item }}
</NTag>
<NButton @click="userStore.deleteAccessToken(item)">
删除
</NButton>
</NListItem>
</NList>
</div>
</div>

<div class="flex items-center space-x-4">
<span class="flex-shrink-0 w-[100px]">Host 列表</span>
<div class="flex flex-wrap items-center gap-4">
<NInputGroup>
<NInput v-model:value="model.host" />
<NButton @click="userStore.addHost(model.host)">
添加
</NButton>
</NInputGroup>
<NList>
<NListItem v-for="item in userStore.userConfig.hostList" :key="item">
<NTag> {{ item }} </NTag>
<NButton @click="userStore.deleteHost(item)">
删除
</NButton>
</NListItem>
</NList>
</div>
</div>
</div>
</div>
</template>
62 changes: 38 additions & 24 deletions src/components/common/Setting/User.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script setup lang='ts'>
import { computed, ref } from 'vue'
import type { FormInst, FormItemRule, FormRules } from 'naive-ui'
import { NButton, NForm, NFormItem, NInput, NSelect, useMessage } from 'naive-ui'
import { NButton, NForm, NFormItem, NInput, NRadio, NRadioGroup, NSelect, useMessage } from 'naive-ui'
import { useUserStore } from '@/store'
import { t } from '@/locales'
import { genOptionByList } from '@/utils/functions'
const userStore = useUserStore()
const ms = useMessage()
Expand All @@ -15,26 +16,17 @@ const model = ref({
name: userInfo.value.name,
avatar: userInfo.value.avatar,
apiKey: userConfig.value.apiKey,
accessToken: userConfig.value.accessToken,
apiKeyList: userConfig.value.apiKeyList,
accessTokenList: userConfig.value.accessTokenList,
hostList: userConfig.value.hostList,
modelName: userConfig.value.modelName,
host: userConfig.value.host,
proxy: userConfig.value.proxy,
accessType: userConfig.value.accessType,
})
const models = userStore.allModels().map(v => ({
label: v,
value: v,
}))
const hosts = [
{
label: 'apiKey: https://api.openai.com/v1/chat/completions',
value: 'https://api.openai.com/v1/chat/completions',
},
{
label: 'accesstoken 代理:https://bypass.duti.tech/api/conversation',
value: 'https://bypass.duti.tech/api/conversation',
},
]
const models = genOptionByList(userStore.allModels())
const rules: FormRules = {
name: [
Expand Down Expand Up @@ -64,7 +56,6 @@ const rules: FormRules = {
}],
apiKey: [
{
required: true,
message: '请输入 api-key',
validator(rule: FormItemRule, value: string) {
if (!value)
Expand Down Expand Up @@ -92,6 +83,17 @@ const rules: FormRules = {
trigger: ['input', 'blur'],
},
],
accessType: [
{
required: true,
message: '选择访问方式',
validator(rule: FormItemRule, value: string) {
if (!value)
return new Error('不能为空')
return true
},
},
],
}
function saveUserInfo() {
Expand All @@ -100,9 +102,11 @@ function saveUserInfo() {
userInfo.value.name = model.value.name
userInfo.value.avatar = model.value.avatar
userConfig.value.apiKey = model.value.apiKey
userConfig.value.accessToken = model.value.accessToken
userConfig.value.modelName = model.value.modelName
userConfig.value.proxy = model.value.proxy
userConfig.value.host = model.value.host
userConfig.value.accessType = model.value.accessType
userStore.recordState()
ms.success(t('common.success'))
}
Expand All @@ -111,22 +115,32 @@ function saveUserInfo() {
</script>

<template>
<div class="p-4 space-y-5 min-h-[200px] max-h-80 overflow-auto">
<div class="p-4 space-y-5 overflow-auto">
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem path="avatar" :label="$t('setting.avatarLink')">
<NInput v-model:value="model.avatar" :placeholder="$t('setting.avatarLinkPlaceholder')" />
</NFormItem>
<NFormItem path="name" :label="$t('setting.name')">
<NInput v-model:value="model.name" :placeholder="$t('setting.namePlaceholder')" />
</NFormItem>
<NFormItem path="apiKey" label="Openai API Key Or Access Token">
<NInput v-model:value="model.apiKey" placeholder="Openai API Key" />
<NFormItem path="type" label="访问方式">
<NRadioGroup v-model:value="model.accessType">
<NRadio value="0">
API key
</NRadio>
<NRadio value="1">
Access Token
</NRadio>
</NRadioGroup>
</NFormItem>
<NFormItem path="apiKey" label="Openai API Key">
<NSelect v-model:value="model.apiKey" placeholder="Select" :options="genOptionByList(model.apiKeyList)" />
</NFormItem>
<NFormItem path="host" label="手动输入 Host">
<NInput v-model:value="model.host" placeholder="" default-value="https://bypass.duti.tech/api/conversation" />
<NFormItem path="accessToken" label="Access Token">
<NSelect v-model:value="model.accessToken" placeholder="Select" :options="genOptionByList(model.accessTokenList)" />
</NFormItem>
<NFormItem path="host" label="下拉选择 Host">
<NSelect v-model:value="model.host" placeholder="Select" :options="hosts" />
<NFormItem path="host" label="Host">
<NSelect v-model:value="model.host" placeholder="Select" :options="genOptionByList(model.hostList)" />
</NFormItem>
<NFormItem path="modelName" label="Model Name">
<NSelect v-model:value="model.modelName" placeholder="Select" :options="models" />
Expand Down
11 changes: 10 additions & 1 deletion src/store/modules/user/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ export interface UserInfo {
export interface UserConfig {
modelName: string
apiKey: string
accessToken: string
host: string
proxy: string | null
accessType: string
maxTokenNum: number
apiKeyList: string[]
accessTokenList: string[]
hostList: string[]
}

export interface UserState {
Expand All @@ -29,11 +34,15 @@ export function defaultSetting(): UserState {
},
userConfig: {
modelName: 'gpt-3.5-turbo',
accessType: '0', // 0: apiKey, 1: accessToken
apiKey: import.meta.env.VITE_GLOB_OPENAI_KEY,
accessToken: import.meta.env.VITE_GLOB_ACCESS_TOKEN,
proxy: 'socks5:https://127.0.0.1:7890',
// host: 'https://api.openai.com/v1/chat/completions',
host: 'https://bypass.duti.tech/api/conversation',
maxTokenNum: 4096,
apiKeyList: [],
accessTokenList: [],
hostList: ['https://bypass.duti.tech/api/conversation', 'https://api.openai.com/v1/chat/completions'],
},
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/store/modules/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,39 @@ export const useUserStore = defineStore('user-store', {
this.recordState()
},

addApiKey(apiKey: string) {
this.userConfig.apiKeyList.push(apiKey)
},

deleteApiKey(apiKey: string) {
this.userConfig.apiKeyList = this.userConfig.apiKeyList.filter(item => item !== apiKey)
this.recordState()
},

addAccessToken(accessToken: string) {
this.userConfig.accessTokenList.push(accessToken)
},

deleteAccessToken(accessToken: string) {
this.userConfig.accessTokenList = this.userConfig.accessTokenList.filter(item => item !== accessToken)
this.recordState()
},

addHost(host: string) {
this.userConfig.hostList.push(host)
this.recordState()
},

deleteHost(host: string) {
this.userConfig.hostList = this.userConfig.hostList.filter(item => item !== host)
this.recordState()
},

resetHost() {
this.userConfig.hostList = defaultSetting().userConfig.hostList
this.recordState()
},

resetUserInfo() {
this.userInfo = { ...defaultSetting().userInfo }
this.recordState()
Expand Down
Loading

0 comments on commit 5f5c4df

Please sign in to comment.