From eae15ced5a1f7becba634193c291dfac0f2842cc Mon Sep 17 00:00:00 2001 From: yuantao Date: Fri, 19 Sep 2025 17:25:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=B8=B8=E7=94=A8=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/PromptComposer.tsx | 9 ++ src/components/PromptSuggestions.tsx | 120 +++++++++++++++++++++++++++ src/services/uploadService.ts | 25 ++++-- 3 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 src/components/PromptSuggestions.tsx diff --git a/src/components/PromptComposer.tsx b/src/components/PromptComposer.tsx index 001236f..b1adbb5 100644 --- a/src/components/PromptComposer.tsx +++ b/src/components/PromptComposer.tsx @@ -6,6 +6,7 @@ import { useImageGeneration, useImageEditing } from '../hooks/useImageGeneration import { Upload, Wand2, Edit3, MousePointer, HelpCircle, ChevronDown, ChevronRight, RotateCcw } from 'lucide-react'; import { blobToBase64, urlToBlob } from '../utils/imageUtils'; import { PromptHints } from './PromptHints'; +import { PromptSuggestions } from './PromptSuggestions'; import { cn } from '../utils/cn'; export const PromptComposer: React.FC = () => { @@ -341,6 +342,14 @@ export const PromptComposer: React.FC = () => { className="min-h-[120px] resize-none text-sm rounded-xl" /> + {/* 常用提示词 */} + { + setCurrentPrompt(currentPrompt ? `${currentPrompt}, ${word}` : word); + }} + minFrequency={3} + /> + {/* 提示质量指示器 */} + )} + + +
+ {displayWords.map(({ word, count }) => ( + + ))} +
+ + {frequentWords.length > 20 && ( +
+ 共 {frequentWords.length} 个常用提示词 +
+ )} + + ); +}; \ No newline at end of file diff --git a/src/services/uploadService.ts b/src/services/uploadService.ts index 8e155fe..b36bdd5 100644 --- a/src/services/uploadService.ts +++ b/src/services/uploadService.ts @@ -60,7 +60,12 @@ function getImageHash(imageData: string): string { if (imageData.startsWith('blob:')) { // 对于Blob URL,我们使用URL本身作为标识符的一部分 // 这不是完美的解决方案,但对于大多数情况足够了 - return btoa(imageData).slice(0, 32) + try { + return btoa(imageData).slice(0, 32) + } catch (e) { + // 如果btoa失败(例如包含非Latin1字符),使用encodeURIComponent + return btoa(encodeURIComponent(imageData)).slice(0, 32) + } } // 对于base64数据,使用简单的哈希函数生成图像标识符 @@ -132,7 +137,12 @@ export const uploadImage = async (imageData: string | Blob, accessToken: string, // 检查缓存是否过期 if (Date.now() - cachedResult.timestamp < CACHE_EXPIRY_TIME) { console.log('从缓存中获取上传结果') - return cachedResult + // 确保返回的数据结构与新上传的结果一致 + return { + success: cachedResult.success, + url: cachedResult.url, + error: cachedResult.error + } } else { // 缓存过期,删除它 uploadCache.delete(imageHash) @@ -150,7 +160,9 @@ export const uploadImage = async (imageData: string | Blob, accessToken: string, // 从base64数据创建Blob const base64Data = imageData.split('base64,')[1] const byteString = atob(base64Data) - const mimeString = 'image/png' // 默认MIME类型 + // 从base64数据中提取MIME类型 + const mimeMatch = imageData.match(/^data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/) + const mimeString = mimeMatch ? mimeMatch[1] : 'image/png' // 默认MIME类型 const ab = new ArrayBuffer(byteString.length) const ia = new Uint8Array(ab) for (let i = 0; i < byteString.length; i++) { @@ -214,13 +226,14 @@ export const uploadImage = async (imageData: string | Blob, accessToken: string, }) } - return uploadResult + return { success: true, url: fullUrl, error: undefined } } else { throw new Error(`上传失败: ${result.msg}`) } } catch (error) { console.error('上传图像时出错:', error) - const errorResult = { success: false, error: error instanceof Error ? error.message : String(error) } + const errorMessage = error instanceof Error ? error.message : String(error) + const errorResult = { success: false, error: errorMessage } // 清理过期缓存 cleanupExpiredCache() @@ -236,7 +249,7 @@ export const uploadImage = async (imageData: string | Blob, accessToken: string, }) } - return errorResult + return { success: false, error: errorMessage } } }