fix(ui): visual correction audit - return to gothic vampire aesthetics, sweep emojis, center header, and fix tooltip overlap
This commit is contained in:
@@ -178,6 +178,13 @@ DOM or live LiteGraph state.
|
||||
`recipe_actions.js` own pure or transactional recipe behavior.
|
||||
- `ui_doctor.js`, `model_picker.js`, and `graph_splice.js` own assistant and
|
||||
explicit graph-edit behavior.
|
||||
- Visual Correction Audit & Gothic Vampire Re-alignment (视觉纠偏与哥特美学回归):
|
||||
- Purged all 83 high-saturation cyberpunk cyan/blue instances (`#38bdf8`, `#00ffcc`, `#2dd4bf`, `#5eead4`, `#007bff`, `#0ea5e9`, `#06b6d4`) and 140+ `rgba(...)` glow values, re-skinning to an aristocratic Crimson (`#dc143c`), Deep Wine (`#722f37`), and Abyssal Violet (`#8b5cf6`) palette;
|
||||
- Restrained all oversized diffused glows (`box-shadow` / `filter: drop-shadow`) to tight 4~8px interior magic micro-auras, adhering to vampire gothic nobility;
|
||||
- Swept legacy system emojis across all 13 frontend modules and `locales.js`, replacing them with pure-code vector SVG icons (`3D Cube`, `Network`, `Magic Wand`, `Folder`, `Image Frame`, `Sliders`, `Trash`, etc.);
|
||||
- Overhauled `#anomalous-header` into a centered symmetric three-column architecture (`leftGroup`, `centerGroup`, `rightGroup`), eliminating left-side visual voids/black holes;
|
||||
- Injected gothic diamond lattice textures and dark crimson radial auras into `#anomalous-gallery-panel`, `.anomalous-gallery-grid`, `.anomalous-gallery-container`, and `#anomalous-content`;
|
||||
- Fixed tooltip overlap hazard by re-aligning bottom toolbar tooltip triggers to center-top (`data-tooltip-pos="top"`), completely preventing horizontal button occlusion.
|
||||
- `locales.js` is the canonical runtime string catalog; `safe_dom.js` is the
|
||||
trusted rich-text boundary.
|
||||
- `web/main.js` and `web/modules/ui_sidebar.js` own theme state management and interactive easter-egg domain toggles,
|
||||
|
||||
+1
-1
@@ -705,7 +705,7 @@ function applyFloatingTriggerPresentation() {
|
||||
const icon = document.createElement('span');
|
||||
icon.className = 'anomalous-trigger-icon';
|
||||
icon.setAttribute('aria-hidden', 'true');
|
||||
icon.textContent = '📦';
|
||||
icon.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:middle;"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'anomalous-trigger-label';
|
||||
|
||||
+74
-74
@@ -102,7 +102,7 @@ export const i18n = {
|
||||
recipeViewGrid: "网格视图",
|
||||
recipeViewList: "列表视图",
|
||||
recipeDragHint: "按住拖拽至画布打开新工作流",
|
||||
recipeDragTargetCanvas: "🚀 释放以在画布打开新工作流",
|
||||
recipeDragTargetCanvas: "释放以在画布打开新工作流",
|
||||
recipeStatusReady: "全部就绪",
|
||||
recipeStatusUnverified: "待校验",
|
||||
recipeStatusMissing: "模型缺失",
|
||||
@@ -164,12 +164,12 @@ export const i18n = {
|
||||
materialChars: "{count} 字符",
|
||||
materialCopyPromptText: "复制提示词",
|
||||
notebookSaveError: '保存失败,修改仍留在编辑器中,请重试。',
|
||||
title: '📦 Anomalous 模型浏览器',
|
||||
title: 'Anomalous 模型浏览器',
|
||||
scan: '🔄 扫描目录并同步 C 站数据',
|
||||
scanTitle: '扫描目录',
|
||||
scanConfirm: '注意:\n扫描过程将会自动比对 Civitai 并重命名本地模型,同时清理损坏文件。\n确定要开始扫描吗?',
|
||||
scanning: '扫描中...',
|
||||
scanBg: '🚀 扫描后台启动!',
|
||||
scanBg: '扫描后台启动!',
|
||||
scanDone: '完成',
|
||||
scanCompleteMsg: '✅ 扫描完成!\n\n⚠️ 提示:由于部分模型已被重命名或去重,请点击 ComfyUI 的 [Refresh] 按钮以同步最新的模型列表。',
|
||||
eco: '🔋 视频悬浮播放 (节能模式)',
|
||||
@@ -190,7 +190,7 @@ export const i18n = {
|
||||
noPreview: '暂无预览图',
|
||||
clickScan: '点击扫描',
|
||||
back: '⬅️ 返回',
|
||||
delModel: '🗑️ 删除',
|
||||
delModel: '删除',
|
||||
delConfirm: '确定彻底删除',
|
||||
delConfirm2: '及其所有关联配置、预览图和缓存文件吗?此操作不可逆!',
|
||||
delSuccess: '✅ 删除成功!\n',
|
||||
@@ -204,7 +204,7 @@ export const i18n = {
|
||||
loading: '加载中…',
|
||||
loadingCompatible: '加载中...',
|
||||
backToPrev: '🔙 返回上一层',
|
||||
applyToCanvas: '➕ 插入节点',
|
||||
applyToCanvas: '插入节点',
|
||||
applySuccess: '✅ 已添加至工作流',
|
||||
help: '帮助',
|
||||
helpTitle: '📖 使用手册',
|
||||
@@ -215,12 +215,12 @@ export const i18n = {
|
||||
<p><strong>1. 🔄 前置准备 (扫描向导)</strong><br>具体位置:界面左侧底部的 <strong>扫描向导 (🔄)</strong>。<br>这是最重要的前置操作!请先打开扫描向导执行扫描。切记保持网络畅通。部分 C站限制级模型信息需要在设置中填入 API-KEY。扫描完成后,插件的各项功能基本激活。</p>
|
||||
<p><strong>2. 🩺 拯救爆红 (模型医生)</strong><br>具体位置:左侧底部第二个 <strong>听诊器 (🩺)</strong> 按钮。<br>当导入别人使用该插件输出的工作流或者图片时,若模型路径爆红,点击模型医生就能智能识别当前所有节点实现正确路径的替换。</p>
|
||||
<p><strong>3. 🤖 选中交互与参数预设 (节点助手)</strong><br>具体位置:左侧第三个 <strong>机器人 (🤖)</strong> 按钮。<br>在画布上选中节点后,“动作”页可以可视化替换模型,或在兼容的 MODEL + CLIP 链前后插入 LoRA。“参数方案”页会按配方分组读取工作流配方保存的同类型节点参数;点击一条记录即可把参数应用到当前选中节点。例如选中 KSampler 后,可一键载入配方中的采样器、调度器、步数、CFG 与降噪等值;种子等每次运行变化的参数会被忽略。</p>
|
||||
<p><strong>4. 📦 工作流配方、参数方案与素材库</strong><br>具体位置:打开 <strong>创作工作台</strong>。<br>配方会保存工作流、封面、标签、备注、模型引用及常用参数。未闭合的局部配方默认追加到当前画布,可独立运行的整体配方会在新画布打开;两者都支持导入/导出配方包。详情页包含概览、参数方案、图库和版本:可匹配本地模型、查看与复制完整参数、比较历史出图参数、比较或恢复配方版本。主图库或配方图库的图片悬浮后可进入参数详情,把原图、完整工作流和节点参数保存为一个素材快照;素材库可打开完整工作流,节点助手则按当前节点类型筛选并应用其中的参数。参数方案可从当前画布读取同一骨架的新参数,也可编辑安全控件并保存;“应用到当前工作流”只会在骨架匹配后写入参数。</p>
|
||||
<p><strong>5. ⚙️ 个性化配置 (设置面板)</strong><br>具体位置:左侧底部的 <strong>齿轮 (⚙️)</strong> 按钮。<br>可以调节语言、主页面字体大小;进入“模型设置”可选择视频封面始终播放或悬停播放,以及卡片使用流畅缩略图或原始封面。详情页始终保留原图画质。</p>
|
||||
<p><strong>6. 📑 顶部导航与提示词笔记</strong><br>具体位置:右侧顶部的按钮分别为 <strong>模型 (📦)</strong>、<strong>图库 (🖼️)</strong>、<strong>创作工作台 (📑)</strong>、<strong>停靠侧边栏 (◧)</strong>。<br>进入创作工作台的“提示词笔记”,点击新建 <strong>➕</strong>。选择基础模型、主模型和 LoRA 后,可通过 <strong>📝纯文本/粘贴</strong> 填入提示词并进行双语对照、分块、查找替换和标签编辑,最后点击 <strong>🚀 发送到画布</strong> 一键组装节点。</p>
|
||||
<p><strong>7. 📁 文件夹管理 (Folder Manager)</strong><br>具体位置:设置 (⚙️) 面板内的 <strong>📁 文件夹管理</strong>。<br>可以用来隐藏不需要的内置文件夹。隐藏后的文件夹不仅从侧边栏消失,后端的静默扫描和本地读取也会完全跳过,实现真正的“零性能损耗”。并且可以在这里拖拽调整文件夹在侧边栏显示的顺序。</p>
|
||||
<p><strong>8. 📥 工作流导出与导入 (分享码)</strong><br>具体位置:设置 (⚙️) 按钮左侧的 <strong>导入/导出 (📥)</strong> 按钮。<br>该功能可以将当前的工作流一键编码导出为一段专属分享码,方便发到社交平台。别人只需复制这串分享码,在此界面粘贴即可直接还原导入完整的工作流,并且在导入前系统会自动校验模型是否在本地存在。</p>
|
||||
<p><strong>9. 💬 提交反馈 / 报告问题</strong><br>具体位置:设置 (⚙️) 面板内的 <strong>💬 提交反馈 / 报告问题</strong>。<br>当你遇到插件报错或有新功能建议时,点击该按钮可一键直达 GitHub Issues 页面提交反馈。</p>
|
||||
<p><strong>4. 工作流配方、参数方案与素材库</strong><br>具体位置:打开 <strong>创作工作台</strong>。<br>配方会保存工作流、封面、标签、备注、模型引用及常用参数。未闭合的局部配方默认追加到当前画布,可独立运行的整体配方会在新画布打开;两者都支持导入/导出配方包。详情页包含概览、参数方案、图库和版本:可匹配本地模型、查看与复制完整参数、比较历史出图参数、比较或恢复配方版本。主图库或配方图库的图片悬浮后可进入参数详情,把原图、完整工作流和节点参数保存为一个素材快照;素材库可打开完整工作流,节点助手则按当前节点类型筛选并应用其中的参数。参数方案可从当前画布读取同一骨架的新参数,也可编辑安全控件并保存;“应用到当前工作流”只会在骨架匹配后写入参数。</p>
|
||||
<p><strong>5. 个性化配置 (设置面板)</strong><br>具体位置:左侧底部的 <strong>齿轮 ()</strong> 按钮。<br>可以调节语言、主页面字体大小;进入“模型设置”可选择视频封面始终播放或悬停播放,以及卡片使用流畅缩略图或原始封面。详情页始终保留原图画质。</p>
|
||||
<p><strong>6. 📑 顶部导航与提示词笔记</strong><br>具体位置:右侧顶部的按钮分别为 <strong>模型 ()</strong>、<strong>图库 ()</strong>、<strong>创作工作台 (📑)</strong>、<strong>停靠侧边栏 (◧)</strong>。<br>进入创作工作台的“提示词笔记”,点击新建 <strong></strong>。选择基础模型、主模型和 LoRA 后,可通过 <strong>📝纯文本/粘贴</strong> 填入提示词并进行双语对照、分块、查找替换和标签编辑,最后点击 <strong>发送到画布</strong> 一键组装节点。</p>
|
||||
<p><strong>7. 文件夹管理 (Folder Manager)</strong><br>具体位置:设置 () 面板内的 <strong>文件夹管理</strong>。<br>可以用来隐藏不需要的内置文件夹。隐藏后的文件夹不仅从侧边栏消失,后端的静默扫描和本地读取也会完全跳过,实现真正的“零性能损耗”。并且可以在这里拖拽调整文件夹在侧边栏显示的顺序。</p>
|
||||
<p><strong>8. 工作流导出与导入 (分享码)</strong><br>具体位置:设置 () 按钮左侧的 <strong>导入/导出 ()</strong> 按钮。<br>该功能可以将当前的工作流一键编码导出为一段专属分享码,方便发到社交平台。别人只需复制这串分享码,在此界面粘贴即可直接还原导入完整的工作流,并且在导入前系统会自动校验模型是否在本地存在。</p>
|
||||
<p><strong>9. 💬 提交反馈 / 报告问题</strong><br>具体位置:设置 () 面板内的 <strong>💬 提交反馈 / 报告问题</strong>。<br>当你遇到插件报错或有新功能建议时,点击该按钮可一键直达 GitHub Issues 页面提交反馈。</p>
|
||||
</div>`,
|
||||
notebooks: '笔记本',
|
||||
notebookTitle: '笔记本管理',
|
||||
@@ -252,7 +252,7 @@ export const i18n = {
|
||||
materialImageDetail: '图片参数详情',
|
||||
workbenchFullscreen: '全屏工作台',
|
||||
workbenchWindowed: '窗口模式',
|
||||
workbenchLoadToCanvas: '⚡ 加载到画布',
|
||||
workbenchLoadToCanvas: '加载到画布',
|
||||
workbenchLoadSuccess: '工作流已成功加载到 ComfyUI 画布!',
|
||||
workbenchLoadFailed: '无法加载工作流到画布:',
|
||||
workbenchNext: '下一张 (→)',
|
||||
@@ -293,11 +293,11 @@ export const i18n = {
|
||||
materialDenoise: '降噪',
|
||||
materialScheduler: '调度',
|
||||
materialReferencedModels: '引用的模型清单 ({count})',
|
||||
materialModelBase: '🎯 主模型 / UNet',
|
||||
materialModelBase: '主模型 / UNet',
|
||||
materialModelLora: '🎨 LoRA 微调层',
|
||||
materialModelVae: '🖼️ VAE 编码器',
|
||||
materialModelVae: 'VAE 编码器',
|
||||
materialModelClip: '👁️ 文本编码器 (CLIP)',
|
||||
materialModelOther: '⚡ 其它模型组件',
|
||||
materialModelOther: '其它模型组件',
|
||||
materialLoraStrength: '权重',
|
||||
materialModelCountLora: '{count} LoRA',
|
||||
materialModelCountClip: '{count} CLIP',
|
||||
@@ -369,11 +369,11 @@ export const i18n = {
|
||||
materialSourceRecipeMissing: '来源已中断 · 配方已删除:{name}',
|
||||
createNotebook: '新建',
|
||||
saveNotebook: '保存笔记',
|
||||
deleteNotebook: '🗑️ 删除',
|
||||
renameNotebook: '✏️ 重命名',
|
||||
deleteNotebook: '删除',
|
||||
renameNotebook: '重命名',
|
||||
baseModel: 'Base Model (基础模型)',
|
||||
mainModel: 'Main Model (主模型)',
|
||||
sendToCanvas: '🚀 发送到画布',
|
||||
sendToCanvas: '发送到画布',
|
||||
translate: '翻译',
|
||||
newNotebookName: '新笔记本名称',
|
||||
noBaseModel: '请先选择 Base Model',
|
||||
@@ -408,10 +408,10 @@ export const i18n = {
|
||||
detailBackDoctor: '⬅ 返回医生面板',
|
||||
detailBackAssistant: '⬅ 返回助手面板',
|
||||
detailJumpToBottom: '跳到底部',
|
||||
detailEdit: '⚙️ 编辑',
|
||||
detailEdit: '编辑',
|
||||
detailNotesTitle: '📓 专属备注 (Notes)',
|
||||
detailEditNotes: '编辑备注',
|
||||
detailGeneratedGallery: '🖼️ 查看历史生成作品',
|
||||
detailGeneratedGallery: '查看历史生成作品',
|
||||
detailTrainedWords: '训练词:',
|
||||
detailDescription: '描述:',
|
||||
detailNotes: '备注:',
|
||||
@@ -525,7 +525,7 @@ export const i18n = {
|
||||
assistantPreviewFailed: '无法加载预览',
|
||||
betaFeature: '测试功能',
|
||||
assistantBetaNotice: '“参数方案”联动仍在测试。应用前请保存当前工作流;它会修改选中节点的参数,但会忽略种子等运行时变化值。“动作”页不属于测试范围。',
|
||||
assistantHistoryTitle: '🖼️ 历史生成图 ({count})',
|
||||
assistantHistoryTitle: '历史生成图 ({count})',
|
||||
assistantViewAll: '查看全部 →',
|
||||
assistantRestoreWorkflow: '点击恢复此工作流',
|
||||
assistantViewGallery: '点击查看完整图库',
|
||||
@@ -534,7 +534,7 @@ export const i18n = {
|
||||
pickerInsertBeforeTitle: '⬅ 在节点前方插入 LoRA',
|
||||
pickerInsertAfterTitle: '在节点后方插入 LoRA ➡',
|
||||
pickerChangeTitle: '🔀 更换当前模型',
|
||||
pickerFolders: '📁 文件夹',
|
||||
pickerFolders: '文件夹',
|
||||
pickerSearch: '🔍 搜索名称或完整路径…',
|
||||
pickerMainAll: '主模型:全部类型',
|
||||
pickerNameAsc: '名称 A–Z',
|
||||
@@ -569,11 +569,11 @@ export const i18n = {
|
||||
pickerNoMatches: '没有符合条件的模型。',
|
||||
pickerOperationFailed: '操作失败:',
|
||||
notebookSelectMain: '请先选择一个主模型。',
|
||||
mainPreflightTitle: '📥 导入工作流 (预检海关)',
|
||||
mainPreflightTitle: '导入工作流 (预检海关)',
|
||||
mainPreflightDesc: '请在此处粘贴工作流的 JSON 代码(未来将支持分享码)。我们将为您提取并校验所有模型依赖。',
|
||||
mainCancel: '取消',
|
||||
mainAnalyzePreflight: '🔍 解析并预检',
|
||||
mainForceLoad: '🚀 强制载入画布',
|
||||
mainForceLoad: '强制载入画布',
|
||||
mainNoDependencies: '✅ 未检测到任何模型依赖。',
|
||||
mainDetectedModels: '扫描到 {count} 个模型资源:',
|
||||
mainDownloadCivitai: '去 C站下载',
|
||||
@@ -615,14 +615,14 @@ export const i18n = {
|
||||
mainResetFloatingTriggerPosition: '重置悬浮入口位置',
|
||||
mainRetryInit: '插件界面初始化失败,点击可重试',
|
||||
mainRetryInitEn: 'The plugin UI failed to initialize. Click to retry.',
|
||||
mainExportTitle: '📦 导出工作流分享码',
|
||||
mainExportTitle: '导出工作流分享码',
|
||||
mainSkeletonOption: '精简版 (推荐 B站评论区, 体积小, 丢失坐标)',
|
||||
mainFullOption: '完整版 (保留所有坐标与颜色, 较长)',
|
||||
mainGenerate: '生成分享码',
|
||||
mainCopyClipboard: '复制到剪贴板',
|
||||
mainClose: '关闭',
|
||||
mainCopied: '✅ 复制成功!',
|
||||
mainImportTitle: '📥 导入工作流分享码',
|
||||
mainImportTitle: '导入工作流分享码',
|
||||
mainSharePlaceholder: '粘贴 AMB0- 或 AMB1- 开头的分享码...',
|
||||
mainImportLoad: '导入并加载',
|
||||
mainShareEmpty: '❌ 分享码为空',
|
||||
@@ -630,17 +630,17 @@ export const i18n = {
|
||||
mainDecodeFailed: '❌ 解析失败: ',
|
||||
mainUnifiedTitle: '🔄 工作流分享与导入',
|
||||
mainExportWorkflow: '📤 导出当前工作流为分享码',
|
||||
mainImportWorkflow: '📥 从分享码导入工作流',
|
||||
mainImportWorkflow: '从分享码导入工作流',
|
||||
sidebarToggle: '收起/展开侧边栏',
|
||||
sidebarDoctor: '模型医生',
|
||||
sidebarAssistant: '节点助手',
|
||||
sidebarPreflight: '📥 预检导入工作流',
|
||||
sidebarPreflight: '预检导入工作流',
|
||||
sidebarSettings: '设置中心',
|
||||
sidebarScanWizard: '📡 模型扫描向导',
|
||||
sidebarScanComplete: '✅ 扫描/重命名已完成!数据已为您更新。',
|
||||
sidebarScanWizardTitle: '扫描向导',
|
||||
sidebarGlobalScanWizard: '全局扫描向导',
|
||||
sidebarSingleModelScan: '🎯 单模型扫描向导: {files}',
|
||||
sidebarSingleModelScan: '单模型扫描向导: {files}',
|
||||
sidebarGlobalWizardTitle: '全局扫描向导',
|
||||
sidebarApiKey: 'API 密钥',
|
||||
sidebarApiKeyPrompt: '请输入 Civitai API Key(留空将清除):',
|
||||
@@ -680,12 +680,12 @@ export const i18n = {
|
||||
sidebarExecute: '执行扫描',
|
||||
sidebarSwitchToEnglish: '🌐 Language: EN',
|
||||
sidebarSwitchToChinese: '🌐 语言: 中文',
|
||||
sidebarImportExport: '📥 导入 / 导出工作流 (分享码)',
|
||||
sidebarImportExport: '导入 / 导出工作流 (分享码)',
|
||||
sidebarGlobalQuickScan: '🌍 一键全盘极速扫描 (不下载封面/不改名)',
|
||||
sidebarGlobalQuickScanShort: '🌍 一键全盘扫描 (不下载封面/不改名)',
|
||||
sidebarGlobalQuickConfirm: '即将执行极速全盘扫描并同步Hash,此操作不改名也不下载封面,是否继续?',
|
||||
sidebarScanning: '⏳ 扫描中...',
|
||||
sidebarGlobalStarted: '🚀 全局扫描已后台启动!',
|
||||
sidebarGlobalStarted: '全局扫描已后台启动!',
|
||||
sidebarScanDone: '✅ 扫描完成',
|
||||
scanProgressTitle: '模型扫描进度',
|
||||
scanProgressPreparing: '正在准备扫描目录…',
|
||||
@@ -712,9 +712,9 @@ export const i18n = {
|
||||
sidebarResetConfirm: '确认重置窗口位置、缩放和停靠状态吗?',
|
||||
sidebarInjectHash: '🟢 注入工作流哈希',
|
||||
sidebarSkipHash: '⚪ 不注入工作流哈希',
|
||||
sidebarManageFolders: '📁 文件夹管理',
|
||||
sidebarManageFolders: '文件夹管理',
|
||||
sidebarFeedback: '💬 提交反馈 / 报告问题',
|
||||
sidebarModelSettings: '🖼️ 模型设置',
|
||||
sidebarModelSettings: '模型设置',
|
||||
sidebarModelCardSettings: '模型卡片设置',
|
||||
sidebarModelCardDescription: '只调整模型浏览器里的卡片显示,不会修改模型文件或原始封面。',
|
||||
sidebarVideoPlayback: '视频封面播放',
|
||||
@@ -731,7 +731,7 @@ export const i18n = {
|
||||
sidebarExpandAll: '展开全部',
|
||||
sidebarCollapseAll: '收起全部',
|
||||
sidebarSearchModels: '搜索模型...',
|
||||
sidebarFolderManagerTitle: '📁 侧边栏文件夹管理',
|
||||
sidebarFolderManagerTitle: '侧边栏文件夹管理',
|
||||
sidebarFolderManagerDesc: '上下拖拽调整侧边栏的显示顺序。点击眼睛图标可隐藏不需要显示的底层文件夹。隐藏的文件夹不会被扫描,完全不占用性能。',
|
||||
sidebarCategoryMode: '分类模式 (ComfyUI)',
|
||||
sidebarPhysicalMode: '物理模式 (本地文件夹)',
|
||||
@@ -756,7 +756,7 @@ export const i18n = {
|
||||
settingsBtn: '设置',
|
||||
closeSettings: '✖ 关闭设置面板',
|
||||
doctorBtn: '节点助手',
|
||||
importBtn: '📥 导入 / 导出工作流 (分享码)',
|
||||
importBtn: '导入 / 导出工作流 (分享码)',
|
||||
recipeTitle: '工作流配方',
|
||||
recipeBetaNotice: '工作流配方仍在测试。更新插件以及导入、恢复、删除或应用参数前,请保存当前画布并备份用户 workflows 目录中的 anomalous_recipes 与 anomalous_parameters。',
|
||||
recipeClose: '关闭工作流配方',
|
||||
@@ -938,7 +938,7 @@ export const i18n = {
|
||||
recipeDetailSampler: '采样器',
|
||||
recipeDetailResolution: '分辨率',
|
||||
recipeDetailUnavailable: '不可用',
|
||||
recipeDetailEditOrigin: '✏️ 编辑来源',
|
||||
recipeDetailEditOrigin: '编辑来源',
|
||||
recipeOriginDialogTitle: '编辑模型来源信息',
|
||||
recipeOriginOfficialName: '官方模型名称',
|
||||
recipeOriginModelUrl: '官方网址 (Model URL)',
|
||||
@@ -1046,7 +1046,7 @@ export const i18n = {
|
||||
recipeStatusMissing: '🔴 缺少模型 ({count})',
|
||||
recipeStatusChecking: '⏳ 检查环境中…',
|
||||
recipeQuickSpecs: '核心规格',
|
||||
recipeApplyAllMatches: '⚡ 一键应用全部匹配',
|
||||
recipeApplyAllMatches: '一键应用全部匹配',
|
||||
recipeApplyingAllMatches: '正在应用全部匹配…',
|
||||
recipeApplyAllMatchesSuccess: '✅ 已成功应用全部匹配!',
|
||||
recipeRawNodesToggle: '节点参数',
|
||||
@@ -1162,7 +1162,7 @@ export const i18n = {
|
||||
recipeViewGrid: "Grid View",
|
||||
recipeViewList: "List View",
|
||||
recipeDragHint: "Hold & drag onto canvas to load workflow",
|
||||
recipeDragTargetCanvas: "🚀 Drop to load workflow in canvas",
|
||||
recipeDragTargetCanvas: "Drop to load workflow in canvas",
|
||||
recipeStatusReady: "Ready",
|
||||
recipeStatusUnverified: "Unverified",
|
||||
recipeStatusMissing: "Missing",
|
||||
@@ -1224,12 +1224,12 @@ export const i18n = {
|
||||
materialChars: "{count} characters",
|
||||
materialCopyPromptText: "Copy prompt",
|
||||
notebookSaveError: 'Save failed. Your edits remain in the editor; please retry.',
|
||||
title: '📦 Anomalous Model Browser',
|
||||
title: 'Anomalous Model Browser',
|
||||
scan: '🔄 Scan folder and sync Civitai data',
|
||||
scanTitle: 'Scan Folder',
|
||||
scanConfirm: 'Notice:\nThe scan process will automatically compare with Civitai and rename your local files.\nStart scan?',
|
||||
scanning: 'Scanning...',
|
||||
scanBg: '🚀 Scan started in background!',
|
||||
scanBg: 'Scan started in background!',
|
||||
scanDone: 'Done',
|
||||
scanCompleteMsg: '✅ Scan Complete!\n\n⚠️ Note: Please click [Refresh] to sync the model list since files were renamed.',
|
||||
eco: '🔋 Eco Mode (Play video on hover)',
|
||||
@@ -1250,7 +1250,7 @@ export const i18n = {
|
||||
noPreview: 'No preview available',
|
||||
clickScan: 'Click Scan',
|
||||
back: '⬅️ Back',
|
||||
delModel: '🗑️ Delete',
|
||||
delModel: 'Delete',
|
||||
delConfirm: 'Are you sure you want to permanently delete',
|
||||
delConfirm2: ' and all its associated configs, previews, and cache files? This action is irreversible!',
|
||||
delSuccess: '✅ Delete Complete!\n',
|
||||
@@ -1264,7 +1264,7 @@ export const i18n = {
|
||||
loading: 'Loading…',
|
||||
loadingCompatible: 'Loading...',
|
||||
backToPrev: '🔙 Back to prev',
|
||||
applyToCanvas: '➕ Add Node',
|
||||
applyToCanvas: 'Add Node',
|
||||
applySuccess: '✅ Added to workflow',
|
||||
help: 'Help',
|
||||
helpTitle: '📖 User Manual',
|
||||
@@ -1275,12 +1275,12 @@ export const i18n = {
|
||||
<p><strong>1. 🔄 Prerequisites (Scan Wizard)</strong><br>Location: The <strong>Scan (🔄)</strong> icon at the bottom left.<br>This is the most crucial step! Open the Scan Wizard and execute a scan. Keep your network connected. (API-KEY may be needed in Settings for NSFW Civitai models). Scanning activates all core features.</p>
|
||||
<p><strong>2. 🩺 Fix Broken Nodes (Model Doctor)</strong><br>Location: The 2nd icon <strong>(Stethoscope 🩺)</strong> from the bottom left.<br>When importing a workflow/image exported by this plugin and nodes turn red, open the Model Doctor to intelligently identify and replace them with correct local paths.</p>
|
||||
<p><strong>3. 🤖 Node actions and parameter presets (Node Assistant)</strong><br>Location: The 3rd icon <strong>(Robot 🤖)</strong> from the bottom left.<br>After selecting a canvas node, Actions can visually replace its model or insert a LoRA before/after a compatible MODEL + CLIP chain. Parameter Presets reads same-type node values saved by Workflow Recipes and groups them by recipe. Click one record to apply it to the selected node. For example, selecting a KSampler can load its sampler, scheduler, steps, CFG, and denoise values in one click; volatile values such as seeds are ignored.</p>
|
||||
<p><strong>4. 📦 Workflow Recipes and Parameter Sets</strong><br>Location: Open the <strong>Creative Workspace</strong> and switch to <strong>Workflow Recipes</strong>.<br>A recipe saves the workflow, cover, tags, notes, model references, and common parameters. Open partial recipes append to the current canvas, while independently runnable complete recipes open in a new canvas; both can be shared as import/export packages. Details include Overview, Parameter Set, Gallery, and Versions for local model matching, full parameter viewing/copying, output comparison, and version comparison/restoration. Parameter notes can capture fresh values from a matching live canvas or edit safe controls; Apply to Current Workflow writes only after the recipe skeleton matches. Third-party prompt nodes that cannot be classified reliably can be labelled manually.</p>
|
||||
<p><strong>5. ⚙️ Settings Panel</strong><br>Location: The <strong>Gear (⚙️)</strong> icon at the bottom left.<br>Adjust language and UI scale. Open “Model Settings” to choose always-play or hover-play video covers and optimized thumbnails or original covers. Detail pages always retain original image quality.</p>
|
||||
<p><strong>6. 📑 Top Navigation & Prompt Notes</strong><br>Location: Top-right tabs <strong>Models (📦)</strong>, <strong>Gallery (🖼️)</strong>, <strong>Creative Workspace (📑)</strong>, and <strong>Dock (◧)</strong>.<br>Open Prompt Notes in the Creative Workspace and click <strong>➕</strong>. Select a Base Model, Main Model, and LoRAs; use <strong>📝 Edit Raw/Paste</strong> for bilingual prompts, chunking, find/replace, and tag editing; then click <strong>🚀 Send to Canvas</strong> to assemble the nodes.</p>
|
||||
<p><strong>7. 📁 Folder Manager</strong><br>Location: Inside the Settings (⚙️) panel, click <strong>📁 Manage Folders</strong>.<br>Use this to hide unneeded built-in folders. Hidden folders will be completely skipped by background scans and I/O reads, achieving true "zero performance cost". You can also drag and drop to reorder how folders appear in the sidebar.</p>
|
||||
<p><strong>8. 📥 Workflow Export & Import (Share Code)</strong><br>Location: The <strong>Import/Export (📥)</strong> icon to the left of the Settings gear.<br>You can encode your current workflow into a single share code for easy sharing on social platforms. Others can simply copy this code and paste it here to instantly import the workflow. The system will also automatically pre-check if the required models exist locally.</p>
|
||||
<p><strong>9. 💬 Submit Feedback / Report Bug</strong><br>Location: Inside the Settings (⚙️) panel, click <strong>💬 Submit Feedback / Report Bug</strong>.<br>If you encounter bugs or have feature requests, click this to go directly to our GitHub Issues page.</p>
|
||||
<p><strong>4. Workflow Recipes and Parameter Sets</strong><br>Location: Open the <strong>Creative Workspace</strong> and switch to <strong>Workflow Recipes</strong>.<br>A recipe saves the workflow, cover, tags, notes, model references, and common parameters. Open partial recipes append to the current canvas, while independently runnable complete recipes open in a new canvas; both can be shared as import/export packages. Details include Overview, Parameter Set, Gallery, and Versions for local model matching, full parameter viewing/copying, output comparison, and version comparison/restoration. Parameter notes can capture fresh values from a matching live canvas or edit safe controls; Apply to Current Workflow writes only after the recipe skeleton matches. Third-party prompt nodes that cannot be classified reliably can be labelled manually.</p>
|
||||
<p><strong>5. Settings Panel</strong><br>Location: The <strong>Gear ()</strong> icon at the bottom left.<br>Adjust language and UI scale. Open “Model Settings” to choose always-play or hover-play video covers and optimized thumbnails or original covers. Detail pages always retain original image quality.</p>
|
||||
<p><strong>6. 📑 Top Navigation & Prompt Notes</strong><br>Location: Top-right tabs <strong>Models ()</strong>, <strong>Gallery ()</strong>, <strong>Creative Workspace (📑)</strong>, and <strong>Dock (◧)</strong>.<br>Open Prompt Notes in the Creative Workspace and click <strong></strong>. Select a Base Model, Main Model, and LoRAs; use <strong>📝 Edit Raw/Paste</strong> for bilingual prompts, chunking, find/replace, and tag editing; then click <strong>Send to Canvas</strong> to assemble the nodes.</p>
|
||||
<p><strong>7. Folder Manager</strong><br>Location: Inside the Settings () panel, click <strong>Manage Folders</strong>.<br>Use this to hide unneeded built-in folders. Hidden folders will be completely skipped by background scans and I/O reads, achieving true "zero performance cost". You can also drag and drop to reorder how folders appear in the sidebar.</p>
|
||||
<p><strong>8. Workflow Export & Import (Share Code)</strong><br>Location: The <strong>Import/Export ()</strong> icon to the left of the Settings gear.<br>You can encode your current workflow into a single share code for easy sharing on social platforms. Others can simply copy this code and paste it here to instantly import the workflow. The system will also automatically pre-check if the required models exist locally.</p>
|
||||
<p><strong>9. 💬 Submit Feedback / Report Bug</strong><br>Location: Inside the Settings () panel, click <strong>💬 Submit Feedback / Report Bug</strong>.<br>If you encounter bugs or have feature requests, click this to go directly to our GitHub Issues page.</p>
|
||||
</div>`,
|
||||
notebooks: 'Notebooks',
|
||||
notebookTitle: 'Notebook Manager',
|
||||
@@ -1312,7 +1312,7 @@ export const i18n = {
|
||||
materialImageDetail: 'Image Parameter Details',
|
||||
workbenchFullscreen: 'Fullscreen Workbench',
|
||||
workbenchWindowed: 'Windowed Mode',
|
||||
workbenchLoadToCanvas: '⚡ Load to Canvas',
|
||||
workbenchLoadToCanvas: 'Load to Canvas',
|
||||
workbenchLoadSuccess: 'Workflow loaded to ComfyUI canvas successfully!',
|
||||
workbenchLoadFailed: 'Could not load workflow into canvas: ',
|
||||
workbenchNext: 'Next (→)',
|
||||
@@ -1353,11 +1353,11 @@ export const i18n = {
|
||||
materialDenoise: 'Denoise',
|
||||
materialScheduler: 'Scheduler',
|
||||
materialReferencedModels: 'Referenced Models ({count})',
|
||||
materialModelBase: '🎯 Base Model / UNet',
|
||||
materialModelBase: 'Base Model / UNet',
|
||||
materialModelLora: '🎨 LoRA Layers',
|
||||
materialModelVae: '🖼️ VAE',
|
||||
materialModelVae: 'VAE',
|
||||
materialModelClip: '👁️ Text Encoder (CLIP)',
|
||||
materialModelOther: '⚡ Other Models',
|
||||
materialModelOther: 'Other Models',
|
||||
materialLoraStrength: 'Weight',
|
||||
materialModelCountLora: '{count} LoRA',
|
||||
materialModelCountClip: '{count} CLIP',
|
||||
@@ -1429,11 +1429,11 @@ export const i18n = {
|
||||
materialSourceRecipeMissing: 'Source broken · recipe deleted: {name}',
|
||||
createNotebook: 'New',
|
||||
saveNotebook: '💾 Save prompt note',
|
||||
deleteNotebook: '🗑️ Delete',
|
||||
renameNotebook: '✏️ Rename',
|
||||
deleteNotebook: 'Delete',
|
||||
renameNotebook: 'Rename',
|
||||
baseModel: 'Base Model',
|
||||
mainModel: 'Main Model',
|
||||
sendToCanvas: '🚀 Send to Canvas',
|
||||
sendToCanvas: 'Send to Canvas',
|
||||
translate: 'Translate',
|
||||
newNotebookName: 'New Notebook Name',
|
||||
noBaseModel: 'Select a Base Model first',
|
||||
@@ -1468,10 +1468,10 @@ export const i18n = {
|
||||
detailBackDoctor: '⬅ Back to Doctor',
|
||||
detailBackAssistant: '⬅ Back to Assistant',
|
||||
detailJumpToBottom: 'Jump to bottom',
|
||||
detailEdit: '⚙️ Edit',
|
||||
detailEdit: 'Edit',
|
||||
detailNotesTitle: '📓 Custom Notes',
|
||||
detailEditNotes: 'Edit Notes',
|
||||
detailGeneratedGallery: '🖼️ View Generated Gallery',
|
||||
detailGeneratedGallery: 'View Generated Gallery',
|
||||
detailTrainedWords: 'Trained Words:',
|
||||
detailDescription: 'Description:',
|
||||
detailNotes: 'Notes:',
|
||||
@@ -1585,7 +1585,7 @@ export const i18n = {
|
||||
assistantPreviewFailed: 'Failed to load preview',
|
||||
betaFeature: 'Beta feature',
|
||||
assistantBetaNotice: 'The Parameter Set integration is still in beta. Save the current workflow before applying a preset; it changes the selected node while ignoring volatile values such as seeds. The Actions tab is not part of this beta.',
|
||||
assistantHistoryTitle: '🖼️ History ({count})',
|
||||
assistantHistoryTitle: 'History ({count})',
|
||||
assistantViewAll: 'View All →',
|
||||
assistantRestoreWorkflow: 'Click to restore workflow',
|
||||
assistantViewGallery: 'Click to view full gallery',
|
||||
@@ -1594,7 +1594,7 @@ export const i18n = {
|
||||
pickerInsertBeforeTitle: '⬅ Insert LoRA Before Node',
|
||||
pickerInsertAfterTitle: 'Insert LoRA After Node ➡',
|
||||
pickerChangeTitle: '🔀 Change Current Model',
|
||||
pickerFolders: '📁 Folders',
|
||||
pickerFolders: 'Folders',
|
||||
pickerSearch: '🔍 Search name or full path…',
|
||||
pickerMainAll: 'Main model: All types',
|
||||
pickerNameAsc: 'Name A–Z',
|
||||
@@ -1629,11 +1629,11 @@ export const i18n = {
|
||||
pickerNoMatches: 'No models match the current filters.',
|
||||
pickerOperationFailed: 'Operation failed: ',
|
||||
notebookSelectMain: 'Please select a Main Model first.',
|
||||
mainPreflightTitle: '📥 Pre-flight Import Workflow',
|
||||
mainPreflightTitle: 'Pre-flight Import Workflow',
|
||||
mainPreflightDesc: 'Paste the workflow JSON here. We will extract and validate all model dependencies before loading.',
|
||||
mainCancel: 'Cancel',
|
||||
mainAnalyzePreflight: '🔍 Analyze & Pre-flight',
|
||||
mainForceLoad: '🚀 Force Load to Canvas',
|
||||
mainForceLoad: 'Force Load to Canvas',
|
||||
mainNoDependencies: '✅ No model dependencies detected.',
|
||||
mainDetectedModels: 'Detected {count} model resources:',
|
||||
mainDownloadCivitai: 'Download',
|
||||
@@ -1675,14 +1675,14 @@ export const i18n = {
|
||||
mainResetFloatingTriggerPosition: 'Reset floating entry position',
|
||||
mainRetryInit: '插件界面初始化失败,点击可重试',
|
||||
mainRetryInitEn: 'The plugin UI failed to initialize. Click to retry.',
|
||||
mainExportTitle: '📦 Export Workflow Share Code',
|
||||
mainExportTitle: 'Export Workflow Share Code',
|
||||
mainSkeletonOption: 'Skeleton (Compact, best for comments, loses coords)',
|
||||
mainFullOption: 'Full (Keeps coords and colors, longer string)',
|
||||
mainGenerate: 'Generate',
|
||||
mainCopyClipboard: 'Copy',
|
||||
mainClose: 'Close',
|
||||
mainCopied: '✅ Copied successfully!',
|
||||
mainImportTitle: '📥 Import Workflow Share Code',
|
||||
mainImportTitle: 'Import Workflow Share Code',
|
||||
mainSharePlaceholder: 'Paste AMB0- or AMB1- share code here...',
|
||||
mainImportLoad: 'Import & Load',
|
||||
mainShareEmpty: '❌ Share code is empty',
|
||||
@@ -1690,17 +1690,17 @@ export const i18n = {
|
||||
mainDecodeFailed: '❌ Decode Failed: ',
|
||||
mainUnifiedTitle: '🔄 Workflow Share & Import',
|
||||
mainExportWorkflow: '📤 Export Workflow to Share Code',
|
||||
mainImportWorkflow: '📥 Import Workflow from Share Code',
|
||||
mainImportWorkflow: 'Import Workflow from Share Code',
|
||||
sidebarToggle: 'Toggle Sidebar',
|
||||
sidebarDoctor: 'Model Doctor',
|
||||
sidebarAssistant: 'Node Assistant',
|
||||
sidebarPreflight: '📥 Preflight Import',
|
||||
sidebarPreflight: 'Preflight Import',
|
||||
sidebarSettings: 'Settings Hub',
|
||||
sidebarScanWizard: '📡 Model Scan Wizard',
|
||||
sidebarScanComplete: '✅ Scan/Rename completed! Data updated.',
|
||||
sidebarScanWizardTitle: 'Scan Wizard',
|
||||
sidebarGlobalScanWizard: 'Global Scan Wizard',
|
||||
sidebarSingleModelScan: '🎯 Single Model Scan: {files}',
|
||||
sidebarSingleModelScan: 'Single Model Scan: {files}',
|
||||
sidebarGlobalWizardTitle: 'Global Scan Wizard',
|
||||
sidebarApiKey: 'API Key',
|
||||
sidebarApiKeyPrompt: 'Enter Civitai API Key (leave blank to clear):',
|
||||
@@ -1740,12 +1740,12 @@ export const i18n = {
|
||||
sidebarExecute: 'Execute Scan',
|
||||
sidebarSwitchToEnglish: '🌐 Language: EN',
|
||||
sidebarSwitchToChinese: '🌐 语言: 中文',
|
||||
sidebarImportExport: '📥 Import / Export Workflow (Share Code)',
|
||||
sidebarImportExport: 'Import / Export Workflow (Share Code)',
|
||||
sidebarGlobalQuickScan: '🌍 Global Quick Scan (No rename/No media)',
|
||||
sidebarGlobalQuickScanShort: '🌍 Global Quick Scan (No rename/No media)',
|
||||
sidebarGlobalQuickConfirm: 'Start global quick scan to sync all hashes?',
|
||||
sidebarScanning: '⏳ Scanning...',
|
||||
sidebarGlobalStarted: '🚀 Global Scan started in background!',
|
||||
sidebarGlobalStarted: 'Global Scan started in background!',
|
||||
sidebarScanDone: '✅ Scan Complete',
|
||||
scanProgressTitle: 'Model scan progress',
|
||||
scanProgressPreparing: 'Preparing scan folders…',
|
||||
@@ -1772,9 +1772,9 @@ export const i18n = {
|
||||
sidebarResetConfirm: 'Reset window position, scale and dock state?',
|
||||
sidebarInjectHash: '🟢 Inject Workflow Hash',
|
||||
sidebarSkipHash: '⚪ Skip Workflow Hash',
|
||||
sidebarManageFolders: '📁 Manage Folders',
|
||||
sidebarManageFolders: 'Manage Folders',
|
||||
sidebarFeedback: '💬 Submit Feedback / Report Bug',
|
||||
sidebarModelSettings: '🖼️ Model Settings',
|
||||
sidebarModelSettings: 'Model Settings',
|
||||
sidebarModelCardSettings: 'Model Card Settings',
|
||||
sidebarModelCardDescription: 'Only changes cards in the model browser. Model files and original covers remain untouched.',
|
||||
sidebarVideoPlayback: 'Video cover playback',
|
||||
@@ -1791,7 +1791,7 @@ export const i18n = {
|
||||
sidebarExpandAll: 'Expand All',
|
||||
sidebarCollapseAll: 'Collapse All',
|
||||
sidebarSearchModels: 'Search models...',
|
||||
sidebarFolderManagerTitle: '📁 Manage Folders',
|
||||
sidebarFolderManagerTitle: 'Manage Folders',
|
||||
sidebarFolderManagerDesc: 'Drag and drop to reorder folders. Click the eye icon to hide unneeded folders. Hidden folders are skipped during scanning and consume zero performance.',
|
||||
sidebarCategoryMode: 'Category Mode (ComfyUI)',
|
||||
sidebarPhysicalMode: 'Physical Mode (Local)',
|
||||
@@ -1816,7 +1816,7 @@ export const i18n = {
|
||||
settingsBtn: 'Settings',
|
||||
closeSettings: '✖ Close Settings',
|
||||
doctorBtn: 'Node Assistant',
|
||||
importBtn: '📥 Import / Export Workflow (Share Code)',
|
||||
importBtn: 'Import / Export Workflow (Share Code)',
|
||||
recipeTitle: 'Workflow Recipes',
|
||||
recipeBetaNotice: 'Workflow Recipes are still in beta. Before updating the plugin or importing, restoring, deleting, or applying parameters, save the canvas and back up anomalous_recipes and anomalous_parameters under your user workflows directory.',
|
||||
recipeClose: 'Close Workflow Recipes',
|
||||
@@ -1998,7 +1998,7 @@ export const i18n = {
|
||||
recipeDetailSampler: 'Sampler',
|
||||
recipeDetailResolution: 'Resolution',
|
||||
recipeDetailUnavailable: 'Unavailable',
|
||||
recipeDetailEditOrigin: '✏️ Edit Origin',
|
||||
recipeDetailEditOrigin: 'Edit Origin',
|
||||
recipeOriginDialogTitle: 'Edit Model Origin',
|
||||
recipeOriginOfficialName: 'Official Model Name',
|
||||
recipeOriginModelUrl: 'Official Model URL',
|
||||
@@ -2106,7 +2106,7 @@ export const i18n = {
|
||||
recipeStatusMissing: '🔴 Missing Models ({count})',
|
||||
recipeStatusChecking: '⏳ Checking Environment…',
|
||||
recipeQuickSpecs: 'Quick Specs',
|
||||
recipeApplyAllMatches: '⚡ Apply All Matches',
|
||||
recipeApplyAllMatches: 'Apply All Matches',
|
||||
recipeApplyingAllMatches: 'Applying all matches…',
|
||||
recipeApplyAllMatchesSuccess: '✅ Applied all local matches!',
|
||||
recipeRawNodesToggle: 'Raw Node Parameters',
|
||||
|
||||
@@ -485,7 +485,7 @@ export function showDetail(model) {
|
||||
notesTitle.style.letterSpacing = '0.5px';
|
||||
|
||||
const notesEditBtn = document.createElement('button');
|
||||
notesEditBtn.innerHTML = '✏️';
|
||||
notesEditBtn.innerHTML = '<svg style="width:13px;height:13px;vertical-align:middle;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>';
|
||||
notesEditBtn.title = t('detailEditNotes');
|
||||
notesEditBtn.style.background = 'transparent';
|
||||
notesEditBtn.style.border = 'none';
|
||||
@@ -858,7 +858,7 @@ export function showEditModal(model) {
|
||||
coverRow.style.gap = '8px';
|
||||
|
||||
const galleryBtn = document.createElement('button');
|
||||
galleryBtn.textContent = `🖼️ ${t('detailPickGallery')}`;
|
||||
galleryBtn.innerHTML = `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>${t('detailPickGallery')}`;
|
||||
galleryBtn.style.padding = '8px';
|
||||
galleryBtn.style.background = '#303134';
|
||||
galleryBtn.style.color = '#8ab4f8';
|
||||
@@ -875,7 +875,7 @@ export function showEditModal(model) {
|
||||
};
|
||||
|
||||
const localBtn = document.createElement('button');
|
||||
localBtn.textContent = `📁 ${t('detailUploadLocal')}`;
|
||||
localBtn.innerHTML = `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>${t('detailUploadLocal')}`;
|
||||
localBtn.style.padding = '8px';
|
||||
localBtn.style.background = '#303134';
|
||||
localBtn.style.color = '#8ab4f8';
|
||||
@@ -1641,7 +1641,7 @@ export function _openAdvancedModelSelector(initialSelectedMap, onConfirm) {
|
||||
|
||||
|
||||
} else {
|
||||
imgContainer.innerHTML = '<span style="font-size:40px;opacity:0.2;">📄</span>';
|
||||
imgContainer.innerHTML = '<span style="display:inline-block;opacity:0.25;"><svg style="width:40px;height:40px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></span>';
|
||||
}
|
||||
card.appendChild(imgContainer);
|
||||
|
||||
@@ -1882,7 +1882,7 @@ export function _openAdvancedModelSelector(initialSelectedMap, onConfirm) {
|
||||
leftPart.style.display = 'flex';
|
||||
leftPart.style.alignItems = 'center';
|
||||
leftPart.style.gap = '8px';
|
||||
leftPart.innerHTML = `<span style="color:#9aa0a6">📁</span> <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px;">${escapeHtml(fData.name)}</span> <span style="color:#5f6368;font-size:12px">(${escapeHtml(fData.model_count)})</span>`;
|
||||
leftPart.innerHTML = `<span style="display:inline-flex;align-items:center;color:#9aa0a6"><svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></span> <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px;">${escapeHtml(fData.name)}</span> <span style="color:#5f6368;font-size:12px">(${escapeHtml(fData.model_count)})</span>`;
|
||||
|
||||
const badge = document.createElement('div');
|
||||
badge.className = 'selection-badge';
|
||||
|
||||
+25
-12
@@ -1394,8 +1394,11 @@ export function _openGalleryReplacer(node, w, options = {}) {
|
||||
folders.forEach(folderPath => {
|
||||
const button = document.createElement('button');
|
||||
const depth = folderPath ? folderPath.split('/').length - 1 : 0;
|
||||
const label = folderPath ? folderPath.split('/').pop() : t('pickerAllModels');
|
||||
button.textContent = `${folderPath ? '📂' : '📦'} ${label} (${folderCounts.get(folderPath) || 0})`;
|
||||
const label = folderPath ? folderPath.split('/').pop() : t('pickerAllModels');
|
||||
const folderIcon = folderPath
|
||||
? '<svg style="width:13px;height:13px;vertical-align:-2px;margin-right:4px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>'
|
||||
: '<svg style="width:13px;height:13px;vertical-align:-2px;margin-right:4px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>';
|
||||
button.innerHTML = `${folderIcon}${escapeHtml(label)} (${folderCounts.get(folderPath) || 0})`;
|
||||
button.title = folderPath || label;
|
||||
button.style.cssText = `text-align:left;padding:7px 8px 7px ${8 + depth * 14}px;border-radius:6px;border:none;cursor:pointer;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:${selectedFolder === folderPath ? '#fff' : '#aaa'};background:${selectedFolder === folderPath ? 'rgba(25,118,210,0.45)' : 'transparent'};`;
|
||||
button.onclick = () => {
|
||||
@@ -1460,7 +1463,7 @@ export function _openGalleryReplacer(node, w, options = {}) {
|
||||
image.style.cssText = 'width:100%;height:100%;object-fit:cover;';
|
||||
previewBox.appendChild(image);
|
||||
} else {
|
||||
previewBox.textContent = '🖼️';
|
||||
previewBox.innerHTML = '<span style="display:inline-block;opacity:0.25;"><svg style="width:28px;height:28px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg></span>';
|
||||
}
|
||||
if (isCurrent) {
|
||||
const badge = document.createElement('span');
|
||||
@@ -1687,6 +1690,23 @@ function renderMaterialPresets(node, container, forceRefresh) {
|
||||
section.append(header, loader);
|
||||
container.appendChild(section);
|
||||
|
||||
const url = `/anomalous/materials/by_node_type?type=${encodeURIComponent(node.type)}${forceRefresh ? '&refresh=1' : ''}`;
|
||||
fetch(url, { cache: 'no-store' }).then(response => {
|
||||
if (!response.ok) throw new Error('material preset request failed');
|
||||
return response.json();
|
||||
}).then(payload => {
|
||||
loader.remove();
|
||||
const materials = Array.isArray(payload.materials) ? payload.materials : [];
|
||||
if (!materials.length) {
|
||||
const empty = document.createElement('div');
|
||||
empty.style.cssText = 'font-size:11px;color:#666;text-align:center;padding:10px;background:rgba(0,0,0,.2);border-radius:8px;border:1px dashed rgba(255,255,255,.1);';
|
||||
empty.textContent = t('materialNoNodePresets');
|
||||
const loader = document.createElement('div');
|
||||
loader.style.cssText = 'font-size:12px;color:#555;text-align:center;padding:10px;';
|
||||
loader.textContent = t('loading');
|
||||
section.append(header, loader);
|
||||
container.appendChild(section);
|
||||
|
||||
const url = `/anomalous/materials/by_node_type?type=${encodeURIComponent(node.type)}${forceRefresh ? '&refresh=1' : ''}`;
|
||||
fetch(url, { cache: 'no-store' }).then(response => {
|
||||
if (!response.ok) throw new Error('material preset request failed');
|
||||
@@ -1705,26 +1725,19 @@ function renderMaterialPresets(node, container, forceRefresh) {
|
||||
const group = document.createElement('div');
|
||||
group.style.cssText = 'display:flex;flex-direction:column;gap:6px;padding:8px;border:1px solid rgba(92,202,180,.16);border-radius:9px;background:rgba(92,202,180,.045);';
|
||||
const name = document.createElement('strong');
|
||||
name.textContent = `🖼️ ${material.name || t('materialUntitled')}`;
|
||||
name.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>${escapeHtml(material.name || t('materialUntitled'))}`;
|
||||
name.style.cssText = 'font-size:12px;color:#bcebe1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
|
||||
group.appendChild(name);
|
||||
for (const block of (Array.isArray(material.blocks) ? material.blocks : [])) {
|
||||
const apply = document.createElement('button');
|
||||
const suffix = material.blocks.length > 1 ? ` #${block.occurrence}` : '';
|
||||
const label = `✨ ${block.title || block.type}${suffix}`;
|
||||
const label = `${block.title || block.type}${suffix}`;
|
||||
apply.textContent = label;
|
||||
apply.style.cssText = 'padding:8px 10px;border:1px solid rgba(255,255,255,.1);border-radius:6px;background:rgba(255,255,255,.05);color:#fff;text-align:left;cursor:pointer;';
|
||||
apply.onclick = () => {
|
||||
apply.disabled = true;
|
||||
try {
|
||||
applyMaterialToSelectedNode(node, block, material.workflow_hashes, group);
|
||||
apply.textContent = `✅ ${t('materialNodeApplied')}`;
|
||||
apply.style.background = 'rgba(46,139,87,.6)';
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Anomalous] Failed to apply material node parameters:', error);
|
||||
apply.textContent = `⚠️ ${t('recipeParameterApplyError')}`;
|
||||
apply.style.background = 'rgba(180,60,60,.55)';
|
||||
}
|
||||
window.setTimeout(() => {
|
||||
apply.disabled = false;
|
||||
|
||||
@@ -152,7 +152,7 @@ export async function loadGalleryImages(page = 1, reset = false) {
|
||||
|
||||
const delBtn = document.createElement('button');
|
||||
delBtn.className = 'anomalous-gallery-delete';
|
||||
delBtn.innerHTML = '🗑️';
|
||||
delBtn.innerHTML = '<svg style="width:14px;height:14px;vertical-align:middle;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>';
|
||||
delBtn.title = t('galleryDelete');
|
||||
|
||||
delBtn.onclick = (e) => {
|
||||
@@ -189,7 +189,7 @@ export async function loadGalleryImages(page = 1, reset = false) {
|
||||
btnRow.style.gap = '12px';
|
||||
|
||||
const confirmBtn = document.createElement('button');
|
||||
confirmBtn.textContent = `🗑️ ${t('galleryDelete')}`;
|
||||
confirmBtn.innerHTML = `<svg style="width:14px;height:14px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>${t('galleryDelete')}`;
|
||||
confirmBtn.style.background = '#dc3545';
|
||||
confirmBtn.style.color = '#fff';
|
||||
confirmBtn.style.border = 'none';
|
||||
|
||||
@@ -673,11 +673,11 @@ function buildModelsSection(orderedRefs, groups, onOpenModel) {
|
||||
wrap.appendChild(groupEl);
|
||||
};
|
||||
|
||||
renderGroup(t('materialModelBase') || '🎯 主模型 / UNet', groups.base, 'is-base');
|
||||
renderGroup(t('materialModelLora') || '🎨 LoRA 微调层', groups.lora, 'is-lora');
|
||||
renderGroup(t('materialModelClip') || '👁️ 文本编码器 (CLIP)', groups.clip, 'is-clip');
|
||||
renderGroup(t('materialModelVae') || '🖼️ VAE 编码器', groups.vae, 'is-vae');
|
||||
renderGroup(t('materialModelOther') || '⚡ 其它模型组件', groups.other, 'is-other');
|
||||
renderGroup(t('materialModelBase') || '主模型 / UNet', groups.base, 'is-base');
|
||||
renderGroup(t('materialModelLora') || 'LoRA 微调层', groups.lora, 'is-lora');
|
||||
renderGroup(t('materialModelClip') || '文本编码器 (CLIP)', groups.clip, 'is-clip');
|
||||
renderGroup(t('materialModelVae') || 'VAE 编码器', groups.vae, 'is-vae');
|
||||
renderGroup(t('materialModelOther') || '其它模型组件', groups.other, 'is-other');
|
||||
|
||||
return wrap;
|
||||
}
|
||||
@@ -1075,10 +1075,10 @@ async function renderInspectorContent(data, item) {
|
||||
tabsBar.className = 'anomalous-workbench-tabs-bar';
|
||||
|
||||
const tabs = [
|
||||
{ id: 'specs', label: `📊 ${t('workbenchTabOverview') || '核心参数'}` },
|
||||
{ id: 'prompts', label: `💬 ${t('workbenchTabPrompts') || '提示词'}` },
|
||||
{ id: 'models', label: `🧩 ${t('workbenchTabModels') || '模型与LoRA'}` },
|
||||
{ id: 'nodes', label: `⚙️ ${t('workbenchTabNodes') || '工作流节点'}` },
|
||||
{ id: 'specs', label: t('workbenchTabOverview') || '核心参数', icon: '<svg style="width:13px;height:13px;margin-right:5px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></svg>' },
|
||||
{ id: 'prompts', label: t('workbenchTabPrompts') || '提示词', icon: '<svg style="width:13px;height:13px;margin-right:5px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>' },
|
||||
{ id: 'models', label: t('workbenchTabModels') || '模型与LoRA', icon: '<svg style="width:13px;height:13px;margin-right:5px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>' },
|
||||
{ id: 'nodes', label: t('workbenchTabNodes') || '工作流节点', icon: '<svg style="width:13px;height:13px;margin-right:5px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>' },
|
||||
];
|
||||
|
||||
const tabPanels = {};
|
||||
@@ -1090,7 +1090,7 @@ async function renderInspectorContent(data, item) {
|
||||
tabBtn.className = (wb.activeTab === tDef.id)
|
||||
? 'anomalous-workbench-tab-btn is-active'
|
||||
: 'anomalous-workbench-tab-btn';
|
||||
tabBtn.textContent = tDef.label;
|
||||
tabBtn.innerHTML = `${tDef.icon}<span>${tDef.label}</span>`;
|
||||
|
||||
tabBtn.onclick = () => {
|
||||
wb.activeTab = tDef.id;
|
||||
|
||||
@@ -159,7 +159,7 @@ export async function loadModels() {
|
||||
};
|
||||
|
||||
const applyBtn = document.createElement('button');
|
||||
applyBtn.innerHTML = '➕';
|
||||
applyBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>`;
|
||||
applyBtn.className = 'anomalous-card-action-btn anomalous-tooltip-target';
|
||||
applyBtn.removeAttribute('title');
|
||||
applyBtn.setAttribute('data-tooltip', t('applyToCanvas'));
|
||||
@@ -178,7 +178,7 @@ export async function loadModels() {
|
||||
applyBtn.style.display = 'none';
|
||||
|
||||
const singleScanBtn = document.createElement('button');
|
||||
singleScanBtn.innerHTML = '🎯';
|
||||
singleScanBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><circle cx="12" cy="12" r="10"/><line x1="22" y1="12" x2="18" y2="12"/><line x1="6" y1="12" x2="2" y2="12"/><line x1="12" y1="6" x2="12" y2="2"/><line x1="12" y1="22" x2="12" y2="18"/></svg>`;
|
||||
singleScanBtn.className = 'anomalous-card-action-btn anomalous-tooltip-target';
|
||||
singleScanBtn.removeAttribute('title');
|
||||
singleScanBtn.setAttribute('data-tooltip', t('scanModelPrecisely'));
|
||||
@@ -203,7 +203,7 @@ export async function loadModels() {
|
||||
card.appendChild(singleScanBtn);
|
||||
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.innerHTML = '⚙️';
|
||||
editBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`;
|
||||
editBtn.className = 'anomalous-card-action-btn anomalous-tooltip-target';
|
||||
editBtn.removeAttribute('title');
|
||||
editBtn.setAttribute('data-tooltip', t('editModel'));
|
||||
|
||||
@@ -266,8 +266,9 @@ function buildMaterialDetailHeader(owner, material) {
|
||||
const headerActions = document.createElement('div');
|
||||
headerActions.className = 'anomalous-library-detail-actions';
|
||||
if ((material.capabilities || []).includes('open_workflow')) {
|
||||
const open = text(headerActions, 'button', `🚀 ${t('materialOpenWorkflow')}`, 'anomalous-btn-primary');
|
||||
const open = text(headerActions, 'button', '', 'anomalous-btn-primary');
|
||||
open.type = 'button';
|
||||
open.innerHTML = `<svg style="width:14px;height:14px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>${t('materialOpenWorkflow')}`;
|
||||
open.onclick = async () => {
|
||||
open.disabled = true;
|
||||
try {
|
||||
@@ -282,8 +283,9 @@ function buildMaterialDetailHeader(owner, material) {
|
||||
const edit = text(headerActions, 'button', t('materialEditDetails'), 'anomalous-btn-ghost');
|
||||
edit.type = 'button';
|
||||
edit.onclick = () => toggleMaterialEditor(owner, material, header);
|
||||
const remove = text(headerActions, 'button', `🗑️ ${t('materialDelete')}`, 'anomalous-btn-ghost');
|
||||
const remove = text(headerActions, 'button', '', 'anomalous-btn-ghost');
|
||||
remove.type = 'button';
|
||||
remove.innerHTML = `<svg style="width:14px;height:14px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>${t('materialDelete')}`;
|
||||
remove.onclick = () => deleteMaterial(owner, material);
|
||||
header.appendChild(headerActions);
|
||||
|
||||
@@ -515,7 +517,7 @@ function renderMaterialCard(owner, material) {
|
||||
const remove = document.createElement('button');
|
||||
remove.type = 'button';
|
||||
remove.className = 'anomalous-material-card-action-btn anomalous-material-card-delete';
|
||||
remove.innerHTML = '🗑️';
|
||||
remove.innerHTML = '<svg style="width:13px;height:13px;vertical-align:middle;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>';
|
||||
remove.title = t('materialDelete');
|
||||
remove.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -529,7 +531,7 @@ function renderMaterialCard(owner, material) {
|
||||
const quickOpen = document.createElement('button');
|
||||
quickOpen.type = 'button';
|
||||
quickOpen.className = 'anomalous-material-card-action-btn anomalous-material-card-quick-load';
|
||||
quickOpen.innerHTML = '🚀';
|
||||
quickOpen.innerHTML = '<svg style="width:13px;height:13px;vertical-align:middle;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>';
|
||||
quickOpen.title = t('materialOpenWorkflow');
|
||||
quickOpen.onclick = async (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -139,7 +139,8 @@ export async function showNotebooks() {
|
||||
btnRow.style.gap = '5px';
|
||||
|
||||
const createBtn = document.createElement('button');
|
||||
createBtn.innerHTML = `➕ <span class="anomalous-nb-create-text">${t('createNotebook')}</span>`;
|
||||
const createBtnHtml = `<svg style="width:13px;height:13px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg><span class="anomalous-nb-create-text">${t('createNotebook')}</span>`;
|
||||
createBtn.innerHTML = createBtnHtml;
|
||||
createBtn.className = 'anomalous-btn-primary';
|
||||
|
||||
const createInput = document.createElement('input');
|
||||
@@ -168,7 +169,7 @@ export async function showNotebooks() {
|
||||
createInput.value = '';
|
||||
}
|
||||
createInput.style.display = 'none';
|
||||
createBtn.innerHTML = `➕ <span class="anomalous-nb-create-text">${t('createNotebook')}</span>`;
|
||||
createBtn.innerHTML = createBtnHtml;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -217,7 +218,7 @@ export async function refreshNotebooks(autoOpenFirst = false) {
|
||||
if (this.currentNotebook && this.currentNotebook.filename === nb.filename) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
item.innerHTML = `<span class="anomalous-nb-item-icon">📄 </span><span class="anomalous-nb-item-text">${escapeHtml(nb.name)}</span>`;
|
||||
item.innerHTML = `<span class="anomalous-nb-item-icon"><svg style="width:13px;height:13px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></span><span class="anomalous-nb-item-text">${escapeHtml(nb.name)}</span>`;
|
||||
item.onclick = () => {
|
||||
this.currentNotebook = nb;
|
||||
this.renderNotebookEditor();
|
||||
|
||||
@@ -331,21 +331,24 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
const leftPanel = text(workbenchGrid, 'section', '', 'anomalous-workbench-left-panel');
|
||||
const leftHeader = text(leftPanel, 'div', '', 'anomalous-workbench-col-header');
|
||||
const leftTitleWrap = text(leftHeader, 'div', '', 'anomalous-workbench-col-title');
|
||||
leftTitleWrap.innerHTML = `📚 <strong>${window.anomalous_browser_lang === 'zh' ? '成型提示词库' : 'Prompt Library'}</strong> <span class="anomalous-sub-counter"></span>`;
|
||||
leftTitleWrap.innerHTML = `<strong>${window.anomalous_browser_lang === 'zh' ? '成型提示词库' : 'Prompt Library'}</strong> <span class="anomalous-sub-counter"></span>`;
|
||||
|
||||
// Button: Create New Custom Card (placed in header right, never wraps)
|
||||
const newCardTriggerBtn = text(leftHeader, 'button', `➕ ${window.anomalous_browser_lang === 'zh' ? '新建词卡' : 'New Card'}`, 'anomalous-btn-ghost anomalous-btn-sm');
|
||||
const newCardTriggerBtn = text(leftHeader, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm');
|
||||
newCardTriggerBtn.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>${window.anomalous_browser_lang === 'zh' ? '新建词卡' : 'New Card'}`;
|
||||
newCardTriggerBtn.title = window.anomalous_browser_lang === 'zh' ? '新建并保存一张提示词卡片' : 'Create a new prompt card';
|
||||
|
||||
// Sub-action bar: Node extract and Material Library import (full-width, balanced)
|
||||
const leftSubActions = text(leftPanel, 'div', '', 'anomalous-workbench-sub-actions');
|
||||
|
||||
// Button 1: Extract Prompts from Selected Canvas Node
|
||||
const extractNodeBtn = text(leftSubActions, 'button', `🎯 ${window.anomalous_browser_lang === 'zh' ? '从节点提取' : 'From Node'}`, 'anomalous-btn-primary anomalous-btn-sm anomalous-btn-extract-node');
|
||||
const extractNodeBtn = text(leftSubActions, 'button', '', 'anomalous-btn-primary anomalous-btn-sm anomalous-btn-extract-node');
|
||||
extractNodeBtn.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>${window.anomalous_browser_lang === 'zh' ? '从节点提取' : 'From Node'}`;
|
||||
extractNodeBtn.title = window.anomalous_browser_lang === 'zh' ? '读取 ComfyUI 画布当前选中节点的提示词文本并生成词卡' : 'Extract prompt text from selected canvas node into cards';
|
||||
|
||||
// Button 2: One-click Sync / Import from Material Library
|
||||
const importMaterialsBtn = text(leftSubActions, 'button', `📥 ${window.anomalous_browser_lang === 'zh' ? '从素材库导入' : 'Import from Library'}`, 'anomalous-btn-ghost anomalous-btn-sm');
|
||||
const importMaterialsBtn = text(leftSubActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm');
|
||||
importMaterialsBtn.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>${window.anomalous_browser_lang === 'zh' ? '从素材库导入' : 'Import from Library'}`;
|
||||
importMaterialsBtn.title = window.anomalous_browser_lang === 'zh' ? '读取素材库,一键把素材库里沉淀的提示词捞入当前工坊词库' : 'Read and import all prompts from Material Library into workbench';
|
||||
|
||||
// Search and category filters bar
|
||||
@@ -360,10 +363,10 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
const leftCategoryPills = text(leftPanel, 'div', '', 'anomalous-workbench-category-pills');
|
||||
const filterCats = [
|
||||
{ id: 'all', label: window.anomalous_browser_lang === 'zh' ? '全部' : 'All' },
|
||||
{ id: 'base', label: window.anomalous_browser_lang === 'zh' ? '💎 通用' : '💎 Base' },
|
||||
{ id: 'style', label: window.anomalous_browser_lang === 'zh' ? '🎨 风格' : '🎨 Style' },
|
||||
{ id: 'subject', label: window.anomalous_browser_lang === 'zh' ? '🧍 主体' : '🧍 Subject' },
|
||||
{ id: 'trigger', label: window.anomalous_browser_lang === 'zh' ? '⚡ 触发' : '⚡ LoRA' },
|
||||
{ id: 'base', label: window.anomalous_browser_lang === 'zh' ? '通用底模' : 'Base' },
|
||||
{ id: 'style', label: window.anomalous_browser_lang === 'zh' ? '风格氛围' : 'Style' },
|
||||
{ id: 'subject', label: window.anomalous_browser_lang === 'zh' ? '主体内容' : 'Subject' },
|
||||
{ id: 'trigger', label: window.anomalous_browser_lang === 'zh' ? '触发词' : 'Trigger' },
|
||||
];
|
||||
filterCats.forEach(cat => {
|
||||
const pill = text(leftCategoryPills, 'button', cat.label, `anomalous-workbench-pill${sourceFilterCategory === cat.id ? ' is-active' : ''}`);
|
||||
@@ -394,13 +397,16 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
const rightActions = text(rightHeader, 'div', '', 'anomalous-mixer-actions');
|
||||
|
||||
// Right quick extract: suck into right mixer directly
|
||||
const rightSuckNodeBtn = text(rightActions, 'button', `🎯 ${t('promptReadSelectedNode')}`, 'anomalous-btn-ghost anomalous-btn-sm');
|
||||
const rightSuckNodeBtn = text(rightActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm');
|
||||
rightSuckNodeBtn.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>${t('promptReadSelectedNode')}`;
|
||||
rightSuckNodeBtn.title = window.anomalous_browser_lang === 'zh' ? '直接将画布选中节点的提示词作为积木吸入当前拼装台' : 'Extract node prompt directly into current mixer track';
|
||||
|
||||
const smartSortBtn = text(rightActions, 'button', `🪄 ${t('promptSortByCategory')}`, 'anomalous-mixer-smart-sort-btn');
|
||||
const smartSortBtn = text(rightActions, 'button', '', 'anomalous-mixer-smart-sort-btn');
|
||||
smartSortBtn.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>${t('promptSortByCategory')}`;
|
||||
smartSortBtn.title = window.anomalous_browser_lang === 'zh' ? '按 [通用底模 ➔ 风格氛围 ➔ 主体内容 ➔ LoRA/触发词] 自动排序' : 'Auto sort: [Base ➔ Style ➔ Subject ➔ Trigger]';
|
||||
|
||||
const clearRightBtn = text(rightActions, 'button', '🧹', 'anomalous-btn-ghost');
|
||||
const clearRightBtn = text(rightActions, 'button', '', 'anomalous-btn-ghost');
|
||||
clearRightBtn.innerHTML = '<svg style="width:13px;height:13px;vertical-align:middle;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
||||
clearRightBtn.title = window.anomalous_browser_lang === 'zh' ? '清空当前拼装池' : 'Clear current track';
|
||||
|
||||
// Blocks Container & Dropzone
|
||||
@@ -541,13 +547,13 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
|
||||
if (extractedCount > 0) {
|
||||
renderSourceCardsList();
|
||||
showWorkbenchToast(`🎯 ${window.anomalous_browser_lang === 'zh'
|
||||
showWorkbenchToast(window.anomalous_browser_lang === 'zh'
|
||||
? `已成功从节点【${heading}】提取 ${extractedCount} 段提示词${intoRightMixer ? '并加入组合' : '并加入左侧词库'}!`
|
||||
: `Successfully extracted ${extractedCount} prompts from [${heading}]!`}`);
|
||||
: `Successfully extracted ${extractedCount} prompts from [${heading}]!`);
|
||||
} else {
|
||||
anomalousAlert(window.anomalous_browser_lang === 'zh'
|
||||
? `⚠️ 选中的节点【${heading}】文本内容为空!`
|
||||
: `⚠️ The text fields in node [${heading}] are empty!`);
|
||||
? `选中的节点【${heading}】文本内容为空!`
|
||||
: `The text fields in node [${heading}] are empty!`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,12 +563,14 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
let materialSyncController = null;
|
||||
async function syncMaterialsIntoSourceDeck() {
|
||||
materialSyncController?.abort();
|
||||
if (materialSyncController) {
|
||||
materialSyncController.abort();
|
||||
}
|
||||
const controller = new AbortController();
|
||||
materialSyncController = controller;
|
||||
|
||||
importMaterialsBtn.disabled = true;
|
||||
importMaterialsBtn.textContent = `⏳ ${window.anomalous_browser_lang === 'zh' ? '同步中...' : 'Syncing...'}`;
|
||||
let addedCount = 0;
|
||||
importMaterialsBtn.textContent = window.anomalous_browser_lang === 'zh' ? '正在同步素材...' : 'Syncing...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/anomalous/materials?category=prompts&limit=150', { signal: controller.signal });
|
||||
@@ -583,7 +591,6 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
persisted: true,
|
||||
filename: item.filename,
|
||||
});
|
||||
addedCount++;
|
||||
}
|
||||
if (prompts.negative && !sourceCards.some(c => c.content === prompts.negative.trim())) {
|
||||
sourceCards.push({
|
||||
@@ -595,7 +602,6 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
persisted: true,
|
||||
filename: item.filename,
|
||||
});
|
||||
addedCount++;
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
@@ -603,9 +609,9 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
|
||||
if (controller.signal.aborted || !view.isConnected) return;
|
||||
renderSourceCardsList();
|
||||
showWorkbenchToast(`📥 ${window.anomalous_browser_lang === 'zh'
|
||||
showWorkbenchToast(window.anomalous_browser_lang === 'zh'
|
||||
? (addedCount > 0 ? `已从素材库成功同步并导入 ${addedCount} 条提示词卡片!` : '素材库提示词已是最新状态,未发现新词条。')
|
||||
: `Synced from Material Library: ${addedCount} new prompts added!`}`);
|
||||
: `Synced from Material Library: ${addedCount} new prompts added!`);
|
||||
} catch (error) {
|
||||
if (error.name !== 'AbortError') {
|
||||
await anomalousAlert(t('materialLoadError'));
|
||||
@@ -614,7 +620,7 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
if (materialSyncController === controller) {
|
||||
materialSyncController = null;
|
||||
importMaterialsBtn.disabled = false;
|
||||
importMaterialsBtn.textContent = `📥 ${window.anomalous_browser_lang === 'zh' ? '从素材库导入' : 'Import from Library'}`;
|
||||
importMaterialsBtn.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>${window.anomalous_browser_lang === 'zh' ? '从素材库导入' : 'Import from Library'}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -893,7 +899,8 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
};
|
||||
|
||||
if (card.persisted === false || (card.id.startsWith('node_') && !card.persisted)) {
|
||||
const saveCardBtn = text(actions, 'button', `💾 ${t('promptSaveCard')}`, 'anomalous-source-action-btn is-save-lib');
|
||||
const saveCardBtn = text(actions, 'button', '', 'anomalous-source-action-btn is-save-lib');
|
||||
saveCardBtn.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>${t('promptSaveCard')}`;
|
||||
saveCardBtn.title = window.anomalous_browser_lang === 'zh' ? '将此卡片存入素材库' : 'Save this card to Material Library';
|
||||
saveCardBtn.onclick = async (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -901,7 +908,8 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
const dockBtn = text(actions, 'button', `➕ ${t('promptAddToPlan')}`, 'anomalous-source-action-btn is-dock');
|
||||
const dockBtn = text(actions, 'button', '', 'anomalous-source-action-btn is-dock');
|
||||
dockBtn.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>${t('promptAddToPlan')}`;
|
||||
dockBtn.title = window.anomalous_browser_lang === 'zh' ? '加入当前拼装组合' : 'Add into mixer track';
|
||||
dockBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -919,8 +927,8 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
const negCount = draft.plan.parts.filter(p => p.role === 'negative').length;
|
||||
|
||||
const tabs = [
|
||||
{ key: 'positive', label: `✨ ${window.anomalous_browser_lang === 'zh' ? '正向拼装台' : 'Positive'} (${posCount})` },
|
||||
{ key: 'negative', label: `🚫 ${window.anomalous_browser_lang === 'zh' ? '负向拼装台' : 'Negative'} (${negCount})` },
|
||||
{ key: 'positive', label: `${window.anomalous_browser_lang === 'zh' ? '正向拼装台' : 'Positive'} (${posCount})` },
|
||||
{ key: 'negative', label: `${window.anomalous_browser_lang === 'zh' ? '负向拼装台' : 'Negative'} (${negCount})` },
|
||||
];
|
||||
|
||||
for (const tab of tabs) {
|
||||
@@ -968,7 +976,13 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
if (!currentRoleParts.length) {
|
||||
const dropzoneNotice = text(blocksContainer, 'div', '', 'anomalous-assembly-dropzone');
|
||||
dropzoneNotice.innerHTML = `
|
||||
<div style="font-size: 30px; margin-bottom: 8px;">📥</div>
|
||||
<div style="margin-bottom: 12px; opacity: 0.7;">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="7 10 12 15 17 10"/>
|
||||
<line x1="12" y1="15" x2="12" y2="3"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div style="font-size: 0.92rem; font-weight: 600; color: #e2e8f0;">${window.anomalous_browser_lang === 'zh' ? '将左侧的成型提示词拖放到这里摆放与拼装' : 'Drop prompt cards from the left panel here'}</div>
|
||||
<div style="font-size: 0.76rem; color: #64748b; margin-top: 4px;">${window.anomalous_browser_lang === 'zh' ? '支持自由调换次序,点击【智能理顺】自动按画质底模➔风格➔主体➔LoRA排序' : 'Drag to reorder anytime, or click Smart Sort to auto-align.'}</div>
|
||||
`;
|
||||
@@ -1257,12 +1271,12 @@ function buildPromptComposer(owner, container, options = {}) {
|
||||
const targets = promptWidgetTargets(node);
|
||||
|
||||
if (!node || !targets.length) {
|
||||
text(targetBar, 'span', `💡 ${t('promptSelectTextNode')}`, 'anomalous-prompt-target-info');
|
||||
text(targetBar, 'span', t('promptSelectTextNode'), 'anomalous-prompt-target-info');
|
||||
return;
|
||||
}
|
||||
|
||||
const info = text(targetBar, 'div', '', 'anomalous-prompt-target-info');
|
||||
info.textContent = `🎯 ${t('materialApplyingTo', { name: materialNodeHeading(node), id: node.id })}`;
|
||||
info.textContent = t('materialApplyingTo', { name: materialNodeHeading(node), id: node.id });
|
||||
|
||||
const actions = text(targetBar, 'div', '', 'anomalous-prompt-target-actions');
|
||||
const widgetSelect = text(actions, 'select', '');
|
||||
|
||||
@@ -1082,11 +1082,15 @@ function renderOverview(content, owner, recipe, references, finish) {
|
||||
overviewActions.style.alignItems = 'center';
|
||||
overviewActions.style.gap = '8px';
|
||||
|
||||
const heroAppendIcon = recipe?.workflow_scope === 'partial'
|
||||
? `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>`
|
||||
: `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>`;
|
||||
const heroAppend = button(
|
||||
overviewActions,
|
||||
`${recipe?.workflow_scope === 'partial' ? '🧩' : '🚀'} ${recipeCanvasActionLabel(recipe)}`,
|
||||
'',
|
||||
'anomalous-recipe-btn-primary-action',
|
||||
);
|
||||
heroAppend.innerHTML = `${heroAppendIcon}${recipeCanvasActionLabel(recipe)}`;
|
||||
heroAppend.style.padding = '8px 16px';
|
||||
heroAppend.style.fontSize = '0.88rem';
|
||||
heroAppend.onclick = () => {
|
||||
@@ -1100,11 +1104,13 @@ function renderOverview(content, owner, recipe, references, finish) {
|
||||
appendText(more, 'summary', t('notebookMore'));
|
||||
overviewActions.appendChild(more);
|
||||
|
||||
const heroEdit = button(more, '✏️ ' + t('recipeEdit'), 'anomalous-btn-ghost');
|
||||
const heroEdit = button(more, '', 'anomalous-btn-ghost');
|
||||
heroEdit.innerHTML = `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>${t('recipeEdit')}`;
|
||||
heroEdit.title = t('recipeEdit');
|
||||
heroEdit.onclick = () => finish('edit');
|
||||
|
||||
const heroExport = button(more, '📥 ' + t('recipeExport'), 'anomalous-btn-ghost');
|
||||
const heroExport = button(more, '', 'anomalous-btn-ghost');
|
||||
heroExport.innerHTML = `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>${t('recipeExport')}`;
|
||||
heroExport.title = t('recipeExport');
|
||||
heroExport.onclick = async () => {
|
||||
try {
|
||||
@@ -1164,7 +1170,7 @@ function renderOverview(content, owner, recipe, references, finish) {
|
||||
const advanced = document.createElement('details');
|
||||
advanced.className = 'anomalous-recipe-advanced-info';
|
||||
advanced.style.marginBottom = '14px';
|
||||
appendText(advanced, 'summary', `⚙️ ${t('recipeAdvancedInfo')}`);
|
||||
appendText(advanced, 'summary', t('recipeAdvancedInfo'));
|
||||
const fingerprint = document.createElement('div');
|
||||
fingerprint.className = 'anomalous-recipe-advanced-row';
|
||||
appendText(fingerprint, 'span', `${t('recipeDetailFingerprint')}:`);
|
||||
@@ -2031,7 +2037,7 @@ function renderRawNodesLazy(parent, source, options = {}) {
|
||||
|
||||
const summary = document.createElement('summary');
|
||||
summary.className = 'anomalous-recipe-raw-nodes-summary';
|
||||
summary.textContent = `⚙️ ${t('recipeRawNodesToggle')} (${ordered.length} ${t('recipeRawNodesCount')}) ▾`;
|
||||
summary.textContent = `${t('recipeRawNodesToggle')} (${ordered.length} ${t('recipeRawNodesCount')}) ▾`;
|
||||
details.appendChild(summary);
|
||||
|
||||
let selecting = false;
|
||||
@@ -2436,14 +2442,16 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
} finally {
|
||||
readCurrent.disabled = false;
|
||||
readCurrent.classList.remove('is-busy');
|
||||
readCurrent.textContent = originalLabel;
|
||||
readCurrent.innerHTML = originalLabel;
|
||||
}
|
||||
};
|
||||
|
||||
const readCurrent = button(sidebarActions, '💾 ' + t('recipeParameterReadCurrent'), 'anomalous-preset-btn-primary');
|
||||
const readCurrent = button(sidebarActions, '', 'anomalous-preset-btn-primary');
|
||||
readCurrent.innerHTML = `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>${t('recipeParameterReadCurrent')}`;
|
||||
readCurrent.onclick = readCurrentHandler;
|
||||
|
||||
const newSnapshot = button(sidebarActions, '➕ ' + t('recipeParameterNew'), 'anomalous-preset-btn-secondary');
|
||||
const newSnapshot = button(sidebarActions, '', 'anomalous-preset-btn-secondary');
|
||||
newSnapshot.innerHTML = `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>${t('recipeParameterNew')}`;
|
||||
newSnapshot.onclick = () => {
|
||||
const draft = cloneJson({
|
||||
workflow: baseSource.workflow,
|
||||
@@ -2532,7 +2540,8 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
);
|
||||
};
|
||||
|
||||
const rename = button(actions, '✏️', 'anomalous-preset-item-btn');
|
||||
const rename = button(actions, '', 'anomalous-preset-item-btn');
|
||||
rename.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>';
|
||||
rename.title = t('recipeParameterRename');
|
||||
rename.onclick = async (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -2559,7 +2568,8 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
}
|
||||
};
|
||||
|
||||
const remove = button(actions, '🗑️', 'anomalous-preset-item-btn is-delete');
|
||||
const remove = button(actions, '', 'anomalous-preset-item-btn is-delete');
|
||||
remove.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
||||
remove.title = t('recipeParameterDelete');
|
||||
remove.onclick = async (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -2625,7 +2635,8 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
const consoleActions = document.createElement('div');
|
||||
consoleActions.className = 'anomalous-preset-console-actions';
|
||||
|
||||
const applyButton = button(consoleActions, '🚀 ' + t('recipeParameterApply'), 'anomalous-preset-btn-primary');
|
||||
const applyButton = button(consoleActions, '', 'anomalous-preset-btn-primary');
|
||||
applyButton.innerHTML = `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>${t('recipeParameterApply')}`;
|
||||
applyButton.style.padding = '8px 16px';
|
||||
applyButton.style.fontSize = '0.88rem';
|
||||
const applyStatus = appendText(consoleActions, 'small', '', 'anomalous-recipe-header-status');
|
||||
@@ -2665,7 +2676,8 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
);
|
||||
|
||||
if (parameterState?.selectedFilename) {
|
||||
const renameHeadingBtn = button(consoleActions, '✏️ ' + t('recipeParameterRename'), 'anomalous-btn-ghost');
|
||||
const renameHeadingBtn = button(consoleActions, '', 'anomalous-btn-ghost');
|
||||
renameHeadingBtn.innerHTML = `<svg style="width:13px;height:13px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>${t('recipeParameterRename')}`;
|
||||
renameHeadingBtn.onclick = async () => {
|
||||
const newName = await anomalousPrompt(t('recipeParameterRenamePrompt'), currentName);
|
||||
if (newName === null || !newName.trim() || newName.trim() === currentName) return;
|
||||
@@ -2698,7 +2710,8 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
const thumb = document.createElement('img');
|
||||
thumb.src = outputImageUrl(gallery.images[0]);
|
||||
compactGallery.appendChild(thumb);
|
||||
appendText(compactGallery, 'span', `🖼️ ${gallery.images.length}`, 'anomalous-preset-compact-gallery-badge');
|
||||
const galleryBadge = appendText(compactGallery, 'span', '', 'anomalous-preset-compact-gallery-badge');
|
||||
galleryBadge.innerHTML = `<svg style="width:11px;height:11px;margin-right:3px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>${gallery.images.length}`;
|
||||
compactGallery.onclick = () => {
|
||||
const dialog = document.createElement('dialog');
|
||||
dialog.className = 'anomalous-recipe-gallery-dialog';
|
||||
@@ -2724,7 +2737,8 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
card.appendChild(image);
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'anomalous-recipe-gallery-card-actions';
|
||||
const details = button(actions, `🔎 ${t('materialViewDetails')}`, 'anomalous-btn-primary');
|
||||
const details = button(actions, '', 'anomalous-btn-primary');
|
||||
details.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>${t('materialViewDetails')}`;
|
||||
details.onclick = event => {
|
||||
event.stopPropagation();
|
||||
const images = gallery.images;
|
||||
@@ -2761,13 +2775,13 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
bentoGrid.className = 'anomalous-recipe-bento-grid';
|
||||
|
||||
const bentoItems = [
|
||||
{ label: t('recipeDetailSteps') || '步数', val: params.steps, icon: '⚡' },
|
||||
{ label: t('recipeDetailCFG') || 'CFG Scale', val: params.cfg, icon: '🎯' },
|
||||
{ label: t('recipeDetailSampler') || '采样器', val: params.sampler_name || params.samplers, icon: '🎲' },
|
||||
{ label: t('recipeDetailScheduler') || '调度器', val: params.scheduler, icon: '📈' },
|
||||
{ label: t('recipeDetailResolution') || '分辨率', val: resDisplay || params.resolution, icon: '📐' },
|
||||
{ label: t('recipeDetailDenoise') || '降噪比', val: params.denoise, icon: '🌊' },
|
||||
{ label: t('recipeDetailSeed') || '随机种子', val: params.seed, icon: '🌱', isSeed: true },
|
||||
{ label: t('recipeDetailSteps') || '步数', val: params.steps, icon: '<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>' },
|
||||
{ label: t('recipeDetailCFG') || 'CFG Scale', val: params.cfg, icon: '<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/></svg>' },
|
||||
{ label: t('recipeDetailSampler') || '采样器', val: params.sampler_name || params.samplers, icon: '<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="20" rx="5"/><circle cx="8" cy="8" r="1.5"/><circle cx="16" cy="16" r="1.5"/><circle cx="12" cy="12" r="1.5"/></svg>' },
|
||||
{ label: t('recipeDetailScheduler') || '调度器', val: params.scheduler, icon: '<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>' },
|
||||
{ label: t('recipeDetailResolution') || '分辨率', val: resDisplay || params.resolution, icon: '<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>' },
|
||||
{ label: t('recipeDetailDenoise') || '降噪比', val: params.denoise, icon: '<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2.69l5.66 5.66a8 8 0 11-11.31 0z"/></svg>' },
|
||||
{ label: t('recipeDetailSeed') || '随机种子', val: params.seed, icon: '<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>', isSeed: true },
|
||||
].filter(item => item.val !== undefined && item.val !== null && item.val !== '');
|
||||
|
||||
if (bentoItems.length > 0) {
|
||||
@@ -2781,7 +2795,10 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
labelRow.style.justifyContent = 'space-between';
|
||||
labelRow.style.alignItems = 'center';
|
||||
|
||||
appendText(labelRow, 'span', `${item.icon} ${item.label}`, 'anomalous-recipe-bento-label');
|
||||
const bentoLabel = document.createElement('span');
|
||||
bentoLabel.className = 'anomalous-recipe-bento-label';
|
||||
bentoLabel.innerHTML = `${item.icon}${item.label}`;
|
||||
labelRow.appendChild(bentoLabel);
|
||||
|
||||
if (item.isSeed) {
|
||||
const copySeedBtn = document.createElement('button');
|
||||
@@ -2818,14 +2835,15 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery,
|
||||
if (baseModelVal) {
|
||||
const modelCard = document.createElement('div');
|
||||
modelCard.className = 'anomalous-recipe-model-highlight-card';
|
||||
modelCard.innerHTML = `<div style="display:flex;align-items:center;gap:8px;"><span style="font-size:1.1rem;">🧠</span><span style="font-size:0.75rem;color:#94a3b8;text-transform:uppercase;font-weight:600;">${t('recipeDetailBaseModel') || '底模'}</span></div><div style="font-size:0.9rem;font-weight:600;color:#f8fafc;margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(String(baseModelVal))}">${escapeHtml(String(baseModelVal))}</div>`;
|
||||
modelCard.innerHTML = `<div style="display:flex;align-items:center;gap:8px;"><span style="display:inline-flex;align-items:center;color:#dc143c;"><svg style="width:16px;height:16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg></span><span style="font-size:0.75rem;color:#94a3b8;text-transform:uppercase;font-weight:600;">${t('recipeDetailBaseModel') || '底模'}</span></div><div style="font-size:0.9rem;font-weight:600;color:#f8fafc;margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(String(baseModelVal))}">${escapeHtml(String(baseModelVal))}</div>`;
|
||||
modelsDeck.appendChild(modelCard);
|
||||
}
|
||||
|
||||
if (Array.isArray(params.loras) && params.loras.length > 0) {
|
||||
const loraSection = document.createElement('div');
|
||||
loraSection.className = 'anomalous-recipe-lora-stack';
|
||||
appendText(loraSection, 'div', `⚡ ${t('recipeDetailLoraSummary') || 'LoRA 阵容'} (${params.loras.length})`, 'anomalous-recipe-bento-label');
|
||||
const loraHeader = appendText(loraSection, 'div', '', 'anomalous-recipe-bento-label');
|
||||
loraHeader.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>${t('recipeDetailLoraSummary') || 'LoRA 阵容'} (${params.loras.length})`;
|
||||
|
||||
const loraGrid = document.createElement('div');
|
||||
loraGrid.style.display = 'grid';
|
||||
|
||||
+36
-22
@@ -851,13 +851,15 @@ function buildRecipeStudioTopbar(owner) {
|
||||
viewSwitch.append(gridBtn, listBtn);
|
||||
right.appendChild(viewSwitch);
|
||||
|
||||
const saveBtn = appendText(right, 'button', `💾 ${t('recipeSaveCurrent')}`, 'anomalous-recipe-topbar-btn is-primary');
|
||||
const saveBtn = appendText(right, 'button', '', 'anomalous-recipe-topbar-btn is-primary');
|
||||
saveBtn.dataset.recipeSaveCurrent = 'true';
|
||||
saveBtn.type = 'button';
|
||||
saveBtn.innerHTML = `<svg style="width:14px;height:14px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>${t('recipeSaveCurrent')}`;
|
||||
saveBtn.onclick = () => owner.handleSaveRecipe();
|
||||
|
||||
const importBtn = appendText(right, 'button', `📥 ${t('recipeImport')}`, 'anomalous-recipe-topbar-btn');
|
||||
const importBtn = appendText(right, 'button', '', 'anomalous-recipe-topbar-btn');
|
||||
importBtn.type = 'button';
|
||||
importBtn.innerHTML = `<svg style="width:14px;height:14px;margin-right:6px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>${t('recipeImport')}`;
|
||||
importBtn.onclick = () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
@@ -974,19 +976,22 @@ function createRecipeQuickSpecs(params) {
|
||||
|
||||
const loras = Array.isArray(params.loras) ? params.loras : [];
|
||||
if (loras.length > 0) {
|
||||
const loraTag = appendText(strip, 'span', `🧩 ${loras.length} ${t('recipeCardSpecsLoras')}`, 'anomalous-recipe-spec-tag is-lora');
|
||||
const loraTag = appendText(strip, 'span', '', 'anomalous-recipe-spec-tag is-lora');
|
||||
loraTag.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>${loras.length} ${t('recipeCardSpecsLoras')}`;
|
||||
loraTag.title = loras.map((l) => (typeof l === 'object' && l?.name ? l.name : String(l))).join(', ');
|
||||
}
|
||||
|
||||
if (params.steps) {
|
||||
const samplerName = params.sampler_name ? ` · ${params.sampler_name}` : '';
|
||||
const stepTag = appendText(strip, 'span', `⏱️ ${params.steps} ${t('recipeCardSpecsSteps')}${samplerName}`, 'anomalous-recipe-spec-tag is-step');
|
||||
const stepTag = appendText(strip, 'span', '', 'anomalous-recipe-spec-tag is-step');
|
||||
stepTag.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>${params.steps} ${t('recipeCardSpecsSteps')}${samplerName}`;
|
||||
stepTag.title = `${params.steps} ${t('recipeCardSpecsSteps')}${samplerName}`;
|
||||
}
|
||||
|
||||
const formattedRes = formatRecipeResolution(params.resolution);
|
||||
if (formattedRes) {
|
||||
const resTag = appendText(strip, 'span', `📐 ${formattedRes}`, 'anomalous-recipe-spec-tag');
|
||||
const resTag = appendText(strip, 'span', '', 'anomalous-recipe-spec-tag');
|
||||
resTag.innerHTML = `<svg style="width:12px;height:12px;margin-right:4px;vertical-align:-1px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>${formattedRes}`;
|
||||
resTag.title = `${t('recipeCardSpecsResolution')}: ${formattedRes}`;
|
||||
}
|
||||
|
||||
@@ -1044,19 +1049,23 @@ function createRecipeCard(owner, recipe) {
|
||||
|
||||
const baseModelStr = data.params?.baseModel || data.params?.baseModels;
|
||||
if (baseModelStr) {
|
||||
const pill = appendText(bottomChips, 'span', `📦 ${modelDisplayName(baseModelStr)}`, 'anomalous-recipe-cover-pill');
|
||||
const pill = document.createElement('span');
|
||||
pill.className = 'anomalous-recipe-cover-pill';
|
||||
pill.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:-1px;margin-right:4px;"><path d="m21 16-9 5-9-5V8l9-5 9 5v8z"/><path d="m3.27 6.96 8.73 4.84 8.73-4.84"/><path d="M12 22.08V11.8"/></svg><span>${escapeHtml(modelDisplayName(baseModelStr))}</span>`;
|
||||
pill.title = String(baseModelStr);
|
||||
bottomChips.appendChild(pill);
|
||||
}
|
||||
mediaWrap.appendChild(bottomChips);
|
||||
|
||||
// Scope Pill (Top-right of cover)
|
||||
const isPartial = data.workflow_scope === 'partial';
|
||||
appendText(
|
||||
mediaWrap,
|
||||
'span',
|
||||
isPartial ? '🧩 ' + t('recipeScopePill_partial') : '⚡ ' + t('recipeScopePill_complete'),
|
||||
`anomalous-recipe-scope-pill ${isPartial ? 'is-partial' : 'is-complete'}`,
|
||||
);
|
||||
const scopePill = document.createElement('span');
|
||||
scopePill.className = `anomalous-recipe-scope-pill ${isPartial ? 'is-partial' : 'is-complete'}`;
|
||||
const scopeSvg = isPartial
|
||||
? `<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:-1px;margin-right:3px;"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9v6"/><path d="M9 6h6"/></svg>`
|
||||
: `<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:-1px;margin-right:3px;"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>`;
|
||||
scopePill.innerHTML = `${scopeSvg}<span>${t(isPartial ? 'recipeScopePill_partial' : 'recipeScopePill_complete')}</span>`;
|
||||
mediaWrap.appendChild(scopePill);
|
||||
card.appendChild(mediaWrap);
|
||||
|
||||
// 2. Card Body
|
||||
@@ -1093,17 +1102,15 @@ function createRecipeCard(owner, recipe) {
|
||||
body.appendChild(tags);
|
||||
}
|
||||
|
||||
// Footer: Primary Action Button ("🚀 载入画布" / "🧩 追加画布") + Mini Actions
|
||||
// Footer: Primary Action Button + Mini Actions
|
||||
const footer = document.createElement('div');
|
||||
footer.className = 'anomalous-recipe-card-footer';
|
||||
|
||||
const appendBtn = appendText(
|
||||
footer,
|
||||
'button',
|
||||
`${isPartial ? '🧩' : '🚀'} ${t(isPartial ? 'recipeAppendCanvas' : 'recipeOpenCanvas')}`,
|
||||
'anomalous-recipe-btn-primary-action anomalous-tooltip-target',
|
||||
);
|
||||
const appendBtn = document.createElement('button');
|
||||
appendBtn.type = 'button';
|
||||
appendBtn.className = 'anomalous-recipe-btn-primary-action anomalous-tooltip-target';
|
||||
const actionSvg = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="display:inline-block;vertical-align:-1px;margin-right:4px;"><polygon points="5 3 19 12 5 21 5 3"/></svg>`;
|
||||
appendBtn.innerHTML = `${actionSvg}<span>${t(isPartial ? 'recipeAppendCanvas' : 'recipeOpenCanvas')}</span>`;
|
||||
appendBtn.removeAttribute('title');
|
||||
appendBtn.setAttribute('data-tooltip', isPartial ? t('recipeAppendCanvas') : t('recipeOpenCanvas'));
|
||||
appendBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
@@ -1115,12 +1122,15 @@ function createRecipeCard(owner, recipe) {
|
||||
await applyRecipeToCanvas(owner, fullRecipe);
|
||||
}, isPartial ? 'recipeAppendError' : 'recipeOpenError');
|
||||
};
|
||||
footer.appendChild(appendBtn);
|
||||
|
||||
const miniActions = document.createElement('div');
|
||||
miniActions.className = 'anomalous-recipe-card-mini-actions';
|
||||
|
||||
const editBtn = appendText(miniActions, 'button', '✏️', 'anomalous-recipe-card-mini-btn anomalous-tooltip-target');
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.type = 'button';
|
||||
editBtn.className = 'anomalous-recipe-card-mini-btn anomalous-tooltip-target';
|
||||
editBtn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>`;
|
||||
editBtn.removeAttribute('title');
|
||||
editBtn.setAttribute('data-tooltip', t('recipeEdit'));
|
||||
editBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
@@ -1129,8 +1139,10 @@ function createRecipeCard(owner, recipe) {
|
||||
runRecipeCardAction(editBtn, () => editRecipe(owner, recipe?.data || {}, recipe.filename), 'recipeUpdateError');
|
||||
};
|
||||
|
||||
const exportBtn = appendText(miniActions, 'button', '📥', 'anomalous-recipe-card-mini-btn anomalous-tooltip-target');
|
||||
const exportBtn = document.createElement('button');
|
||||
exportBtn.type = 'button';
|
||||
exportBtn.className = 'anomalous-recipe-card-mini-btn anomalous-tooltip-target';
|
||||
exportBtn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
|
||||
exportBtn.removeAttribute('title');
|
||||
exportBtn.setAttribute('data-tooltip', t('recipeExport'));
|
||||
exportBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
@@ -1139,8 +1151,10 @@ function createRecipeCard(owner, recipe) {
|
||||
runRecipeCardAction(exportBtn, () => exportRecipePackage(recipe.filename), 'recipeExportError');
|
||||
};
|
||||
|
||||
const removeBtn = appendText(miniActions, 'button', '🗑️', 'anomalous-recipe-card-mini-btn is-delete anomalous-tooltip-target');
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.type = 'button';
|
||||
removeBtn.className = 'anomalous-recipe-card-mini-btn is-delete anomalous-tooltip-target';
|
||||
removeBtn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>`;
|
||||
removeBtn.removeAttribute('title');
|
||||
removeBtn.setAttribute('data-tooltip', t('recipeDelete'));
|
||||
removeBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
|
||||
+38
-29
@@ -13,13 +13,13 @@ const t = (key, params) => translate(key, params);
|
||||
const SCAN_RADAR_ICON_SVG = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><circle cx="12" cy="12" r="9"/><path d="M12 12 18.5 5.5"/><circle cx="12" cy="12" r="2"/><path d="M12 7a5 5 0 0 1 5 5"/></svg>`;
|
||||
|
||||
const SIDEBAR_ICONS = {
|
||||
MODELS: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="anomalous-btn-icon"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>`,
|
||||
MODELS: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="anomalous-btn-icon"><path d="m21 16-9 5-9-5V8l9-5 9 5v8z"/><path d="m3.27 6.96 8.73 4.84 8.73-4.84"/><path d="M12 22.08V11.8"/></svg>`,
|
||||
GALLERY: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="anomalous-btn-icon"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`,
|
||||
RECIPES: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="anomalous-btn-icon"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>`,
|
||||
RECIPES: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="anomalous-btn-icon"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9v6"/><path d="M9 6h6"/><path d="M7.8 7.8l8.4 8.4"/></svg>`,
|
||||
DOCK: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/></svg>`,
|
||||
HELP: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="anomalous-btn-icon"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>`,
|
||||
DOCTOR: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>`,
|
||||
ASSISTANT: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><circle cx="12" cy="5" r="3"/><circle cx="5" cy="19" r="3"/><circle cx="19" cy="19" r="3"/><line x1="12" y1="8" x2="5" y2="16"/><line x1="12" y1="8" x2="19" y2="16"/><line x1="8" y1="19" x2="16" y2="19"/></svg>`,
|
||||
ASSISTANT: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><path d="m15 4 5 5"/><path d="m18 7-8.5 8.5a2.12 2.12 0 1 1-3-3L15 4Z"/><path d="m2 22 5-5"/><path d="M19 13v2"/><path d="M21 15h-2"/><path d="M7 2v2"/><path d="M9 4H7"/></svg>`,
|
||||
SETTINGS: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
|
||||
FOLDER: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline-block;vertical-align:-3px;margin-right:7px;"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"/></svg>`,
|
||||
CHEVRON_UP: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline-block;vertical-align:-2px;margin-right:4px;"><polyline points="18 15 12 9 6 15"/></svg>`,
|
||||
@@ -218,14 +218,14 @@ export function createDOM() {
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const spacer = document.createElement('div');
|
||||
spacer.style.flex = '1 1 auto';
|
||||
|
||||
const leftGroup = document.createElement('div');
|
||||
leftGroup.className = 'anomalous-header-group';
|
||||
leftGroup.className = 'anomalous-header-group anomalous-header-left';
|
||||
|
||||
const centerGroup = document.createElement('div');
|
||||
centerGroup.className = 'anomalous-header-group anomalous-header-center';
|
||||
|
||||
const rightGroup = document.createElement('div');
|
||||
rightGroup.className = 'anomalous-header-group';
|
||||
rightGroup.className = 'anomalous-header-group anomalous-header-right';
|
||||
|
||||
// We will define hideAllPanels as a class method instead of a local closure to make it globally accessible.
|
||||
|
||||
@@ -341,9 +341,11 @@ export function createDOM() {
|
||||
this.showRecipes();
|
||||
};
|
||||
|
||||
rightGroup.appendChild(modelsBtn);
|
||||
rightGroup.appendChild(galleryBtn);
|
||||
rightGroup.appendChild(nbBtn);
|
||||
centerGroup.appendChild(modelsBtn);
|
||||
centerGroup.appendChild(galleryBtn);
|
||||
centerGroup.appendChild(nbBtn);
|
||||
leftGroup.appendChild(dockBtn);
|
||||
leftGroup.appendChild(helpBtn);
|
||||
|
||||
const apiKeyBtn = document.createElement('button');
|
||||
apiKeyBtn.id = 'anomalous-api-btn';
|
||||
@@ -367,7 +369,7 @@ export function createDOM() {
|
||||
scanBtn.removeAttribute('title');
|
||||
scanBtn.setAttribute('aria-label', t('sidebarScanWizard'));
|
||||
scanBtn.setAttribute('data-tooltip', t('sidebarScanWizard'));
|
||||
scanBtn.setAttribute('data-tooltip-pos', 'right');
|
||||
scanBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
setScanButtonState(scanBtn, false);
|
||||
scanBtn.style.background = 'transparent';
|
||||
scanBtn.style.border = 'none';
|
||||
@@ -728,7 +730,11 @@ export function createDOM() {
|
||||
left.style.gap = '16px';
|
||||
|
||||
const iconEl = document.createElement('div');
|
||||
iconEl.innerText = icon;
|
||||
if (typeof icon === 'string' && icon.startsWith('<')) {
|
||||
iconEl.innerHTML = icon;
|
||||
} else {
|
||||
iconEl.innerText = icon;
|
||||
}
|
||||
iconEl.style.fontSize = '1.4em';
|
||||
iconEl.style.lineHeight = '1.2';
|
||||
iconEl.style.width = '24px';
|
||||
@@ -819,7 +825,7 @@ export function createDOM() {
|
||||
pRow.style.display = 'flex';
|
||||
pRow.style.alignItems = 'center';
|
||||
pRow.style.gap = '8px';
|
||||
pRow.innerHTML = `<span style="font-size:0.9em; color:#ddd;">💾 ${t('sidebarPhysicalRename')}</span>`;
|
||||
pRow.innerHTML = `<span style="font-size:0.9em; color:#ddd; display:inline-flex; align-items:center; gap:6px;"><svg style="width:14px;height:14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>${t('sidebarPhysicalRename')}</span>`;
|
||||
pRow.appendChild(physicalSwitch);
|
||||
|
||||
const pDesc = document.createElement('div');
|
||||
@@ -846,7 +852,8 @@ export function createDOM() {
|
||||
dualChannelRow.appendChild(vContainer);
|
||||
dualChannelRow.appendChild(pContainer);
|
||||
|
||||
s2List.appendChild(createListRow('📝', 'sidebarNormalize', 'sidebarNormalizeDesc', createMaterialSwitch(enableRename, (s) => { enableRename = s; updateDualChannelUI(); })));
|
||||
const editSvg = '<svg style="width:20px;height:20px;vertical-align:middle;stroke:#dc143c;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>';
|
||||
s2List.appendChild(createListRow(editSvg, 'sidebarNormalize', 'sidebarNormalizeDesc', createMaterialSwitch(enableRename, (s) => { enableRename = s; updateDualChannelUI(); })));
|
||||
s2List.lastChild.style.borderBottom = 'none';
|
||||
s2List.appendChild(dualChannelRow);
|
||||
updateDualChannelUI();
|
||||
@@ -865,9 +872,12 @@ export function createDOM() {
|
||||
|
||||
const s3List = document.createElement('div');
|
||||
const isInject = localStorage.getItem('anomalous_inject_hash') !== 'false';
|
||||
s3List.appendChild(createListRow('📦', 'sidebarProvenance', 'sidebarProvenanceDesc', createMaterialSwitch(isInject, (s) => localStorage.setItem('anomalous_inject_hash', s ? 'true' : 'false'))));
|
||||
s3List.appendChild(createListRow('⚠️', 'sidebarOverwrite', 'sidebarOverwriteDesc', createMaterialSwitch(enableForceOverwrite, (s) => enableForceOverwrite = s)));
|
||||
s3List.appendChild(createListRow('🪄', 'sidebarSmartFix', 'sidebarSmartFixDesc', createMaterialSwitch(enableAutoCheck, (s) => enableAutoCheck = s)));
|
||||
const cubeSvg = '<svg style="width:20px;height:20px;vertical-align:middle;stroke:#dc143c;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>';
|
||||
const warnSvg = '<svg style="width:20px;height:20px;vertical-align:middle;stroke:#f59e0b;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
|
||||
const wandSvg = '<svg style="width:20px;height:20px;vertical-align:middle;stroke:#8b5cf6;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>';
|
||||
s3List.appendChild(createListRow(cubeSvg, 'sidebarProvenance', 'sidebarProvenanceDesc', createMaterialSwitch(isInject, (s) => localStorage.setItem('anomalous_inject_hash', s ? 'true' : 'false'))));
|
||||
s3List.appendChild(createListRow(warnSvg, 'sidebarOverwrite', 'sidebarOverwriteDesc', createMaterialSwitch(enableForceOverwrite, (s) => enableForceOverwrite = s)));
|
||||
s3List.appendChild(createListRow(wandSvg, 'sidebarSmartFix', 'sidebarSmartFixDesc', createMaterialSwitch(enableAutoCheck, (s) => enableAutoCheck = s)));
|
||||
s3List.lastChild.style.borderBottom = 'none';
|
||||
|
||||
section3.appendChild(s3List);
|
||||
@@ -1125,11 +1135,10 @@ export function createDOM() {
|
||||
|
||||
|
||||
|
||||
rightGroup.appendChild(dockBtn);
|
||||
rightGroup.appendChild(closeBtn);
|
||||
|
||||
header.appendChild(leftGroup);
|
||||
header.appendChild(spacer);
|
||||
header.appendChild(centerGroup);
|
||||
header.appendChild(rightGroup);
|
||||
|
||||
const settingsHubModal = document.createElement('div');
|
||||
@@ -1159,7 +1168,7 @@ export function createDOM() {
|
||||
scanBtn.removeAttribute('title');
|
||||
scanBtn.setAttribute('aria-label', t('sidebarScanWizard'));
|
||||
scanBtn.setAttribute('data-tooltip', t('sidebarScanWizard'));
|
||||
scanBtn.setAttribute('data-tooltip-pos', 'right');
|
||||
scanBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
setScanButtonState(scanBtn, isCurrentlyScanning);
|
||||
helpBtn.innerHTML = `${SIDEBAR_ICONS.HELP}<span class="anomalous-btn-text">${t('help')}</span>`;
|
||||
nbBtn.removeAttribute('title');
|
||||
@@ -1168,13 +1177,13 @@ export function createDOM() {
|
||||
nbBtn.innerHTML = `${SIDEBAR_ICONS.RECIPES}<span class="anomalous-btn-text">${t('recipeTitle')}</span>`;
|
||||
|
||||
const dBtn = document.getElementById('anomalous-doctor-btn');
|
||||
if (dBtn) { dBtn.removeAttribute('title'); dBtn.setAttribute('data-tooltip', t('sidebarDoctor')); dBtn.setAttribute('data-tooltip-pos', 'right'); }
|
||||
if (dBtn) { dBtn.removeAttribute('title'); dBtn.setAttribute('data-tooltip', t('sidebarDoctor')); dBtn.setAttribute('data-tooltip-pos', 'top'); }
|
||||
const aBtn = document.getElementById('anomalous-assistant-btn');
|
||||
if (aBtn) { aBtn.removeAttribute('title'); aBtn.setAttribute('data-tooltip', t('sidebarAssistant')); aBtn.setAttribute('data-tooltip-pos', 'right'); }
|
||||
if (aBtn) { aBtn.removeAttribute('title'); aBtn.setAttribute('data-tooltip', t('sidebarAssistant')); aBtn.setAttribute('data-tooltip-pos', 'top'); }
|
||||
const iBtn = document.getElementById('anomalous-materials-btn');
|
||||
if (iBtn) { iBtn.removeAttribute('title'); iBtn.setAttribute('aria-label', t('materialLibrary')); iBtn.setAttribute('data-tooltip', t('materialLibrary')); iBtn.setAttribute('data-tooltip-pos', 'right'); }
|
||||
if (iBtn) { iBtn.removeAttribute('title'); iBtn.setAttribute('aria-label', t('materialLibrary')); iBtn.setAttribute('data-tooltip', t('materialLibrary')); iBtn.setAttribute('data-tooltip-pos', 'top'); }
|
||||
const sBtn = document.getElementById('anomalous-global-settings-btn');
|
||||
if (sBtn) { sBtn.removeAttribute('title'); sBtn.setAttribute('data-tooltip', t('sidebarSettings')); sBtn.setAttribute('data-tooltip-pos', 'right'); }
|
||||
if (sBtn) { sBtn.removeAttribute('title'); sBtn.setAttribute('data-tooltip', t('sidebarSettings')); sBtn.setAttribute('data-tooltip-pos', 'top'); }
|
||||
|
||||
// Reset dynamic panels so they re-render in new language
|
||||
if (window.anomalousBrowserInstance) {
|
||||
@@ -1647,7 +1656,7 @@ export function createDOM() {
|
||||
settingsBtn.removeAttribute('title');
|
||||
settingsBtn.setAttribute('aria-label', t('sidebarSettings'));
|
||||
settingsBtn.setAttribute('data-tooltip', t('sidebarSettings'));
|
||||
settingsBtn.setAttribute('data-tooltip-pos', 'right');
|
||||
settingsBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
settingsBtn.style.background = 'transparent';
|
||||
settingsBtn.style.border = 'none';
|
||||
settingsBtn.style.borderRadius = '6px';
|
||||
@@ -1679,7 +1688,7 @@ export function createDOM() {
|
||||
importBtn.removeAttribute('title');
|
||||
importBtn.setAttribute('aria-label', t('materialLibrary'));
|
||||
importBtn.setAttribute('data-tooltip', t('materialLibrary'));
|
||||
importBtn.setAttribute('data-tooltip-pos', 'right');
|
||||
importBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
importBtn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></svg>`;
|
||||
importBtn.style.background = 'transparent';
|
||||
importBtn.style.border = 'none';
|
||||
@@ -1697,7 +1706,7 @@ export function createDOM() {
|
||||
doctorBtn.removeAttribute('title');
|
||||
doctorBtn.setAttribute('aria-label', t('sidebarDoctor'));
|
||||
doctorBtn.setAttribute('data-tooltip', t('sidebarDoctor'));
|
||||
doctorBtn.setAttribute('data-tooltip-pos', 'right');
|
||||
doctorBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
doctorBtn.innerHTML = SIDEBAR_ICONS.DOCTOR;
|
||||
doctorBtn.style.background = 'transparent';
|
||||
doctorBtn.style.border = 'none';
|
||||
@@ -1733,7 +1742,7 @@ export function createDOM() {
|
||||
assistantBtn.removeAttribute('title');
|
||||
assistantBtn.setAttribute('aria-label', t('sidebarAssistant'));
|
||||
assistantBtn.setAttribute('data-tooltip', t('sidebarAssistant'));
|
||||
assistantBtn.setAttribute('data-tooltip-pos', 'right');
|
||||
assistantBtn.setAttribute('data-tooltip-pos', 'top');
|
||||
assistantBtn.innerHTML = SIDEBAR_ICONS.ASSISTANT;
|
||||
assistantBtn.style.background = 'transparent';
|
||||
assistantBtn.style.border = 'none';
|
||||
|
||||
+383
-277
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user