Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support image generate #116

Merged
merged 3 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add ai generate image
  • Loading branch information
orangelckc committed Mar 27, 2023
commit 26e2eefe1be58dfd33e31e821f905582fa723e37
2 changes: 2 additions & 0 deletions .eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
"executeSQL": true,
"generalKeys": true,
"getActivePinia": true,
"getAiIamge": true,
"getAiMessage": true,
"getContributorsApi": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"getKeySymbol": true,
"getMemoryList": true,
"getOpenAICreditApi": true,
"getOpenAIImage": true,
"getOpenAIKey": true,
"getOpenAIResultApi": true,
"getOpenAIResultStreamApi": true,
Expand Down
22 changes: 21 additions & 1 deletion src/api/openAi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
fetchEventSource,
type EventSourceMessage
} from '@microsoft/fetch-event-source'
import type { MessageData } from '@/types'
import type { MessageData, ImageData } from '@/types'

/**
* 获取 openai 对话消息
Expand Down Expand Up @@ -126,3 +126,23 @@ export const getOpenAICreditApi = async () => {
}
}
}

/**
* 根据提示创建图像
* @param messages 图像参数
*/
export const getOpenAIImage = async (imageData: ImageData) => {
if (!imageData) return

const apiKey = getOpenAIKey()
if (!apiKey) return

return await request('/v1/images/generations', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
HostUrl: HOST_URL.OPENAI
},
body: Body.json(imageData)
})
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码补丁主要有两个改动:

  1. 引入了类型 ImageData,用于描述图像数据。
  2. 添加了导出函数 getOpenAIImage,用于根据提示生成图像。

代码本身没有明显的 bug 风险,但是可以考虑一些改进:

  1. 在 getOpenAIImage 函数中,如果没有传入图像数据,则直接返回。这样做可能会使调用者不知道该函数是否成功执行,建议增加一个异常抛出或者返回错误信息,以便提高代码可读性和鲁棒性。
  2. 在函数 getOpenAICreditApi 中,存在一个潜在的 bug 风险,即 CreditResponse 结构体中的 credit_remaining 属性可能为 null。建议增加一个判空操作或者给 credit_remaining 属性设置默认值,以避免空指针异常。
  3. 可以考虑优化 request 函数的实现方式,使用 axios 等第三方库来发送 HTTP 请求,同时也能够提供更多的配置选项和拦截器功能,提高代码的可扩展性和可维护性。

50 changes: 30 additions & 20 deletions src/components/Input/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import { appWindow } from '@tauri-apps/api/window'

const recordStore = useSessionStore()
const { isThinking } = storeToRefs(recordStore)
const { isThinking, currentSession } = storeToRefs(recordStore)

const roleStore = useRoleStore()
const { currentRole, isEdit, textAreaValue, popoverVisible } =
const { currentRole, isEdit, textAreaValue, imageValue, popoverVisible } =
storeToRefs(roleStore)

const textAreaElement = ref<HTMLTextAreaElement | null>(null)
Expand All @@ -25,11 +25,10 @@ const onKeydown = (event: KeyboardEvent) => {

if (!value) return

// if (popoverVisible) {
popoverVisible.value = false
// }

getAiMessage(value)
if (currentSession.value?.type === 'text') getAiMessage(value)
else getAiIamge(value)

textAreaElement.value?.blur()
textAreaValue.value = ''
Expand Down Expand Up @@ -60,21 +59,32 @@ onMounted(() => {
<template>
<div class="app-input flex items-center gap-2">
<RoleList />

<a-textarea
ref="textAreaElement"
class="bordered bg-transparent!"
:class="!textAreaValue && 'rounded-10'"
:placeholder="isThinking ? 'AI 正在思考...' : '有什么问题尽管问我'"
v-model="textAreaValue"
:disabled="isThinking || isEdit"
:auto-size="{
minRows: 1,
maxRows: 5
}"
clearable
@keydown="onKeydown"
></a-textarea>
<div class="flex w-full flex-col">
<a-textarea
ref="textAreaElement"
class="bordered bg-transparent!"
:class="!textAreaValue && 'rounded-10'"
:placeholder="isThinking ? 'AI 正在思考...' : '有什么问题尽管问我'"
v-model="textAreaValue"
:disabled="isThinking || isEdit"
:auto-size="{
minRows: 1,
maxRows: 5
}"
clearable
@keydown="onKeydown"
></a-textarea>
<div class="flex w-full" v-if="currentSession?.type === 'image'">
<a-select placeholder="选择生成的尺寸" v-model="imageValue.size">
<a-option>256x256</a-option>
<a-option>512x512</a-option>
<a-option>1024x1024</a-option>
</a-select>
<a-select placeholder="选择生成的数量" v-model="imageValue.number">
<a-option v-for="i in 10" :key="i">{{ i + '' }}</a-option>
</a-select>
</div>
</div>
</div>
</template>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码主要是关于一个聊天界面的交互部分,其中包含了输入框以及相关的一些处理逻辑。以下是我对这段代码的简要评估:

  • 在第 4 行,增加了一个名为 currentSession 的变量。但是没有看到该变量定义在哪里,可能需要确认该变量的来源。
  • 第 7-10 行定义了一个 roleStore 变量,其中的 textAreaValuepopoverVisible 属性的类型和名称与原来的不同,需要确认是否有必要更改属性的名称。
  • 在第 14 行,定义了一个组件中的 textAreaElement 变量,但是并没有使用它,可能是多余的代码。
  • 在第 21-24 行,当用户按下回车键时会执行 getAiMessage(value)getAiIamge(value) 函数。但是没有看到这两个函数的定义,需要确认其实现是否正确。
  • 在第 31-42 行,增加了一个 div 元素用来处理图片生成的逻辑。建议将其封装成一个单独的组件,使得代码更易读和管理。

除此之外,这份代码看起来比较简洁和易懂,没有明显的错误和潜在风险。如果你能提供更多上下文和其他文件的话,我们可以做更深入的评估和优化建议。

Expand Down
7 changes: 6 additions & 1 deletion src/sqls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const executeSQL = async (sql: string, hideError = false) => {
export const initSQL = async () => {
await executeSQL(
`
CREATE TABLE IF NOT EXISTS session (id TEXT, title TEXT, role_id INTEGER, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
CREATE TABLE IF NOT EXISTS session (id TEXT, title TEXT, role_id INTEGER, type TEXT, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
CREATE TABLE IF NOT EXISTS session_data (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, message TEXT, is_ask INTEGER, is_memory INTEGER, message_type TEXT, time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
CREATE TABLE IF NOT EXISTS role (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, is_default INTEGER DEFAULT false);
CREATE TABLE IF NOT EXISTS credit (id INTEGER PRIMARY KEY AUTOINCREMENT, history_id INTEGER, token_cost INTEGER, api_key TEXT);
Expand All @@ -81,6 +81,11 @@ export const initSQL = async () => {
`ALTER TABLE session ADD COLUMN update_time TIMESTAMP DEFAULT ${Date.now()};`,
true
)
// 2. 2023-03-27 在 session 表中添加 type 列,记录对话的类型
await executeSQL(
`ALTER TABLE session ADD COLUMN type TEXT DEFAULT 'text';`,
true
)
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/stores/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const useRoleStore = defineStore(
const popoverVisible = ref(false)
// 检索角色列表的输入框值
const textAreaValue = ref('')
const imageValue = reactive({
number: '1',
size: '256x256'
})
// 是否有角色正在编辑
const isEdit = computed(() => roleList.value.some((item) => item.isEdit))

Expand Down Expand Up @@ -118,6 +122,7 @@ export const useRoleStore = defineStore(
filterRoleList,
popoverVisible,
textAreaValue,
imageValue,
isEdit,
getRoleList,
getFilterRoleList,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码看起来很简洁,没有明显的 bug 风险。这里有两个改进建议:

  1. useRoleStore 中使用 defineStore API 时,请确保在此之前已经安装了 @vue/runtime-core 模块,否则会抛出错误。

  2. 对于 imageValue,如果您打算将其用作全局状态,则可以使用 ref 替换 reactive,因为它只是一个简单的对象,并不需要响应式转换。

Expand Down
32 changes: 22 additions & 10 deletions src/stores/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export const useSessionStore = defineStore(
currentSession.value = {
id: crypto.randomUUID(),
title: '',
role_id: defaultRole.id
role_id: defaultRole.id,
type: 'image'
}
}

Expand All @@ -75,19 +76,30 @@ export const useSessionStore = defineStore(
await insertSQL('session', {
id: currentSession.value.id,
title: data.content,
role_id: currentRole.id
role_id: currentRole.id,
type: currentSession.value.type
})
}

const { isMemory } = useSettingsStore()
if (messageType === 'text') {
const { isMemory } = useSettingsStore()

await insertSQL('session_data', {
session_id: currentSession.value.id,
is_ask: isAsk,
is_memory: isMemory,
message: data,
message_type: messageType
})
await insertSQL('session_data', {
session_id: currentSession.value.id,
is_ask: isAsk,
is_memory: isMemory,
message: data,
message_type: messageType
})
} else if (messageType === 'image') {
await insertSQL('session_data', {
session_id: currentSession.value.id,
is_ask: isAsk,
is_memory: false,
message: data,
message_type: messageType
})
}

await getSessionData()
}
Expand Down
7 changes: 7 additions & 0 deletions src/types/sql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export interface MessageData {
role: Role
content: string
}

export interface ImageData {
n: number
prompt: string
size: '256x256' | '512x512' | '1024x1024'
}
export interface RolePayload {
id?: number
name: string
Expand Down Expand Up @@ -33,6 +39,7 @@ export interface SessionPayload {
role_id: number
update_time?: string
name?: string
type: 'text' | 'image'
isEdit?: boolean
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码主要是 TypeScript 中定义了一些类型和接口。以下是我的一些建议:

  1. ImageData 接口中的 n 属性名不太直观,可以改为更有描述性的名称。
  2. MessageData 接口中的 content 属性应该有一个明确的类型定义,而不是使用 any
  3. SessionPayload 接口中新增的 type: MessageType 属性没有定义,需要在其他地方定义或者补充上来。
  4. 没有看到具体的实现代码,无法准确判断是否存在潜在的 bug 风险。

最后,建议添加更多注释以增强代码可读性。

Expand Down
68 changes: 68 additions & 0 deletions src/utils/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,71 @@ export const getAiMessage = async (value?: string) => {
isThinking.value = false
}
}

/**
* 获取 ai 作图
* @param value 消息内容
*/
export const getAiIamge = async (value?: string) => {
const { isThinking, sessionDataList } = storeToRefs(useSessionStore())
const { updateSessionData } = useSessionStore()

try {
// 获取图像的记忆模式 = 按照上一张图去修改生成
let imageData

if (!value) {
// 重复上一次提问
const { sessionDataList } = useSessionStore()

const lastQuestion = sessionDataList.filter((item) => item.is_ask).at(-1)
if (!lastQuestion) return

const deleteSql = `DELETE FROM session_data WHERE session_id = '${lastQuestion?.session_id}' AND id >= ${lastQuestion?.id};`
await executeSQL(deleteSql)

imageData = JSON.parse(lastQuestion?.message.content as any)
} else {
// 添加正常提问
const { imageValue } = useRoleStore()

imageData = {
n: parseInt(imageValue.number),
size: imageValue.size,
prompt: value
}
}

const { addSessionData } = useSessionStore()

isThinking.value = true

await addSessionData({
isAsk: true,
data: {
role: 'user',
content: imageData
}
})

await addSessionData({
isAsk: false,
messageType: 'image',
data: {
role: 'assistant',
content: ''
}
})

const res = await getOpenAIImage(imageData)
if (!res) return

sessionDataList.value.at(-1)!.message.content = res.data as string
} catch ({ message }: any) {
sessionDataList.value.at(-1)!.message.content = message as any

updateSessionData(sessionDataList.value.at(-1)!)
} finally {
isThinking.value = false
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码看起来是一段 TypeScript 代码,主要用于调用 OpenAI 的 API 进行文本或者图片的生成,具体可分为两个部分:getAiMessage 和 getAiIamge。以下是我的一些建议和注意事项:

  1. 在 getAiMessage 中,可以将 try-catch 块中的错误信息改为 error.message,以更好地反映错误。

  2. 在 catch 块中,将 sessionDataList.value.at(-1)!.message.content 赋值给类型为 any 的 message 变量存在潜在的安全隐患,建议使用 as string 或者定义一个合适的类型。

  3. 在 getAiIamge 中,需要确保传入的 value 参数不为空,否则会抛出异常。

  4. 在添加正常提问时,可以先检查参数的合法性,以避免发生意外的错误。

  5. 修改最近一次会话内容的函数名 changeLastSessionContent 不够准确,应该改为 updateLastSessionContent 更符合其实际作用。

  6. 最后,在使用 try-catch 块时,尽量精确地捕获异常,以便于更好地排除错误。

以上是我的一些建议,希望对你有所帮助。

2 changes: 1 addition & 1 deletion src/utils/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const marked = new MarkdownIt({
})

export const checkVersion = async () => {
const updateInfo = await checkUpdate()
const updateInfo = await checkUpdate().catch(() => null)

if (!updateInfo || !updateInfo.manifest) return

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码看起来比较简单,主要是导入了一个MarkdownIt库和一个异步函数checkVersion。在checkVersion函数中,它使用了一个异步函数checkUpdate()来检查更新信息,并且在调用checkUpdate()时通过.catch(() => null)捕获了可能的异常。

从风险角度看,如果checkUpdate()函数出现了错误并返回了无效的数据,那么这个函数就会继续执行而不会抛出任何错误。因此,我们需要确保checkUpdate()在出现错误时能够正确地处理异常情况,并且在必要的情况下抛出适当的错误。

另外,由于没有看到checkUpdate()函数的定义,我无法评估它的质量和安全性。因此,如果有必要的话,我们可能需要对checkUpdate()函数进行进一步的审核和测试。

关于改进建议,我建议可以添加更多的注释,以便其他开发人员更容易理解代码和实现细节。此外,如果代码被用于生产环境,还应该考虑添加日志记录和错误处理机制,以便更好地监控和管理应用程序的行为。

Expand Down