Change the model cache to local storage and support caching multiple models

This commit is contained in:
Rafi
2023-02-12 12:40:47 +08:00
parent 2f2ed46911
commit edde4112c1
7 changed files with 179 additions and 55 deletions

11
app.vue
View File

@@ -1,8 +1,8 @@
<script setup> <script setup>
import { fetchEventSource } from '@microsoft/fetch-event-source' import { fetchEventSource } from '@microsoft/fetch-event-source'
import ApiKeyEditor from "./components/ApiKeyEditor";
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const currentModel = useCurrentModel()
const fetchingResponse = ref(false) const fetchingResponse = ref(false)
const fetchReply = async (message, parentMessageId) => { const fetchReply = async (message, parentMessageId) => {
const ctrl = new AbortController() const ctrl = new AbortController()
@@ -14,6 +14,7 @@ const fetchReply = async (message, parentMessageId) => {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
model: currentModel.value,
message: message, message: message,
parentMessageId: parentMessageId, parentMessageId: parentMessageId,
conversationId: currentConversation.value.id conversationId: currentConversation.value.id
@@ -114,18 +115,18 @@ onNuxtReady(() => {
:theme="theme" :theme="theme"
> >
<v-navigation-drawer <v-navigation-drawer
theme="dark"
permanent permanent
> >
<v-list> <v-list>
<ModelNameEditor/> <ClientOnly>
<ApiKeyEditor/> <ModelDialog/>
</ClientOnly>
</v-list> </v-list>
<template v-slot:append> <template v-slot:append>
<v-divider></v-divider> <v-divider></v-divider>
<v-list> <v-list>
<!-- <v-list-item title="Clear conversations"></v-list-item>--> <ApiKeyEditor/>
<v-list-item <v-list-item
:prepend-icon="theme === 'light' ? 'dark_mode' : 'light_mode'" :prepend-icon="theme === 'light' ? 'dark_mode' : 'light_mode'"
:title="(theme === 'light' ? 'Dark' : 'Light') + ' mode'" :title="(theme === 'light' ? 'Dark' : 'Light') + ' mode'"

121
components/ModelDialog.vue Normal file
View File

@@ -0,0 +1,121 @@
<template>
<v-dialog
v-model="dialog"
persistent
scrollable
>
<template v-slot:activator="{ props }">
<v-list-item
rounded="xl"
v-bind="props"
prepend-icon="smart_toy"
active
color="primary"
>
{{ currentModel }}
</v-list-item>
</template>
<v-card>
<v-card-title>
<span class="text-h5">OpenAI Models</span>
<div>
About the models:
<a target="_blank" href="https://platform.openai.com/docs/models/overview">https://platform.openai.com/docs/models/overview</a>
</div>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<div
v-for="(model, index) in models"
:key="index"
class="d-flex align-center"
>
<v-switch
v-model="currentModel"
color="primary"
:label="model"
:value="model"
hide-details
></v-switch>
<v-icon
icon="delete_outline"
@click="removeModel(index)"
></v-icon>
</div>
<div>
<v-btn
variant="outlined"
v-if="!showInputModel"
@click="showInputModel = true"
>
Add a model
</v-btn>
<div
v-else
class="d-flex align-center"
>
<v-text-field
v-model="inputModel"
label="Enter a model name"
hide-details
></v-text-field>
<v-btn class="ml-3" icon="done" @click="createNewModel"></v-btn>
<v-btn class="ml-3" icon="close" @click="showInputModel = false"></v-btn>
</div>
</div>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-alert
v-if="warningText"
density="compact"
type="warning"
:text="warningText"
></v-alert>
<v-spacer></v-spacer>
<v-btn
color="primary"
@click="save"
>
Save & Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup>
const dialog = ref(false)
const models = useModels()
const currentModel = useCurrentModel()
const inputModel = ref('')
const showInputModel = ref(false)
const warningText = ref(null)
const showWarning = (text) => {
warningText.value = text
setTimeout(() => {
warningText.value = null
}, 3000)
}
const createNewModel = () => {
models.value.push(inputModel.value)
inputModel.value = ''
showInputModel.value = false
}
const removeModel = (index) => {
if (currentModel.value === models.value[index]) {
currentModel.value = null
}
models.value.splice(index, 1)
}
const save = async () => {
console.log(currentModel.value)
if (!currentModel.value) {
showWarning('Please select at least one model.')
return
}
setModels(models.value)
setCurrentModel(currentModel.value)
dialog.value = false
}
</script>

View File

@@ -1,48 +0,0 @@
<template>
<v-list-item v-if="showModelNameEditor">
<v-text-field
label="Model name"
v-model="modelNameInput"
hide-details
variant="outlined"
></v-text-field>
<template v-slot:append>
<v-icon icon="done" size="small" @click="submitModelName"></v-icon>
<v-icon icon="close" size="small" @click="showModelNameEditor = false"></v-icon>
</template>
</v-list-item>
<v-list-item
v-else
:title="currentModelName"
subtitle="Current model"
>
<template v-slot:append>
<v-icon icon="edit" @click="showModelNameEditor = true"></v-icon>
</template>
</v-list-item>
</template>
<script setup>
const { data } = await useFetch('/api/settings/?key=modelName')
const currentModelName = ref(data.value.data)
const modelNameInput = ref(currentModelName.value)
const showModelNameEditor = ref(false)
const submitModelName = async () => {
try {
const { data } = await useFetch('/api/settings', {
method: 'POST',
body: { key: 'modelName', value: modelNameInput.value }
})
if (data.value.status === 'success') {
currentModelName.value = modelNameInput.value
showModelNameEditor.value = false
}
} catch (e) {
console.log(e)
}
}
</script>
<style scoped>
</style>

3
composables/states.js Normal file
View File

@@ -0,0 +1,3 @@
export const useModels = () => useState('models', () => getStoredModels())
export const useCurrentModel = () => useState('currentModel', () => getCurrentModel())

View File

@@ -38,7 +38,6 @@ export default defineEventHandler(async (event) => {
'Connection': 'keep-alive' 'Connection': 'keep-alive'
}) })
const modelName = await getSetting('modelName')
const apiKey = await getSetting('apiKey') const apiKey = await getSetting('apiKey')
if (!apiKey) { if (!apiKey) {
@@ -57,7 +56,7 @@ export default defineEventHandler(async (event) => {
modelOptions: { modelOptions: {
// The model is set to text-chat-davinci-002-20221122 by default, but you can override // The model is set to text-chat-davinci-002-20221122 by default, but you can override
// it and any other parameters here // it and any other parameters here
model: modelName, model: body.model,
}, },
// (Optional) Set custom instructions instead of "You are ChatGPT...". // (Optional) Set custom instructions instead of "You are ChatGPT...".
// promptPrefix: 'You are Bob, a cowboy in Western times...', // promptPrefix: 'You are Bob, a cowboy in Western times...',

5
utils/enums.js Normal file
View File

@@ -0,0 +1,5 @@
export const STORAGE_KEY = {
OPENAI_MODELS: 'openai_models',
CURRENT_OPENAI_MODEL: 'current_openai_model',
}

43
utils/localStorage.js Normal file
View File

@@ -0,0 +1,43 @@
import {useCurrentModel, useModels} from "~/composables/states";
const get = (key) => {
let val = localStorage.getItem(key)
if (val) {
val = JSON.parse(val)
}
return val
}
const set = (key, val) => {
localStorage.setItem(key, JSON.stringify(val))
}
const DEFAULT_OPENAI_MODEL = 'text-davinci-003'
export const setModels = (val) => {
const models = useModels()
set(STORAGE_KEY.OPENAI_MODELS, val)
models.value = val
}
export const getStoredModels = () => {
let models = get(STORAGE_KEY.OPENAI_MODELS)
if (!models) {
models = [DEFAULT_OPENAI_MODEL]
}
return models
}
export const setCurrentModel = (val) => {
const model = useCurrentModel()
set(STORAGE_KEY.CURRENT_OPENAI_MODEL, val)
model.value = val
}
export const getCurrentModel = () => {
let model = get(STORAGE_KEY.CURRENT_OPENAI_MODEL)
if (!model) {
model = DEFAULT_OPENAI_MODEL
}
return model
}