diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index b48e6ece..c9e6174c 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -961,6 +961,11 @@ export const TEXT_TO_IMAGES_MODELS = [ } ] +export const TEXT_TO_IMAGES_MODELS_SUPPORT_IMAGE_ENHANCEMENT = [ + 'stabilityai/stable-diffusion-2-1', + 'stabilityai/stable-diffusion-xl-base-1.0' +] + export function isTextToImageModel(model: Model): boolean { return TEXT_TO_IMAGE_REGEX.test(model.id) } diff --git a/src/renderer/src/hooks/usePaintings.ts b/src/renderer/src/hooks/usePaintings.ts index b01c0acc..8a8ed550 100644 --- a/src/renderer/src/hooks/usePaintings.ts +++ b/src/renderer/src/hooks/usePaintings.ts @@ -14,6 +14,7 @@ export function usePaintings() { paintings, addPainting: () => { const newPainting: Painting = { + model: TEXT_TO_IMAGES_MODELS[0].id, id: uuid(), urls: [], files: [], @@ -24,7 +25,7 @@ export function usePaintings() { seed: generateRandomSeed(), steps: 25, guidanceScale: 4.5, - model: TEXT_TO_IMAGES_MODELS[0].id + promptEnhancement: true } dispatch(addPainting(newPainting)) return newPainting diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index b8cdec96..232b5d06 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -279,7 +279,9 @@ "regenerate.confirm": "This will replace your existing generated images. Do you want to continue?", "seed": "Seed", "seed_tip": "The same seed and prompt can produce similar images", - "title": "Images" + "title": "Images", + "prompt_enhancement": "Prompt Enhancement", + "prompt_enhancement_tip": "Rewrite prompts into detailed, model-friendly versions when switched on" }, "provider": { "aihubmix": "AiHubMix", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 1f146aaf..5570313f 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -277,7 +277,9 @@ "regenerate.confirm": "これにより、既存の生成画像が置き換えられます。続行しますか?", "seed": "シード", "seed_tip": "同じシードとプロンプトで似た画像を生成できます", - "title": "画像" + "title": "画像", + "prompt_enhancement": "プロンプト強化", + "prompt_enhancement_tip": "オンにすると、プロンプトを詳細でモデルに適したバージョンに書き直します" }, "provider": { "aihubmix": "AiHubMix", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index c1088ab5..3f758652 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -279,7 +279,9 @@ "regenerate.confirm": "Это заменит ваши существующие сгенерированные изображения. Хотите продолжить?", "seed": "Ключ генерации", "seed_tip": "Одинаковый ключ генерации и промпт могут производить похожие изображения", - "title": "Изображения" + "title": "Изображения", + "prompt_enhancement": "Улучшение промпта", + "prompt_enhancement_tip": "При включении переписывает промпт в более детальную, модель-ориентированную версию" }, "provider": { "aihubmix": "AiHubMix", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index a2fb898f..4073cc95 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -280,7 +280,9 @@ "regenerate.confirm": "这将覆盖已生成的图片,是否继续?", "seed": "随机种子", "seed_tip": "相同的种子和提示词可以生成相似的图片", - "title": "图片" + "title": "图片", + "prompt_enhancement": "提示词增强", + "prompt_enhancement_tip": "开启后将提示重写为详细的、适合模型的版本" }, "provider": { "aihubmix": "AiHubMix", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 105d78ef..87b51b32 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -279,7 +279,9 @@ "regenerate.confirm": "這將覆蓋已生成的圖片,是否繼續?", "seed": "隨機種子", "seed_tip": "相同的種子和提示詞可以生成相似的圖片", - "title": "繪圖" + "title": "繪圖", + "prompt_enhancement": "提示詞增強", + "prompt_enhancement_tip": "開啟後將提示重寫為詳細的、適合模型的版本" }, "provider": { "aihubmix": "AiHubMix", diff --git a/src/renderer/src/pages/paintings/PaintingsPage.tsx b/src/renderer/src/pages/paintings/PaintingsPage.tsx index a6ee6366..7c924486 100644 --- a/src/renderer/src/pages/paintings/PaintingsPage.tsx +++ b/src/renderer/src/pages/paintings/PaintingsPage.tsx @@ -6,7 +6,7 @@ import ImageSize3_4 from '@renderer/assets/images/paintings/image-size-3-4.svg' import ImageSize9_16 from '@renderer/assets/images/paintings/image-size-9-16.svg' import ImageSize16_9 from '@renderer/assets/images/paintings/image-size-16-9.svg' import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' -import { VStack } from '@renderer/components/Layout' +import { HStack, VStack } from '@renderer/components/Layout' import Scrollbar from '@renderer/components/Scrollbar' import TranslateButton from '@renderer/components/TranslateButton' import { isMac } from '@renderer/config/constant' @@ -25,7 +25,7 @@ import { DEFAULT_PAINTING } from '@renderer/store/paintings' import { setGenerating } from '@renderer/store/runtime' import { FileType, Painting } from '@renderer/types' import { getErrorMessage } from '@renderer/utils' -import { Button, Input, InputNumber, Radio, Select, Slider, Tooltip } from 'antd' +import { Button, Input, InputNumber, Radio, Select, Slider, Switch, Tooltip } from 'antd' import TextArea from 'antd/es/input/TextArea' import { FC, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -149,8 +149,13 @@ const PaintingsPage: FC = () => { dispatch(setGenerating(true)) const AI = new AiProvider(provider) + if (!painting.model) { + return + } + try { const urls = await AI.generateImage({ + model: painting.model, prompt, negativePrompt: painting.negativePrompt || '', imageSize: painting.imageSize || '1024x1024', @@ -158,7 +163,8 @@ const PaintingsPage: FC = () => { seed: painting.seed || undefined, numInferenceSteps: painting.steps || 25, guidanceScale: painting.guidanceScale || 4.5, - signal: controller.signal + signal: controller.signal, + promptEnhancement: painting.promptEnhancement || false }) if (urls.length > 0) { @@ -360,13 +366,15 @@ const PaintingsPage: FC = () => { - updatePaintingState({ steps: v })} /> - updatePaintingState({ steps: v || 25 })} - /> + + updatePaintingState({ steps: v })} /> + updatePaintingState({ steps: (v as number) || 25 })} + /> + {t('paintings.guidance_scale')} @@ -374,21 +382,22 @@ const PaintingsPage: FC = () => { - updatePaintingState({ guidanceScale: v })} - /> - updatePaintingState({ guidanceScale: v || 4.5 })} - /> - + + updatePaintingState({ guidanceScale: v })} + /> + updatePaintingState({ guidanceScale: (v as number) || 4.5 })} + /> + {t('paintings.negative_prompt')} @@ -400,6 +409,18 @@ const PaintingsPage: FC = () => { onChange={(e) => updatePaintingState({ negativePrompt: e.target.value })} rows={4} /> + + {t('paintings.prompt_enhancement')} + + + + + + updatePaintingState({ promptEnhancement: checked })} + /> + { + public async generateImage(params: GenerateImageParams): Promise { return this.sdk.generateImage(params) } diff --git a/src/renderer/src/providers/BaseProvider.ts b/src/renderer/src/providers/BaseProvider.ts index 968bb283..303b9181 100644 --- a/src/renderer/src/providers/BaseProvider.ts +++ b/src/renderer/src/providers/BaseProvider.ts @@ -2,7 +2,7 @@ import { REFERENCE_PROMPT } from '@renderer/config/prompts' import { getOllamaKeepAliveTime } from '@renderer/hooks/useOllama' import { getKnowledgeReferences } from '@renderer/services/KnowledgeService' import store from '@renderer/store' -import { Assistant, Message, Model, Provider, Suggestion } from '@renderer/types' +import { Assistant, GenerateImageParams, Message, Model, Provider, Suggestion } from '@renderer/types' import { delay, isJSON } from '@renderer/utils' import OpenAI from 'openai' @@ -26,16 +26,7 @@ export default abstract class BaseProvider { abstract generateText({ prompt, content }: { prompt: string; content: string }): Promise abstract check(): Promise<{ valid: boolean; error: Error | null }> abstract models(): Promise - abstract generateImage(_params: { - prompt: string - negativePrompt: string - imageSize: string - batchSize: number - seed?: string - numInferenceSteps: number - guidanceScale: number - signal?: AbortSignal - }): Promise + abstract generateImage(params: GenerateImageParams): Promise abstract getEmbeddingDimensions(model: Model): Promise public getBaseURL(): string { diff --git a/src/renderer/src/providers/OpenAIProvider.ts b/src/renderer/src/providers/OpenAIProvider.ts index 285b377f..3c19be48 100644 --- a/src/renderer/src/providers/OpenAIProvider.ts +++ b/src/renderer/src/providers/OpenAIProvider.ts @@ -4,7 +4,7 @@ import i18n from '@renderer/i18n' import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService' import { EVENT_NAMES } from '@renderer/services/EventService' import { filterContextMessages } from '@renderer/services/MessagesService' -import { Assistant, FileTypes, Message, Model, Provider, Suggestion } from '@renderer/types' +import { Assistant, FileTypes, GenerateImageParams, Message, Model, Provider, Suggestion } from '@renderer/types' import { removeQuotes } from '@renderer/utils' import { last, takeRight } from 'lodash' import OpenAI, { AzureOpenAI } from 'openai' @@ -345,6 +345,7 @@ export default class OpenAIProvider extends BaseProvider { } public async generateImage({ + model, prompt, negativePrompt, imageSize, @@ -352,30 +353,23 @@ export default class OpenAIProvider extends BaseProvider { seed, numInferenceSteps, guidanceScale, - signal - }: { - prompt: string - negativePrompt?: string - imageSize: string - batchSize: number - seed?: string - numInferenceSteps: number - guidanceScale: number - signal?: AbortSignal - }): Promise { + signal, + promptEnhancement + }: GenerateImageParams): Promise { const response = (await this.sdk.request({ method: 'post', path: '/images/generations', signal, body: { - model: 'stabilityai/stable-diffusion-3-5-large', + model, prompt, negative_prompt: negativePrompt, image_size: imageSize, batch_size: batchSize, seed: seed ? parseInt(seed) : undefined, num_inference_steps: numInferenceSteps, - guidance_scale: guidanceScale + guidance_scale: guidanceScale, + prompt_enhancement: promptEnhancement } })) as { data: Array<{ url: string }> } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 8badfc9c..75bc0125 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -112,6 +112,7 @@ export type Suggestion = { export interface Painting { id: string + model?: string urls: string[] files: FileType[] prompt?: string @@ -121,7 +122,7 @@ export interface Painting { seed?: string steps?: number guidanceScale?: number - model?: string + promptEnhancement?: boolean } export type MinAppType = { @@ -224,3 +225,16 @@ export type KnowledgeBaseParams = { apiVersion?: string baseURL: string } + +export type GenerateImageParams = { + model: string + prompt: string + negativePrompt?: string + imageSize: string + batchSize: number + seed?: string + numInferenceSteps: number + guidanceScale: number + signal?: AbortSignal + promptEnhancement?: boolean +}