diff --git a/vue/src/api/index.ts b/vue/src/api/index.ts index cadcfb7..0e5d516 100644 --- a/vue/src/api/index.ts +++ b/vue/src/api/index.ts @@ -69,7 +69,7 @@ const addInterceptor = (axiosInst: AxiosInstance) => { (resp) => resp, async (err) => { if (isAxiosError(err)) { - if (err.response?.status === 401) { + if (err.response?.status === 401 && err.response?.data?.detail?.type === 'secret_verification_failed') { const key = await promptServerKeyOnce() if (!key) { // user cancelled; leave the request rejected as-is diff --git a/vue/src/i18n/de.ts b/vue/src/i18n/de.ts index f8c05f7..783a8d3 100644 --- a/vue/src/i18n/de.ts +++ b/vue/src/i18n/de.ts @@ -45,8 +45,10 @@ export const de: Partial = { topicSearchGuideTitle: 'Schnellstart (Experimentell)', topicSearchGuideStep1: 'Wählen Sie die Ordner (Bereich) zur Analyse aus (Mehrfachauswahl)', - topicSearchGuideStep2: 'Klicken Sie auf „Aktualisieren“, um Themenkarten zu erzeugen (inkrementelle Vektorisierung)', + topicSearchGuideStep2: 'Klicken Sie auf „Aktualisieren", um Themenkarten zu erzeugen (inkrementelle Vektorisierung)', topicSearchGuideStep3: 'Geben Sie einen Satz ein, um zu suchen; ähnliche Bilder werden abgerufen und die Ergebnisse geöffnet', + topicSearchGuideAdvantage1: '✨ Automatische Gruppierung nach semantischer Ähnlichkeit: KI entdeckt automatisch ähnliche Themen ohne manuelle Kategorisierung', + topicSearchGuideAdvantage2: '🚀 Natürliche Sprachsemantiksuche: Schnelles Finden verwandter Bilder mit einem Satz, ähnlich der RAG-Suche', topicSearchGuideEmptyReasonNoScope: 'Leer, weil: kein Bereich ausgewählt (standardmäßig deaktiviert). Klicken Sie auf „Bereich“, um Ordner zu wählen.', topicSearchGuideEmptyReasonNoTopics: 'Leer, weil: für diesen Bereich noch keine Themen erzeugt wurden (Aktualisieren oder Min. Cluster/Schwelle senken).', topicSearchRequirementsTitle: 'Voraussetzungen', @@ -194,5 +196,13 @@ export const de: Partial = { sortByDate: 'Nach Datum sortieren', fileTypeFilter: 'Dateityp-Filter', allFiles: 'Alle Dateien', - audio: 'Audio' + audio: 'Audio', + aiAnalyzeTags: 'KI-Tags analysieren', + aiAnalyzeTagsNoPrompt: 'Kein Prompt gefunden', + aiAnalyzeTagsNoCustomTags: 'Keine benutzerdefinierten Tags verfügbar', + aiAnalyzeTagsNoMatchedTags: 'KI hat keine passenden Tags gefunden', + aiAnalyzeTagsNoValidTags: 'Keine gültigen passenden Tags gefunden', + aiAnalyzeTagsAllTagsAlreadyAdded: 'Alle passenden Tags wurden bereits zum Bild hinzugefügt', + aiAnalyzeTagsSuccess: '{0} Tags hinzugefügt: {1}', + aiAnalyzeTagsFailed: 'KI-Tag-Analyse fehlgeschlagen, bitte Konfiguration überprüfen' } diff --git a/vue/src/i18n/en.ts b/vue/src/i18n/en.ts index 2d3c8b4..dfbe251 100644 --- a/vue/src/i18n/en.ts +++ b/vue/src/i18n/en.ts @@ -47,6 +47,8 @@ export const en: IIBI18nMap = { topicSearchGuideStep1: 'Select the scope folders to analyze (multi-select)', topicSearchGuideStep2: 'Click Refresh to generate topic cards (incremental vectorization)', topicSearchGuideStep3: 'Type a sentence to search; it will retrieve similar images and open the result page', + topicSearchGuideAdvantage1: '✨ Auto-grouping by semantic similarity: AI automatically discovers similar themes without manual categorization', + topicSearchGuideAdvantage2: '🚀 Natural language semantic search: Quickly find related images with a sentence, similar to RAG retrieval', topicSearchGuideEmptyReasonNoScope: 'Empty because: no scope selected (disabled by default). Click “Scope” to choose folders.', topicSearchGuideEmptyReasonNoTopics: 'Empty because: no topics generated yet for this scope (try Refresh or lower Min cluster/Threshold).', topicSearchRequirementsTitle: 'Requirements', @@ -431,5 +433,12 @@ You can specify which snapshot to restore to when starting IIB in the global set 'autoTag.operators.contains': 'Contains', 'autoTag.operators.equals': 'Equals', 'autoTag.operators.regex': 'Regex', - aiAnalyzeTags: 'AI Analyze Tags' + aiAnalyzeTags: 'AI Analyze Tags', + aiAnalyzeTagsNoPrompt: 'No prompt found', + aiAnalyzeTagsNoCustomTags: 'No custom tags available', + aiAnalyzeTagsNoMatchedTags: 'AI found no matching tags', + aiAnalyzeTagsNoValidTags: 'No valid matching tags found', + aiAnalyzeTagsAllTagsAlreadyAdded: 'All matched tags have already been added to the image', + aiAnalyzeTagsSuccess: 'Added {0} tags: {1}', + aiAnalyzeTagsFailed: 'AI tag analysis failed, please check configuration' } diff --git a/vue/src/i18n/zh-hans.ts b/vue/src/i18n/zh-hans.ts index 6c99557..40647f0 100644 --- a/vue/src/i18n/zh-hans.ts +++ b/vue/src/i18n/zh-hans.ts @@ -45,6 +45,8 @@ export const zhHans = { topicSearchGuideStep1: '选择要分析的文件夹范围(可多选)', topicSearchGuideStep2: '点击刷新,生成主题卡片(会增量向量化)', topicSearchGuideStep3: '输入一句话搜索,会召回相似图片并打开结果页', + topicSearchGuideAdvantage1: '✨ 基于语义相似度自动分组:AI自动发现相似主题,无需手动分类', + topicSearchGuideAdvantage2: '🚀 自然语言语义检索:用一句话快速找到相关图片,类似RAG检索', topicSearchGuideEmptyReasonNoScope: '当前为空:未选择范围(已默认关闭),请先点“范围”选择文件夹', topicSearchGuideEmptyReasonNoTopics: '当前为空:该范围内还未生成主题(可点刷新,或调低最小组/阈值)', topicSearchRequirementsTitle: '使用前置条件', @@ -409,5 +411,12 @@ export const zhHans = { 'autoTag.operators.contains': '包含 (Contains)', 'autoTag.operators.equals': '等于 (Equals)', 'autoTag.operators.regex': '正则 (Regex)', - aiAnalyzeTags: 'AI分析标签' + aiAnalyzeTags: 'AI分析标签', + aiAnalyzeTagsNoPrompt: '没有找到提示词', + aiAnalyzeTagsNoCustomTags: '没有自定义标签', + aiAnalyzeTagsNoMatchedTags: 'AI没有找到匹配的标签', + aiAnalyzeTagsNoValidTags: '没有找到有效的匹配标签', + aiAnalyzeTagsAllTagsAlreadyAdded: '所有匹配的标签已经添加到图像上了', + aiAnalyzeTagsSuccess: '已添加 {0} 个标签:{1}', + aiAnalyzeTagsFailed: 'AI分析标签失败,请检查配置' } diff --git a/vue/src/i18n/zh-hant.ts b/vue/src/i18n/zh-hant.ts index 4d9a365..b4f03bf 100644 --- a/vue/src/i18n/zh-hant.ts +++ b/vue/src/i18n/zh-hant.ts @@ -47,6 +47,8 @@ export const zhHant: Partial = { topicSearchGuideStep1: '選擇要分析的資料夾範圍(可多選)', topicSearchGuideStep2: '點擊刷新,生成主題卡片(會增量向量化)', topicSearchGuideStep3: '輸入一句話搜尋,召回相似圖片並打開結果頁', + topicSearchGuideAdvantage1: '✨ 基於語義相似度自動分組:AI自動發現相似主題,無需手動分類', + topicSearchGuideAdvantage2: '🚀 自然語言語義檢索:用一句話快速找到相關圖片,類似RAG檢索', topicSearchGuideEmptyReasonNoScope: '目前為空:尚未選擇範圍(預設關閉),請先點「範圍」選擇資料夾', topicSearchGuideEmptyReasonNoTopics: '目前為空:此範圍尚未生成主題(可點刷新,或調低最小組/閾值)', topicSearchRequirementsTitle: '使用前置條件', @@ -412,5 +414,13 @@ export const zhHant: Partial = { 'autoTag.fields.seed': 'Seed', 'autoTag.operators.contains': '包含 (Contains)', 'autoTag.operators.equals': '等於 (Equals)', - 'autoTag.operators.regex': '正則 (Regex)' + 'autoTag.operators.regex': '正則 (Regex)', + aiAnalyzeTags: 'AI分析標籤', + aiAnalyzeTagsNoPrompt: '沒有找到提示詞', + aiAnalyzeTagsNoCustomTags: '沒有自定義標籤', + aiAnalyzeTagsNoMatchedTags: 'AI沒有找到匹配的標籤', + aiAnalyzeTagsNoValidTags: '沒有找到有效的匹配標籤', + aiAnalyzeTagsAllTagsAlreadyAdded: '所有匹配的標籤已經添加到圖像上了', + aiAnalyzeTagsSuccess: '已添加 {0} 個標籤:{1}', + aiAnalyzeTagsFailed: 'AI分析標籤失敗,請檢查配置' } diff --git a/vue/src/page/TopicSearch/TopicSearch.vue b/vue/src/page/TopicSearch/TopicSearch.vue index 75acdd3..4a12589 100644 --- a/vue/src/page/TopicSearch/TopicSearch.vue +++ b/vue/src/page/TopicSearch/TopicSearch.vue @@ -475,6 +475,15 @@ watch( {{ $t('topicSearchGuideStep3') }} +
+ + {{ $t('topicSearchGuideAdvantage1') }} +
+
+ 🚀 + {{ $t('topicSearchGuideAdvantage2') }} +
+
💡 {{ $t('topicSearchGuideEmptyReasonNoScope') }} diff --git a/vue/src/page/fileTransfer/fullScreenContextMenu.vue b/vue/src/page/fileTransfer/fullScreenContextMenu.vue index 97a9582..c18ff7c 100644 --- a/vue/src/page/fileTransfer/fullScreenContextMenu.vue +++ b/vue/src/page/fileTransfer/fullScreenContextMenu.vue @@ -314,38 +314,40 @@ const onTiktokViewClick = () => { } // AI分析tag功能 +const analyzingTags = ref(false) const analyzeTagsWithAI = async () => { if (!geninfoStruct.value.prompt) { - message.warning('没有找到提示词') + message.warning(t('aiAnalyzeTagsNoPrompt')) return } if (!global.conf?.all_custom_tags?.length) { - message.warning('没有自定义标签') + message.warning(t('aiAnalyzeTagsNoCustomTags')) return } + analyzingTags.value = true try { const prompt = geninfoStruct.value.prompt const availableTags = global.conf.all_custom_tags.map(tag => tag.name).join(', ') - const systemMessage = `你是一个专业的AI助手,负责分析Stable Diffusion提示词并将其分类到相应的标签中。 + const systemMessage = `You are a professional AI assistant responsible for analyzing Stable Diffusion prompts and categorizing them into appropriate tags. -你的任务是: -1. 分析给定的提示词 -2. 从提供的标签列表中找出所有相关的标签 -3. 只返回匹配的标签名称,用逗号分隔 -4. 如果没有匹配的标签,返回空字符串 -5. 标签匹配应该基于语义相似性和主题相关性 +Your task is: +1. Analyze the given prompt +2. Find all relevant tags from the provided tag list +3. Return only the matching tag names, separated by commas +4. If no tags match, return an empty string +5. Tag matching should be based on semantic similarity and thematic relevance -可用的标签:${availableTags} +Available tags: ${availableTags} -请只返回标签名称,不要包含其他内容。` +Please return only tag names, do not include any other content.` const response = await aiChat({ messages: [ { role: 'system', content: systemMessage }, - { role: 'user', content: `请分析这个提示词并返回匹配的标签:${prompt}` } + { role: 'user', content: `Please analyze this prompt and return matching tags: ${prompt}` } ], temperature: 0.3, max_tokens: 200 @@ -353,7 +355,7 @@ const analyzeTagsWithAI = async () => { const matchedTagsText = response.choices[0].message.content.trim() if (!matchedTagsText) { - message.info('AI没有找到匹配的标签') + message.info(t('aiAnalyzeTagsNoMatchedTags')) return } @@ -361,7 +363,7 @@ const analyzeTagsWithAI = async () => { const matchedTagNames = matchedTagsText.split(',').map((name: string) => name.trim()).filter((name: string) => name) // 找到对应的tag对象 - const tagsToAdd = global.conf.all_custom_tags.filter((tag: Tag) => + const matchedTags = global.conf.all_custom_tags.filter((tag: Tag) => matchedTagNames.some((matchedName: string) => tag.name.toLowerCase() === matchedName.toLowerCase() || tag.name.toLowerCase().includes(matchedName.toLowerCase()) || @@ -369,21 +371,31 @@ const analyzeTagsWithAI = async () => { ) ) + // 过滤掉已经添加到图像上的标签 + const existingTagIds = new Set(selectedTag.value.map((t: Tag) => t.id)) + const tagsToAdd = matchedTags.filter((tag: Tag) => !existingTagIds.has(tag.id)) + if (tagsToAdd.length === 0) { - message.info('没有找到有效的匹配标签') + if (matchedTags.length > 0) { + message.info(t('aiAnalyzeTagsAllTagsAlreadyAdded')) + } else { + message.info(t('aiAnalyzeTagsNoValidTags')) + } return } - // 为每个匹配的tag发送添加请求 + // 为每个匹配的tag发送添加请求(只添加新标签) for (const tag of tagsToAdd) { emit('contextMenuClick', { key: `toggle-tag-${tag.id}` } as any, props.file, props.idx) } - message.success(`已添加 ${tagsToAdd.length} 个标签:${tagsToAdd.map(t => t.name).join(', ')}`) + message.success(t('aiAnalyzeTagsSuccess', [tagsToAdd.length.toString(), tagsToAdd.map(t => t.name).join(', ')])) } catch (error) { console.error('AI分析标签失败:', error) - message.error('AI分析标签失败,请检查配置') + message.error(t('aiAnalyzeTagsFailed')) + } finally { + analyzingTags.value = false } } @@ -469,6 +481,7 @@ const analyzeTagsWithAI = async () => { {{ $t('aiAnalyzeTags') }}