feat(localization): add trigger words functionality with localization support for UI elements and messages

This commit is contained in:
Will Miao
2025-08-31 15:13:12 +08:00
parent 8303196b57
commit b2428f607c
4 changed files with 71 additions and 28 deletions

View File

@@ -543,6 +543,27 @@
"valuePlaceholder": "Value", "valuePlaceholder": "Value",
"add": "Add" "add": "Add"
}, },
"triggerWords": {
"label": "Trigger Words",
"noTriggerWordsNeeded": "No trigger word needed",
"edit": "Edit trigger words",
"cancel": "Cancel editing",
"save": "Save changes",
"addPlaceholder": "Type to add or click suggestions below",
"copyWord": "Copy trigger word",
"deleteWord": "Delete trigger word",
"suggestions": {
"noSuggestions": "No suggestions available",
"noTrainedWords": "No trained words or class tokens found in this model. You can manually enter trigger words.",
"classToken": "Class Token",
"classTokenDescription": "Add to your prompt for best results",
"wordSuggestions": "Word Suggestions",
"wordsFound": "{count} words found",
"loading": "Loading suggestions...",
"frequency": "Frequency",
"alreadyAdded": "Already added"
}
},
"description": { "description": {
"noDescription": "No model description available", "noDescription": "No model description available",
"failedToLoad": "Failed to load model description", "failedToLoad": "Failed to load model description",

View File

@@ -543,6 +543,27 @@
"valuePlaceholder": "值", "valuePlaceholder": "值",
"add": "添加" "add": "添加"
}, },
"triggerWords": {
"label": "触发词",
"noTriggerWordsNeeded": "无需触发词",
"edit": "编辑触发词",
"cancel": "取消编辑",
"save": "保存更改",
"addPlaceholder": "输入以添加或点击下方建议",
"copyWord": "复制触发词",
"deleteWord": "删除触发词",
"suggestions": {
"noSuggestions": "暂无可用建议",
"noTrainedWords": "此模型未找到训练词或类别标记。您可以手动输入触发词。",
"classToken": "类别标记",
"classTokenDescription": "添加到提示词以获得最佳效果",
"wordSuggestions": "词语建议",
"wordsFound": "已找到 {count} 个词",
"loading": "正在加载建议...",
"frequency": "出现频率",
"alreadyAdded": "已添加"
}
},
"description": { "description": {
"noDescription": "无模型描述信息", "noDescription": "无模型描述信息",
"failedToLoad": "加载模型描述失败", "failedToLoad": "加载模型描述失败",

View File

@@ -1,6 +1,6 @@
import { state, getCurrentPageState } from '../state/index.js'; import { state, getCurrentPageState } from '../state/index.js';
import { showToast } from '../utils/uiHelpers.js'; import { showToast } from '../utils/uiHelpers.js';
import { translate } from '../utils/i18n.js'; import { translate } from '../utils/i18nHelpers.js';
import { getStorageItem, getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js'; import { getStorageItem, getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js';
import { import {
getCompleteApiConfig, getCompleteApiConfig,

View File

@@ -4,6 +4,7 @@
* Moved to shared directory for consistency * Moved to shared directory for consistency
*/ */
import { showToast, copyToClipboard } from '../../utils/uiHelpers.js'; import { showToast, copyToClipboard } from '../../utils/uiHelpers.js';
import { translate } from '../../utils/i18nHelpers.js';
import { getModelApiClient } from '../../api/modelApiFactory.js'; import { getModelApiClient } from '../../api/modelApiFactory.js';
/** /**
@@ -48,9 +49,9 @@ function createSuggestionDropdown(trainedWords, classTokens, existingWords = [])
// No suggestions case // No suggestions case
if ((!trainedWords || trainedWords.length === 0) && !classTokens) { if ((!trainedWords || trainedWords.length === 0) && !classTokens) {
header.innerHTML = '<span>No suggestions available</span>'; header.innerHTML = `<span>${translate('modals.model.triggerWords.suggestions.noSuggestions')}</span>`;
dropdown.appendChild(header); dropdown.appendChild(header);
dropdown.innerHTML += '<div class="no-suggestions">No trained words or class tokens found in this model. You can manually enter trigger words.</div>'; dropdown.innerHTML += `<div class="no-suggestions">${translate('modals.model.triggerWords.suggestions.noTrainedWords')}</div>`;
return dropdown; return dropdown;
} }
@@ -65,8 +66,8 @@ function createSuggestionDropdown(trainedWords, classTokens, existingWords = [])
const classTokensHeader = document.createElement('div'); const classTokensHeader = document.createElement('div');
classTokensHeader.className = 'metadata-suggestions-header'; classTokensHeader.className = 'metadata-suggestions-header';
classTokensHeader.innerHTML = ` classTokensHeader.innerHTML = `
<span>Class Token</span> <span>${translate('modals.model.triggerWords.suggestions.classToken')}</span>
<small>Add to your prompt for best results</small> <small>${translate('modals.model.triggerWords.suggestions.classTokenDescription')}</small>
`; `;
dropdown.appendChild(classTokensHeader); dropdown.appendChild(classTokensHeader);
@@ -77,13 +78,13 @@ function createSuggestionDropdown(trainedWords, classTokens, existingWords = [])
// Create a special item for the class token // Create a special item for the class token
const tokenItem = document.createElement('div'); const tokenItem = document.createElement('div');
tokenItem.className = `metadata-suggestion-item class-token-item ${existingWords.includes(classTokens) ? 'already-added' : ''}`; tokenItem.className = `metadata-suggestion-item class-token-item ${existingWords.includes(classTokens) ? 'already-added' : ''}`;
tokenItem.title = `Class token: ${classTokens}`; tokenItem.title = `${translate('modals.model.triggerWords.suggestions.classToken')}: ${classTokens}`;
tokenItem.innerHTML = ` tokenItem.innerHTML = `
<span class="metadata-suggestion-text">${classTokens}</span> <span class="metadata-suggestion-text">${classTokens}</span>
<div class="metadata-suggestion-meta"> <div class="metadata-suggestion-meta">
<span class="token-badge">Class Token</span> <span class="token-badge">${translate('modals.model.triggerWords.suggestions.classToken')}</span>
${existingWords.includes(classTokens) ? ${existingWords.includes(classTokens) ?
'<span class="added-indicator"><i class="fas fa-check"></i></span>' : ''} `<span class="added-indicator"><i class="fas fa-check"></i></span>` : ''}
</div> </div>
`; `;
@@ -119,8 +120,8 @@ function createSuggestionDropdown(trainedWords, classTokens, existingWords = [])
// Add trained words header if we have any // Add trained words header if we have any
if (trainedWords && trainedWords.length > 0) { if (trainedWords && trainedWords.length > 0) {
header.innerHTML = ` header.innerHTML = `
<span>Word Suggestions</span> <span>${translate('modals.model.triggerWords.suggestions.wordSuggestions')}</span>
<small>${trainedWords.length} words found</small> <small>${translate('modals.model.triggerWords.suggestions.wordsFound', { count: trainedWords.length })}</small>
`; `;
dropdown.appendChild(header); dropdown.appendChild(header);
@@ -139,7 +140,7 @@ function createSuggestionDropdown(trainedWords, classTokens, existingWords = [])
<span class="metadata-suggestion-text">${word}</span> <span class="metadata-suggestion-text">${word}</span>
<div class="metadata-suggestion-meta"> <div class="metadata-suggestion-meta">
<span class="trained-word-freq">${frequency}</span> <span class="trained-word-freq">${frequency}</span>
${isAdded ? '<span class="added-indicator"><i class="fas fa-check"></i></span>' : ''} ${isAdded ? `<span class="added-indicator"><i class="fas fa-check"></i></span>` : ''}
</div> </div>
`; `;
@@ -166,7 +167,7 @@ function createSuggestionDropdown(trainedWords, classTokens, existingWords = [])
dropdown.appendChild(container); dropdown.appendChild(container);
} else if (!classTokens) { } else if (!classTokens) {
// If we have neither class tokens nor trained words // If we have neither class tokens nor trained words
dropdown.innerHTML += '<div class="no-suggestions">No word suggestions found in this model. You can manually enter trigger words.</div>'; dropdown.innerHTML += `<div class="no-suggestions">${translate('modals.model.triggerWords.suggestions.noTrainedWords')}</div>`;
} }
return dropdown; return dropdown;
@@ -182,22 +183,22 @@ export function renderTriggerWords(words, filePath) {
if (!words.length) return ` if (!words.length) return `
<div class="info-item full-width trigger-words"> <div class="info-item full-width trigger-words">
<div class="trigger-words-header"> <div class="trigger-words-header">
<label>Trigger Words</label> <label>${translate('modals.model.triggerWords.label')}</label>
<button class="edit-trigger-words-btn metadata-edit-btn" data-file-path="${filePath}" title="Edit trigger words"> <button class="edit-trigger-words-btn metadata-edit-btn" data-file-path="${filePath}" title="${translate('modals.model.triggerWords.edit')}">
<i class="fas fa-pencil-alt"></i> <i class="fas fa-pencil-alt"></i>
</button> </button>
</div> </div>
<div class="trigger-words-content"> <div class="trigger-words-content">
<span class="no-trigger-words">No trigger word needed</span> <span class="no-trigger-words">${translate('modals.model.triggerWords.noTriggerWordsNeeded')}</span>
<div class="trigger-words-tags" style="display:none;"></div> <div class="trigger-words-tags" style="display:none;"></div>
</div> </div>
<div class="metadata-edit-controls" style="display:none;"> <div class="metadata-edit-controls" style="display:none;">
<button class="metadata-save-btn" title="Save changes"> <button class="metadata-save-btn" title="${translate('modals.model.triggerWords.save')}">
<i class="fas fa-save"></i> Save <i class="fas fa-save"></i> ${translate('common.actions.save')}
</button> </button>
</div> </div>
<div class="metadata-add-form" style="display:none;"> <div class="metadata-add-form" style="display:none;">
<input type="text" class="metadata-input" placeholder="Type to add or click suggestions below"> <input type="text" class="metadata-input" placeholder="${translate('modals.model.triggerWords.addPlaceholder')}">
</div> </div>
</div> </div>
`; `;
@@ -205,20 +206,20 @@ export function renderTriggerWords(words, filePath) {
return ` return `
<div class="info-item full-width trigger-words"> <div class="info-item full-width trigger-words">
<div class="trigger-words-header"> <div class="trigger-words-header">
<label>Trigger Words</label> <label>${translate('modals.model.triggerWords.label')}</label>
<button class="edit-trigger-words-btn metadata-edit-btn" data-file-path="${filePath}" title="Edit trigger words"> <button class="edit-trigger-words-btn metadata-edit-btn" data-file-path="${filePath}" title="${translate('modals.model.triggerWords.edit')}">
<i class="fas fa-pencil-alt"></i> <i class="fas fa-pencil-alt"></i>
</button> </button>
</div> </div>
<div class="trigger-words-content"> <div class="trigger-words-content">
<div class="trigger-words-tags"> <div class="trigger-words-tags">
${words.map(word => ` ${words.map(word => `
<div class="trigger-word-tag" data-word="${word}" onclick="copyTriggerWord('${word}')"> <div class="trigger-word-tag" data-word="${word}" onclick="copyTriggerWord('${word}')" title="${translate('modals.model.triggerWords.copyWord')}">
<span class="trigger-word-content">${word}</span> <span class="trigger-word-content">${word}</span>
<span class="trigger-word-copy"> <span class="trigger-word-copy">
<i class="fas fa-copy"></i> <i class="fas fa-copy"></i>
</span> </span>
<button class="metadata-delete-btn" style="display:none;" onclick="event.stopPropagation();"> <button class="metadata-delete-btn" style="display:none;" onclick="event.stopPropagation();" title="${translate('modals.model.triggerWords.deleteWord')}">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
</div> </div>
@@ -226,12 +227,12 @@ export function renderTriggerWords(words, filePath) {
</div> </div>
</div> </div>
<div class="metadata-edit-controls" style="display:none;"> <div class="metadata-edit-controls" style="display:none;">
<button class="metadata-save-btn" title="Save changes"> <button class="metadata-save-btn" title="${translate('modals.model.triggerWords.save')}">
<i class="fas fa-save"></i> Save <i class="fas fa-save"></i> ${translate('common.actions.save')}
</button> </button>
</div> </div>
<div class="metadata-add-form" style="display:none;"> <div class="metadata-add-form" style="display:none;">
<input type="text" class="metadata-input" placeholder="Type to add or click suggestions below"> <input type="text" class="metadata-input" placeholder="${translate('modals.model.triggerWords.addPlaceholder')}">
</div> </div>
</div> </div>
`; `;
@@ -265,7 +266,7 @@ export function setupTriggerWordsEditMode() {
if (isEditMode) { if (isEditMode) {
this.innerHTML = '<i class="fas fa-times"></i>'; // Change to cancel icon this.innerHTML = '<i class="fas fa-times"></i>'; // Change to cancel icon
this.title = "Cancel editing"; this.title = translate('modals.model.triggerWords.cancel');
// Store original trigger words for potential restoration // Store original trigger words for potential restoration
originalTriggerWords = Array.from(triggerWordTags).map(tag => tag.dataset.word); originalTriggerWords = Array.from(triggerWordTags).map(tag => tag.dataset.word);
@@ -302,7 +303,7 @@ export function setupTriggerWordsEditMode() {
// Add loading indicator // Add loading indicator
const loadingIndicator = document.createElement('div'); const loadingIndicator = document.createElement('div');
loadingIndicator.className = 'metadata-loading'; loadingIndicator.className = 'metadata-loading';
loadingIndicator.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading suggestions...'; loadingIndicator.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${translate('modals.model.triggerWords.suggestions.loading')}`;
addForm.appendChild(loadingIndicator); addForm.appendChild(loadingIndicator);
// Get currently added trigger words // Get currently added trigger words
@@ -329,7 +330,7 @@ export function setupTriggerWordsEditMode() {
} else { } else {
this.innerHTML = '<i class="fas fa-pencil-alt"></i>'; // Change back to edit icon this.innerHTML = '<i class="fas fa-pencil-alt"></i>'; // Change back to edit icon
this.title = "Edit trigger words"; this.title = translate('modals.model.triggerWords.edit');
// Hide edit controls and input form // Hide edit controls and input form
editControls.style.display = 'none'; editControls.style.display = 'none';