chore: migrated to xsai
This commit is contained in:
@@ -39,6 +39,8 @@
|
||||
"@unocss/reset": "^0.65.0",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"@vueuse/head": "^2.0.0",
|
||||
"@xsai/shared-chat-completion": "^0.0.13",
|
||||
"@xsai/stream-text": "^0.0.13",
|
||||
"ai": "^4.0.10",
|
||||
"nprogress": "^0.2.0",
|
||||
"ofetch": "^1.4.1",
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
CoreAssistantMessage,
|
||||
CoreSystemMessage,
|
||||
CoreUserMessage,
|
||||
} from 'ai'
|
||||
import type { AssistantMessage, Message, SystemMessage } from '@xsai/shared-chat-completion'
|
||||
|
||||
import type {
|
||||
Emotion,
|
||||
} from '../constants/emotions'
|
||||
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
@@ -42,7 +38,6 @@ const { elevenLabsApiKey, openAiApiBaseURL, openAiApiKey } = storeToRefs(useSett
|
||||
const openAIModel = useLocalStorage<{ id: string, name?: string }>('openai-model', { id: 'openai/gpt-3.5-turbo', name: 'OpenAI GPT3.5 Turbo' })
|
||||
|
||||
const {
|
||||
setupOpenAI,
|
||||
streamSpeech,
|
||||
stream,
|
||||
models,
|
||||
@@ -54,8 +49,8 @@ const listening = ref(false)
|
||||
const live2DViewerRef = ref<{ setMotion: (motionName: string) => Promise<void> }>()
|
||||
const supportedModels = ref<{ id: string, name?: string }[]>([])
|
||||
const messageInput = ref<string>('')
|
||||
const messages = ref<Array<CoreAssistantMessage | CoreUserMessage | CoreSystemMessage>>([SystemPromptV2 as CoreSystemMessage])
|
||||
const streamingMessage = ref<CoreAssistantMessage>({ role: 'assistant', content: '' })
|
||||
const messages = ref<Array<Message>>([SystemPromptV2 as SystemMessage])
|
||||
const streamingMessage = ref<AssistantMessage>({ role: 'assistant', content: '' })
|
||||
const audioAnalyser = ref<AnalyserNode>()
|
||||
const mouthOpenSize = ref(0)
|
||||
const nowSpeaking = ref(false)
|
||||
@@ -183,6 +178,25 @@ function setupAnalyser() {
|
||||
audioAnalyser.value = audioContext.createAnalyser()
|
||||
}
|
||||
|
||||
async function* asyncIteratorFromReadableStream<T, F = Uint8Array>(res: ReadableStream<F>, func: (value: F) => Promise<T>): AsyncGenerator<T, void, unknown> {
|
||||
// react js - TS2504: Type 'ReadableStream<Uint8Array>' must have a '[Symbol.asyncIterator]()' method that returns an async iterator - Stack Overflow
|
||||
// https://stackoverflow.com/questions/76700924/ts2504-type-readablestreamuint8array-must-have-a-symbol-asynciterator
|
||||
const reader = res.getReader()
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
return
|
||||
}
|
||||
|
||||
yield func(value)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
|
||||
async function onSendMessage(sendingMessage: string) {
|
||||
if (!sendingMessage)
|
||||
return
|
||||
@@ -196,7 +210,7 @@ async function onSendMessage(sendingMessage: string) {
|
||||
// const index = messages.value.length - 1
|
||||
live2DViewerRef.value?.setMotion(EmotionThinkMotionName)
|
||||
|
||||
const res = await stream(model.value, messages.value.slice(0, messages.value.length - 1))
|
||||
const res = await stream(openAiApiBaseURL.value, openAiApiKey.value, model.value, messages.value.slice(0, messages.value.length - 1))
|
||||
|
||||
enum States {
|
||||
Literal = 'literal',
|
||||
@@ -206,7 +220,7 @@ async function onSendMessage(sendingMessage: string) {
|
||||
let state = States.Literal
|
||||
let buffer = ''
|
||||
|
||||
for await (const textPart of res.textStream) {
|
||||
for await (const textPart of asyncIteratorFromReadableStream(res.textStream, async v => v)) {
|
||||
for (const textSingleChar of textPart) {
|
||||
let newState: States = state
|
||||
|
||||
@@ -243,25 +257,20 @@ async function onSendMessage(sendingMessage: string) {
|
||||
}
|
||||
|
||||
watch([openAiApiBaseURL, openAiApiKey], async ([baseUrl, apiKey]) => {
|
||||
setupOpenAI({
|
||||
apiKey,
|
||||
baseURL: baseUrl,
|
||||
})
|
||||
if (!baseUrl || !apiKey) {
|
||||
supportedModels.value = []
|
||||
return
|
||||
}
|
||||
|
||||
const fetchedModels = await models()
|
||||
const fetchedModels = await models(baseUrl, apiKey)
|
||||
supportedModels.value = fetchedModels.data
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
if (!openAiApiKey.value)
|
||||
if (!openAiApiBaseURL.value || !openAiApiKey.value)
|
||||
return
|
||||
|
||||
setupOpenAI({
|
||||
apiKey: openAiApiKey.value,
|
||||
baseURL: openAiApiBaseURL.value,
|
||||
})
|
||||
|
||||
const fetchedModels = await models()
|
||||
const fetchedModels = await models(openAiApiBaseURL.value, openAiApiKey.value)
|
||||
supportedModels.value = fetchedModels.data
|
||||
})
|
||||
|
||||
|
||||
@@ -1,38 +1,25 @@
|
||||
import type { GenerateAudioStream } from '@airi-proj/elevenlabs/types'
|
||||
import type { CoreMessage } from 'ai'
|
||||
import { createOpenAI, type OpenAIProvider, type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||
import { streamText } from 'ai'
|
||||
import type { Message } from '@xsai/shared-chat-completion'
|
||||
import { streamText } from '@xsai/stream-text'
|
||||
import { ofetch } from 'ofetch'
|
||||
import { OpenAI } from 'openai'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useLLM = defineStore('llm', () => {
|
||||
const openAI = ref<OpenAI>()
|
||||
const openAIProvider = ref<OpenAIProvider>()
|
||||
|
||||
function setupOpenAI(options: OpenAIProviderSettings) {
|
||||
openAI.value = new OpenAI({ ...options, dangerouslyAllowBrowser: true })
|
||||
openAIProvider.value = createOpenAI(options)
|
||||
}
|
||||
|
||||
async function stream(model: string, messages: CoreMessage[]) {
|
||||
if (!openAIProvider.value)
|
||||
throw new Error('OpenAI not initialized')
|
||||
if (openAI.value?.baseURL === '') {
|
||||
throw new Error('OpenAI not initialized')
|
||||
}
|
||||
|
||||
async function stream(apiUrl: string, apiKey: string, model: string, messages: Message[]) {
|
||||
return await streamText({
|
||||
model: openAIProvider.value(model),
|
||||
url: `${apiUrl}/chat/completions`,
|
||||
apiKey,
|
||||
model,
|
||||
messages,
|
||||
streamOptions: {
|
||||
usage: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function models() {
|
||||
if (!openAI.value)
|
||||
throw new Error('OpenAI not initialized')
|
||||
if (openAI.value?.baseURL === '') {
|
||||
async function models(apiUrl: string, apiKey: string) {
|
||||
if (apiUrl === '') {
|
||||
return {
|
||||
data: [],
|
||||
object: '',
|
||||
@@ -40,7 +27,13 @@ export const useLLM = defineStore('llm', () => {
|
||||
}
|
||||
|
||||
try {
|
||||
return await openAI.value.models.list()
|
||||
const openai = new OpenAI({
|
||||
apiKey,
|
||||
baseURL: apiUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
})
|
||||
|
||||
return await openai.models.list()
|
||||
}
|
||||
catch (err) {
|
||||
if (String(err).includes(`Failed to construct 'URL': Invalid URL`)) {
|
||||
@@ -74,8 +67,6 @@ export const useLLM = defineStore('llm', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
setupOpenAI,
|
||||
openAI,
|
||||
models,
|
||||
stream,
|
||||
streamSpeech,
|
||||
|
||||
Generated
+25
@@ -165,6 +165,12 @@ importers:
|
||||
'@vueuse/head':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(vue@3.5.13(typescript@5.7.2))
|
||||
'@xsai/shared-chat-completion':
|
||||
specifier: ^0.0.13
|
||||
version: 0.0.13
|
||||
'@xsai/stream-text':
|
||||
specifier: ^0.0.13
|
||||
version: 0.0.13
|
||||
ai:
|
||||
specifier: ^4.0.10
|
||||
version: 4.0.10(react@18.3.1)(zod@3.23.8)
|
||||
@@ -2802,6 +2808,15 @@ packages:
|
||||
'@webgpu/types@0.1.51':
|
||||
resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==}
|
||||
|
||||
'@xsai/shared-chat-completion@0.0.13':
|
||||
resolution: {integrity: sha512-dChG1QWeLYm6ElBEn9sCASHfQMdDctx/QSlcXus7erNdS5ZDnhY78tQBg023jdxICcoX6xwNhvMJtcq4qiFNHA==}
|
||||
|
||||
'@xsai/shared@0.0.13':
|
||||
resolution: {integrity: sha512-rPyf1dEU0MsRMwSLJek+w6DIe2iuXjk07VRJ8q2/FSq7JY1LPCOa/OXT9lTwG317+zq2+p8IR6nzwK+VFDZpjQ==}
|
||||
|
||||
'@xsai/stream-text@0.0.13':
|
||||
resolution: {integrity: sha512-ySJnUMjhmSdPHiojuCjyMw25lYZu0v+pbRap4akvUnyslKcKI9yIdKK3Rt+JRm645VBQmFquJ7mYUC6eDii6yw==}
|
||||
|
||||
abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
engines: {node: '>=6.5'}
|
||||
@@ -9601,6 +9616,16 @@ snapshots:
|
||||
|
||||
'@webgpu/types@0.1.51': {}
|
||||
|
||||
'@xsai/shared-chat-completion@0.0.13':
|
||||
dependencies:
|
||||
'@xsai/shared': 0.0.13
|
||||
|
||||
'@xsai/shared@0.0.13': {}
|
||||
|
||||
'@xsai/stream-text@0.0.13':
|
||||
dependencies:
|
||||
'@xsai/shared-chat-completion': 0.0.13
|
||||
|
||||
abort-controller@3.0.0:
|
||||
dependencies:
|
||||
event-target-shim: 5.0.1
|
||||
|
||||
Reference in New Issue
Block a user