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 contentpull/870/head
parent
96655d89c4
commit
0291f3c681
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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分析标签失败,请检查配置'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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分析標籤失敗,請檢查配置'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
|||
<a-button
|
||||
@click="analyzeTagsWithAI"
|
||||
type="primary"
|
||||
:loading="analyzingTags"
|
||||
v-if="imageGenInfo && global.conf?.all_custom_tags?.length"
|
||||
>
|
||||
{{ $t('aiAnalyzeTags') }}
|
||||
|
|
|
|||
Loading…
Reference in New Issue