feat: Add AI tag analysis feature and improve authentication handling

- Add AI-powered tag analysis feature in full-screen context menu
  - Analyze prompts using AI to suggest matching custom tags
  - Add loading state with spinner during analysis
  - Filter out already-added tags to avoid duplicates
  - Support i18n for all user-facing messages (EN/ZH-Hans/ZH-Hant/DE)
  - Keep system prompts in English for consistency

- Improve authentication error handling
  - Add special marker for secret verification 401 errors
  - Only trigger password modal for secret verification failures
  - Prevent password modal from showing on other 401 errors

- Enhance Topic Search guide
  - Add two advantage points highlighting semantic similarity grouping
  - Add natural language semantic search capabilities
  - Support i18n for new guide content
pull/870/head
zanllp 2026-01-04 00:29:03 +08:00
parent 96655d89c4
commit 0291f3c681
7 changed files with 84 additions and 24 deletions

View File

@ -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

View File

@ -45,8 +45,10 @@ export const de: Partial<IIBI18nMap> = {
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<IIBI18nMap> = {
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'
}

View File

@ -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'
}

View File

@ -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分析标签失败请检查配置'
}

View File

@ -47,6 +47,8 @@ export const zhHant: Partial<IIBI18nMap> = {
topicSearchGuideStep1: '選擇要分析的資料夾範圍(可多選)',
topicSearchGuideStep2: '點擊刷新,生成主題卡片(會增量向量化)',
topicSearchGuideStep3: '輸入一句話搜尋,召回相似圖片並打開結果頁',
topicSearchGuideAdvantage1: '✨ 基於語義相似度自動分組AI自動發現相似主題無需手動分類',
topicSearchGuideAdvantage2: '🚀 自然語言語義檢索用一句話快速找到相關圖片類似RAG檢索',
topicSearchGuideEmptyReasonNoScope: '目前為空:尚未選擇範圍(預設關閉),請先點「範圍」選擇資料夾',
topicSearchGuideEmptyReasonNoTopics: '目前為空:此範圍尚未生成主題(可點刷新,或調低最小組/閾值)',
topicSearchRequirementsTitle: '使用前置條件',
@ -412,5 +414,13 @@ export const zhHant: Partial<IIBI18nMap> = {
'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分析標籤失敗請檢查配置'
}

View File

@ -475,6 +475,15 @@ watch(
<span class="guide-text">{{ $t('topicSearchGuideStep3') }}</span>
</div>
<div class="guide-row">
<span class="guide-icon"></span>
<span class="guide-text">{{ $t('topicSearchGuideAdvantage1') }}</span>
</div>
<div class="guide-row">
<span class="guide-icon">🚀</span>
<span class="guide-text">{{ $t('topicSearchGuideAdvantage2') }}</span>
</div>
<div class="guide-hint">
<span class="guide-icon">💡</span>
<span class="guide-text" v-if="!scopeCount">{{ $t('topicSearchGuideEmptyReasonNoScope') }}</span>

View File

@ -314,38 +314,40 @@ const onTiktokViewClick = () => {
}
// AItag
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 () => {
<a-button
@click="analyzeTagsWithAI"
type="primary"
:loading="analyzingTags"
v-if="imageGenInfo && global.conf?.all_custom_tags?.length"
>
{{ $t('aiAnalyzeTags') }}