@dickpy/dsh-imagegen 1.5.2 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -298,6 +298,9 @@ export const zh = {
298
298
  'settings.overridden': '已覆盖',
299
299
  'settings.reset': '重置',
300
300
  'settings.invalidNumber': '请输入有效数字',
301
+
302
+
303
+
301
304
  // template library
302
305
  'templates.open': '模板库',
303
306
  'templates.title': '提示词模板库',
@@ -675,6 +678,9 @@ export const en: Record<keyof typeof zh, string> = {
675
678
  'settings.overridden': 'Overridden',
676
679
  'settings.reset': 'Reset',
677
680
  'settings.invalidNumber': 'Enter a valid number',
681
+
682
+
683
+
678
684
  // template library
679
685
  'templates.open': 'Templates',
680
686
  'templates.title': 'Prompt Template Library',
@@ -712,8 +718,7 @@ export const en: Record<keyof typeof zh, string> = {
712
718
  'templates.source': 'Source: {label}',
713
719
  'templates.featured': 'Featured',
714
720
  // inspiration wall (studio empty state)
715
- 'inspiration.title': 'Inspiration',
716
- 'inspiration.shuffle': 'Shuffle',
721
+ 'inspiration.title': 'Inspiration', 'inspiration.shuffle': 'Shuffle',
717
722
  'inspiration.shuffling': 'Shuffling…',
718
723
  'inspiration.useHint': 'Click to use this prompt',
719
724
  // channel management
@@ -770,3 +775,381 @@ export const en: Record<keyof typeof zh, string> = {
770
775
 
771
776
  /** Locale key union. */
772
777
  export type ImageGenKey = keyof typeof zh
778
+
779
+ /** Russian mirror of every key (полный словарь интерфейса). */
780
+ export const ru: Record<keyof typeof zh, string> = {
781
+ 'entry.label': 'ИИ-генерация',
782
+ 'entry.tooltip': 'Панель ИИ-генерации изображений (gpt-image-2 / glm-image / grok-imagine-image / nanobanana / seedream)',
783
+ 'entry.newSession': 'Новая сессия',
784
+ 'entry.newSessionTooltip': 'Создать новую сессию DSH',
785
+ 'entry.image': 'Генерация',
786
+ 'panel.title': 'ИИ-генерация изображений',
787
+ 'panel.githubTip': 'Понравился плагин или есть идеи? Поставьте звезду или откройте issue на GitHub!',
788
+ 'conversation.add': 'В диалог',
789
+ 'conversation.adding': 'Добавляем…',
790
+ 'conversation.added': 'Изображение добавлено в текущий диалог',
791
+ 'conversation.addHint': 'После добавления можно править через /edit_image; команда использует модель плагина',
792
+ 'conversation.noSession': 'Сначала откройте сессию, затем добавьте изображение',
793
+ 'conversation.unavailable': 'Текущая сессия недоступна',
794
+ 'conversation.busy': 'Диалог отправляет сообщение, попробуйте позже',
795
+ 'mode.text': 'Из текста',
796
+ 'mode.edit': 'Из изображения',
797
+ 'workspace.label': 'Режим работы',
798
+ 'workspace.normal': 'Генерация',
799
+ 'workspace.ecommerce': 'E-commerce',
800
+ 'chat.expand': 'Показать диалог',
801
+ 'chat.collapse': 'Скрыть диалог',
802
+ 'chat.toggle': 'Диалог',
803
+ 'config.resizeHint': 'Потяните, чтобы изменить ширину панели параметров',
804
+ 'ecommerce.generation': 'Параметры генерации',
805
+ 'ecommerce.title': 'Режим e-commerce',
806
+ 'ecommerce.badge': 'Превью',
807
+ 'ecommerce.short': 'E-com',
808
+ 'ecommerce.anchorPending': 'Главное изображение готово, остальные генерируются по нему для единого стиля…',
809
+ 'ecommerce.anchorFailed': 'Не удалось создать главное изображение, остальные приостановлены. Повторите главное (или всю группу) и продолжите.',
810
+ 'ecommerce.anchorNote': 'Якорная генерация: сначала главное изображение, остальные создаются по нему для единого стиля',
811
+ 'ecommerce.noAssetWarn': 'Товар не загружен: главное изображение будет создано по описанию и станет якорем; загрузите фото товара для большей точности',
812
+ 'ecommerce.product': 'Товар',
813
+ 'ecommerce.productName': 'Название товара (обязательно)',
814
+ 'ecommerce.projectName': 'Название проекта (необязательно)',
815
+ 'ecommerce.constraints': 'Преимущества и ограничения',
816
+ 'ecommerce.sellingPoints': 'Преимущества товара, например: водостойкость / большая вместимость / компактность',
817
+ 'ecommerce.protectedFeatures': 'Что обязательно сохранить: цвет, логотип, текст упаковки, конструкцию',
818
+ 'ecommerce.styleHint': 'Стиль, например: премиальный / реалистичный / естественный свет',
819
+ 'ecommerce.setStructure': 'Структура комплекта',
820
+ 'ecommerce.preview': 'Создать превью комплекта',
821
+ 'ecommerce.planTitle': 'Будет сгенерировано изображений: {count}',
822
+ 'ecommerce.planSlot': '{count} шт. · {description}',
823
+ 'ecommerce.confirm': 'Сгенерировать весь комплект',
824
+ 'ecommerce.uploadRef': 'Материалы товара (необязательно, до 4 шт.: товар / упаковка / детали / стиль)',
825
+ 'ecommerce.uploadShort': 'Загрузить материалы',
826
+ 'ecommerce.sellingTitle': 'Преимущества товара',
827
+ 'ecommerce.advanced': 'Дополнительно (проект / площадка / ограничения)',
828
+ 'ecommerce.params': 'Параметры',
829
+ 'ecommerce.platformLabel': 'Площадка',
830
+ 'ecommerce.languageLabel': 'Язык текста',
831
+ 'ecommerce.ratioLabel': 'Пропорции',
832
+ 'ecommerce.categoryLabel': 'Категория',
833
+ 'ecommerce.multiSelect': 'Можно выбрать несколько',
834
+ 'ecommerce.countHint': 'Нажмите, чтобы изменить количество (1-4)',
835
+ 'ecommerce.refSettings': 'Настройки референсов (необязательно)',
836
+ 'ecommerce.styleTitle': 'Стиль / дополнения',
837
+ 'ecommerce.protectedLabel': 'Обязательно сохранить (необязательно)',
838
+ 'ecommerce.assetsFull': 'Не более 4 материалов',
839
+ 'ecommerce.refSelect': 'Референс для этого назначения',
840
+ 'ecommerce.refNone': 'Без референса',
841
+ 'ecommerce.role.product': 'Товар',
842
+ 'ecommerce.role.packaging': 'Упаковка',
843
+ 'ecommerce.role.detail': 'Детали/ракурс',
844
+ 'ecommerce.role.style': 'Стиль',
845
+ 'ecommerce.footerReady': 'Будет сгенерировано {count} изображений',
846
+ 'ecommerce.footerEmpty': 'Выберите структуру комплекта',
847
+ 'ecommerce.results.title': 'Результаты комплекта',
848
+ 'ecommerce.results.progress': '{done}/{total} готово',
849
+ 'ecommerce.results.failed': '{count} с ошибкой',
850
+ 'ecommerce.results.empty': 'После запуска генерации здесь появятся статус и результат каждого изображения',
851
+ 'ecommerce.results.regenerate': 'Сгенерировать заново',
852
+ 'ecommerce.results.export': 'Экспорт списка',
853
+ 'ecommerce.results.newProduct': 'Новый товар',
854
+ 'prompt.placeholder': 'Опишите желаемое изображение, например: рыжий кот в шлеме космонавта смотрит в телескоп на Луне, акварель, мягкий свет…',
855
+ 'prompt.required': 'Введите промпт',
856
+ 'prompt.count': '{count}',
857
+ 'prompt.enhance': 'Улучш.',
858
+ 'prompt.enhancing': 'Улучшаем…',
859
+ 'prompt.enhanceHint': 'Развернуть краткий промпт через настроенную диалоговую модель',
860
+ 'prompt.configTitle': 'Сначала настройте модель улучшения промптов',
861
+ 'prompt.configHint': 'Настройки открыты. Перейдите в «Плагины → ИИ-генерация», укажите или переиспользуйте API-адрес и ключ, выберите диалоговую модель и сохраните.',
862
+ 'tasks.title': 'Задачи генерации',
863
+ 'tasks.queued': 'В очереди',
864
+ 'tasks.running': 'Генерация',
865
+ 'tasks.completed': 'Готово',
866
+ 'tasks.failed': 'Ошибка',
867
+ 'tasks.cancelled': 'Отменено',
868
+ 'tasks.cancel': 'Отменить',
869
+ 'tasks.retry': 'Повторить',
870
+ 'params.size': 'Размер',
871
+ 'params.quality': 'Чёткость',
872
+ 'params.count': 'Количество',
873
+ 'params.detail': 'Детализация',
874
+ 'size.auto': 'Авто',
875
+ 'size.square': '1:1 квадрат',
876
+ 'size.portrait34': '3:4 верт.',
877
+ 'size.landscape43': '4:3 гориз.',
878
+ 'size.portrait916': '9:16 верт.',
879
+ 'size.portrait23': '2:3 верт.',
880
+ 'size.landscape32': '3:2 гориз.',
881
+ 'size.wide169': '16:9 шир.',
882
+ 'size.ultrawide21': '21:9 ультрашир.',
883
+ 'quality.auto': 'Авто',
884
+ 'quality.1k': '1K',
885
+ 'quality.2k': '2K',
886
+ 'quality.4k': '4K',
887
+ 'count.one': '1 шт.',
888
+ 'count.two': '2 шт.',
889
+ 'count.three': '3 шт.',
890
+ 'count.four': '4 шт.',
891
+ 'detail.auto': 'Авто',
892
+ 'detail.standard': 'Стандарт',
893
+ 'detail.high': 'Высокая',
894
+ 'detail.hint': 'Сквозной параметр, поддерживают некоторые шлюзы gpt-image-2; для официального API оставьте «Авто»',
895
+ 'model.label': 'Модель',
896
+ 'model.noEditModels': 'В текущем режиме нет доступных моделей',
897
+ 'compare.enable': 'Сравнение моделей',
898
+ 'compare.models': 'Модели для сравнения',
899
+ 'compare.title': 'Сравнение результатов моделей',
900
+ 'compare.fullscreen': 'Полноэкранное сравнение',
901
+ 'compare.selectRequired': 'Выберите хотя бы одну модель для сравнения',
902
+ 'generate': 'Сгенерировать',
903
+ 'generating': 'Генерация…',
904
+ 'edit.upload': 'Нажмите или перетащите референс',
905
+ 'edit.uploadHint': 'PNG / JPG / WEBP, до 10 МБ',
906
+ 'edit.change': 'Заменить изображение',
907
+ 'edit.remove': 'Убрать',
908
+ 'edit.required': 'Сначала загрузите референс',
909
+ 'canvas.emptyTitle': 'Начните творить',
910
+ 'canvas.emptyHint': 'Введите промпт слева и нажмите «Сгенерировать» — результат появится здесь',
911
+ 'canvas.new': 'Новая генерация',
912
+ 'canvas.newHint': 'Начать новую генерацию и очистить просмотр',
913
+ 'canvas.error': 'Ошибка генерации: {error}',
914
+ 'canvas.submitting': 'Ставим в очередь…',
915
+ 'canvas.queued': 'Задача в очереди',
916
+ 'canvas.queueHint': 'Сейчас ожидают {count} задач',
917
+ 'canvas.generating': 'Генерируем изображение…',
918
+ 'canvas.elapsed': 'Прошло {seconds} с',
919
+ 'canvas.images': 'Сгенерировано: {count}',
920
+ 'download': 'Скачать',
921
+ 'revisedPrompt': 'Улучшенный промпт: {prompt}',
922
+ 'history.title': 'История',
923
+ 'history.empty': 'Истории пока нет — созданные изображения появятся здесь',
924
+ 'history.clear': 'Очистить',
925
+ 'history.clearConfirm': 'Очистить всю историю? Действие необратимо.',
926
+ 'history.restore': 'Восстановить',
927
+ 'history.delete': 'Удалить',
928
+ 'history.images': 'шт.',
929
+ 'history.viewing': 'История · {time}',
930
+ 'history.search': 'Поиск по промптам и моделям…',
931
+ 'history.model': 'Фильтр по модели',
932
+ 'history.ratio': 'Фильтр по пропорциям',
933
+ 'history.allModels': 'Все модели',
934
+ 'history.allRatios': 'Все пропорции',
935
+ 'gallery.title': 'Галерея',
936
+ 'gallery.categories': 'Категории',
937
+ 'gallery.all': 'Все работы',
938
+ 'gallery.gpt': 'gpt-image-2',
939
+ 'gallery.grok': 'grok-imagine-image',
940
+ 'gallery.ratio': 'Пропорции',
941
+ 'gallery.tags': 'Метки',
942
+ 'gallery.filterHint': 'Фильтр галереи по режиму, модели и пропорциям',
943
+ 'gallery.count': '· всего {count}',
944
+ 'gallery.viewMode': 'Вид',
945
+ 'gallery.masonry': 'Плитка',
946
+ 'gallery.grid': 'Сетка',
947
+ 'gallery.sort': 'Сортировка',
948
+ 'gallery.newest': 'Сначала новые',
949
+ 'gallery.oldest': 'Сначала старые',
950
+ 'gallery.untitled': 'Без названия',
951
+ 'gallery.search': 'Поиск по работам и моделям…',
952
+ 'gallery.tagsPlaceholder': 'Метки через запятую',
953
+ 'gallery.tagsApply': 'Добавить метки',
954
+ 'gallery.editTags': 'Править метки',
955
+ 'gallery.tagsEditShort': 'Правка',
956
+ 'gallery.tagsSave': 'Сохранить',
957
+ 'gallery.tagsCancel': 'Отмена',
958
+ 'gallery.selected': 'Выбрано: {count}',
959
+ 'gallery.selectionDone': 'Завершить выбор',
960
+ 'gallery.selectionClear': 'Снять выбор',
961
+ 'gallery.downloadSelected': 'Скачать выбранные',
962
+ 'gallery.exportJson': 'Экспорт JSON',
963
+ 'gallery.select': 'Выбрать работы',
964
+ 'gallery.add': 'В галерею',
965
+ 'gallery.added': 'Добавлено в галерею',
966
+ 'gallery.already': 'Уже в галерее',
967
+ 'gallery.delete': 'Убрать из галереи',
968
+ 'gallery.clear': 'Очистить галерею',
969
+ 'gallery.clearConfirm': 'Очистить всю галерею? Действие необратимо.',
970
+ 'gallery.empty': 'Галерея пуста — добавьте понравившиеся изображения',
971
+ 'gallery.viewing': 'Галерея · {time}',
972
+ 'preview.title': 'Просмотр изображения',
973
+ 'preview.open': 'Открыть просмотр',
974
+ 'preview.close': 'Закрыть',
975
+ 'preview.prev': 'Назад',
976
+ 'preview.next': 'Вперёд',
977
+ 'preview.index': '{index} / {total}',
978
+ 'preview.zoomControls': 'Управление масштабом',
979
+ 'preview.zoomIn': 'Увеличить',
980
+ 'preview.zoomOut': 'Уменьшить',
981
+ 'preview.zoomReset': 'Сбросить масштаб',
982
+ 'preview.zoomLevel': '{percent}%',
983
+ 'preview.copyPrompt': 'Копировать промпт',
984
+ 'preview.copied': 'Скопировано',
985
+ 'preview.addToEdit': 'В режим img2img',
986
+ 'config.missing': 'API не настроен: откройте «Настройки → Плагины → Настраиваемые» и заполните api_url и api_key для ИИ-генерации.',
987
+ 'config.generationTitle': 'Сначала настройте API генерации',
988
+ 'config.generationHint': 'Открыты «Настройки → Плагины → ИИ-генерация». Укажите адрес и ключ API генерации, сохраните — и можно творить.',
989
+ 'config.enhancementTitle': 'Сначала настройте модель улучшения промптов',
990
+ 'config.enhancementHint': 'Открыты «Настройки → Плагины → ИИ-генерация». Укажите или переиспользуйте адрес и ключ диалогового API, выберите модель и сохраните.',
991
+ 'config.disabledTitle': 'Плагин ИИ-генерации отключён',
992
+ 'config.disabledHint': 'Открыты «Настройки → Плагины → ИИ-генерация». Включите плагин — и генерация заработает.',
993
+ 'config.configured': 'Подключено: {url}',
994
+ 'config.disabled': 'Плагин отключён — включите его в настройках.',
995
+ 'connection.connected': 'Подключено',
996
+ 'connection.disconnected': 'Нет подключения',
997
+ 'panel.collapseConfig': 'Свернуть параметры',
998
+ 'panel.expandConfig': 'Развернуть параметры',
999
+ 'update.available': 'Доступна новая версия: {version}',
1000
+ 'update.install': 'Обновить онлайн',
1001
+ 'update.installing': 'Обновляем…',
1002
+ 'update.success': 'Обновлено до {version}, перезапустите DSH',
1003
+ 'update.failed': 'Не удалось обновить, попробуйте ещё раз',
1004
+ 'update.release': 'Открыть релиз',
1005
+ 'settings.title': 'ИИ-генерация (dsh-imagegen)',
1006
+ 'settings.description': 'Адрес и ключ API генерации изображений',
1007
+ 'settings.currentVersion': 'Текущая версия',
1008
+ 'settings.apiUrl': 'Адрес API (api_url)',
1009
+ 'settings.apiUrlHint': 'База OpenAI-совместимого API, например https://api.openai.com/v1; /images/generations и /images/edits дописываются автоматически',
1010
+ 'settings.apiKey': 'Ключ API (api_key)',
1011
+ 'settings.apiKeyHint': 'Bearer-ключ хранится открыто в локальном документе настроек; интерфейс показывает лишь факт его наличия',
1012
+ 'settings.apiKeySet': 'Ключ сохранён; введите новый, чтобы заменить, или нажмите «Очистить»',
1013
+ 'settings.apiKeyClear': 'Очистить',
1014
+ 'settings.imageModelsTitle': 'Модели генерации',
1015
+ 'settings.imageModelsHint': 'После сохранения адреса и ключа будут найдены кандидаты; выбирайте только те, что реально умеют генерировать изображения.',
1016
+ 'settings.imageModels': 'Разрешённые модели генерации',
1017
+ 'settings.imageModelsManualHint': 'По одной модели в строке; можно заполнить вручную, если API не отдаёт /models. Панель и агент используют только этот список.',
1018
+ 'settings.imageModelsFetch': 'Найти модели',
1019
+ 'settings.imageModelsLoading': 'Ищем…',
1020
+ 'settings.imageModelsCandidates': 'Найденные кандидаты (отметьте и сохраните)',
1021
+ 'settings.addModel': '+ Добавить вручную',
1022
+ 'settings.cancelAddModel': 'Скрыть добавление',
1023
+ 'settings.addModelPlaceholder': 'Имя модели, например qwen-image',
1024
+ 'settings.addModelConfirm': 'Добавить',
1025
+ 'settings.removeModel': 'Удалить модель',
1026
+ 'settings.optional': 'необязательно',
1027
+ 'settings.moreOptions': 'Дополнительно',
1028
+ 'settings.promptEnhanceTitle': 'Модель улучшения промптов',
1029
+ 'settings.promptEnhanceHint': 'Разворачивает краткое описание в полноценный промпт; пустые адрес и ключ наследуют настройки генерации.',
1030
+ 'settings.promptApiUrl': 'Адрес диалогового API (необязательно)',
1031
+ 'settings.promptApiUrlHint': 'OpenAI-совместимая база; если пусто — используется адрес API генерации.',
1032
+ 'settings.promptApiKey': 'Ключ диалогового API (необязательно)',
1033
+ 'settings.promptApiKeyHint': 'Если пусто — используется ключ API генерации.',
1034
+ 'settings.promptModel': 'Диалоговая модель',
1035
+ 'settings.promptModelHint': 'Выберите или впишите модель с поддержкой /chat/completions.',
1036
+ 'settings.promptModelDetectionHint': 'По умолчанию используется API генерации; после поиска просто выберите модель.',
1037
+ 'settings.promptModelsFetch': 'Получить модели',
1038
+ 'settings.promptModelsLoading': 'Получаем…',
1039
+ 'settings.promptModelsSelect': 'Выберите модель',
1040
+ 'settings.promptModelsCandidates': 'Найденные диалоговые модели',
1041
+ 'settings.addPromptModelPlaceholder': 'Имя диалоговой модели, например gpt-4.1-mini',
1042
+ 'settings.promptApiAdvanced': 'Отдельный диалоговый API (необязательно)',
1043
+ 'settings.announceToAgent': 'Оповещать агентов о плагине',
1044
+ 'settings.announceToAgentHint': 'Включено — возможности плагина добавляются в системный промпт каждого агента',
1045
+ 'settings.allowAgentImageGeneration': 'Разрешить агентам генерацию',
1046
+ 'settings.allowAgentImageGenerationHint': 'Включено по умолчанию. Выключено — агенты не могут создавать, смотреть или отменять задачи; боковая панель работает.',
1047
+ 'settings.enabled': 'Включить плагин',
1048
+ 'settings.enabledHint': 'Выключено — панель генерации недоступна (карточка настроек работает всегда)',
1049
+ 'settings.save': 'Сохранить',
1050
+ 'settings.saving': 'Сохраняем…',
1051
+ 'settings.discard': 'Отменить правки',
1052
+ 'settings.unsaved': 'Есть несохранённые правки',
1053
+ 'settings.saveFailed': 'Не удалось сохранить, попробуйте ещё раз',
1054
+ 'settings.readOnly': 'Документ настроек сейчас только для чтения, сохранить нельзя.',
1055
+ 'settings.notExposed': 'Пространство настроек недоступно: этот деплой не предоставляет сервис настроек плагина.',
1056
+ 'settings.expand': 'Развернуть',
1057
+ 'settings.collapse': 'Свернуть',
1058
+ 'settings.inherit': 'Наследовать',
1059
+ 'settings.on': 'Вкл',
1060
+ 'settings.off': 'Выкл',
1061
+ 'settings.overridden': 'Переопределено',
1062
+ 'settings.reset': 'Сбросить',
1063
+ 'settings.invalidNumber': 'Введите корректное число',
1064
+
1065
+
1066
+
1067
+ 'templates.open': 'Шаблоны',
1068
+ 'templates.title': 'Библиотека промпт-шаблонов',
1069
+ 'templates.sources': 'Источники шаблонов',
1070
+ 'templates.meta': 'Всего шаблонов: {count} · {origin}',
1071
+ 'templates.origin.bundled': 'встроенный снимок',
1072
+ 'templates.origin.refreshed': 'обновлено онлайн',
1073
+ 'templates.search': 'Поиск по названиям и промптам…',
1074
+ 'templates.all': 'Все',
1075
+ 'templates.close': 'Закрыть',
1076
+ 'templates.back': 'К списку',
1077
+ 'templates.use': 'Использовать промпт',
1078
+ 'templates.copy': 'Копировать промпт',
1079
+ 'templates.copied': 'Скопировано',
1080
+ 'templates.refresh': 'Обновить библиотеку',
1081
+ 'templates.refreshing': 'Обновляем…',
1082
+ 'templates.refreshed': 'Обновлено, шаблонов: {count}',
1083
+ 'templates.refreshFailed': 'Ошибка обновления: {error}',
1084
+ 'templates.favorites': 'Избранное',
1085
+ 'templates.favoritesHint': 'Показать только избранные шаблоны',
1086
+ 'templates.favoritesEmpty': 'Пока пусто — нажмите звёздочку в углу карточки, чтобы сохранить шаблон',
1087
+ 'templates.favoriteAdd': 'Добавить в избранное',
1088
+ 'templates.favoriteRemove': 'Убрать из избранного',
1089
+ 'templates.favorite': 'В избранное',
1090
+ 'templates.unfavorite': 'В избранном',
1091
+ 'templates.cacheAll': 'Кэшировать все картинки',
1092
+ 'templates.cacheAllHint': 'Через локальный прокси сохранит все референсы этой библиотеки на диск — дальше доступно офлайн',
1093
+ 'templates.caching': 'Кэшируем {done}/{total}…',
1094
+ 'templates.cached': 'Все картинки закэшированы',
1095
+ 'templates.empty': 'Нет подходящих шаблонов',
1096
+ 'templates.loading': 'Загружаем библиотеку…',
1097
+ 'templates.loadFailed': 'Не удалось загрузить библиотеку: {error}',
1098
+ 'templates.retry': 'Повторить',
1099
+ 'templates.attribution': 'Шаблоны и изображения принадлежат сайтам-источникам; ссылки на авторов — в карточках',
1100
+ 'templates.source': 'Источник: {label}',
1101
+ 'templates.featured': 'Выбор редакции',
1102
+ 'inspiration.title': 'Вдохновение',
1103
+ 'inspiration.shuffle': 'Другие',
1104
+ 'inspiration.shuffling': 'Подбираем…',
1105
+ 'inspiration.useHint': 'Нажмите, чтобы использовать промпт',
1106
+ 'channels.title': 'Каналы',
1107
+ 'channels.hint': 'Укажите адрес и ключ API каждого канала — и модели станут доступны',
1108
+ 'channels.empty': 'Каналов пока нет — добавьте кнопкой ниже',
1109
+ 'channels.addProvider': 'Добавить провайдера',
1110
+ 'channels.addCustom': 'Добавить свой канал',
1111
+ 'channels.untitled': 'Канал без названия',
1112
+ 'channels.keySet': 'Ключ задан',
1113
+ 'channels.keyMissing': 'Ключ не задан',
1114
+ 'channels.modelCount': 'Моделей: {n}',
1115
+ 'channels.noModels': 'Модели не настроены',
1116
+ 'channels.defaultLabel': 'По умолчанию',
1117
+ 'channels.statusReady': 'Готов',
1118
+ 'channels.statusIncomplete': 'Настройка не завершена',
1119
+ 'channels.edit': 'Править',
1120
+ 'channels.delete': 'Удалить',
1121
+ 'channels.deleteConfirmTitle': 'Удалить канал «{name}»? Ключ канала будет удалён; история и галерея сохранятся.',
1122
+ 'channels.confirm': 'Удалить',
1123
+ 'channels.cancel': 'Отмена',
1124
+ 'channels.editorTitle': 'Канал',
1125
+ 'channels.editorSaveNote': 'Изменения вступят в силу после «Сохранить» внизу карточки',
1126
+ 'channels.displayName': 'Название',
1127
+ 'channels.apiUrl': 'Адрес API',
1128
+ 'channels.apiKey': 'Ключ API',
1129
+ 'channels.keyReplaceHint': 'Задан — введите новый, чтобы заменить',
1130
+ 'channels.keyMissingHint': 'Введите ключ',
1131
+ 'channels.keyClear': 'Очистить ключ',
1132
+ 'channels.modelCatalogTitle': 'Каталог моделей',
1133
+ 'channels.noModelsHint': 'Моделей пока нет: нажмите «Найти» для списка кандидатов или добавьте вручную; псевдоним по умолчанию равен id модели и его можно менять.',
1134
+ 'channels.detect': 'Найти снова',
1135
+ 'channels.detecting': 'Ищем…',
1136
+ 'channels.detectSuccess': 'Связь есть ✓ · кандидатов: {n}',
1137
+ 'channels.detectFailed': 'Ошибка поиска: {error}',
1138
+ 'channels.detectOk': 'API отвечает',
1139
+ 'channels.modelAliasLabel': 'Псевдоним (отображается)',
1140
+ 'channels.modelIdLabel': 'id модели у провайдера',
1141
+ 'channels.generated': 'Сгенерировано: {n}',
1142
+ 'channels.unknownProtocol': 'Протокол неизвестен — пробуем универсальный OpenAI',
1143
+ 'channels.removeModel': 'Удалить модель',
1144
+ 'channels.manualAddPlaceholder': 'id модели, например qwen-image',
1145
+ 'channels.addModelConfirm': 'Добавить',
1146
+ 'channels.copyFrom': 'Скопировать из канала…',
1147
+ 'channels.copyApply': 'Копировать',
1148
+ 'channels.candidatesTitle': 'Кандидаты (нажмите, чтобы добавить)',
1149
+ 'channels.deleteThisChannel': 'Удалить этот канал',
1150
+ 'channels.setDefault': 'Сделать каналом по умолчанию',
1151
+ 'channels.presetPickerTitle': 'Добавить провайдера',
1152
+ 'channels.presetPickerHint': 'Выберите встроенного провайдера — останется ввести только ключ API',
1153
+ 'channels.presetCustomHint': 'Свой адрес, ключ и каталог моделей',
1154
+ 'channels.presetLoadFailed': 'Не удалось загрузить провайдеров: {error}',
1155
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * React binding for the plugin locale, which follows the DSH interface
3
+ * language (bridged from ctx.locale in client/index.ts). Surfaces that render
4
+ * tt() output subscribe to this tick so a DSH language switch re-renders them
5
+ * immediately.
6
+ */
7
+
8
+ import { useSyncExternalStore } from 'react'
9
+ import { getImageGenLanguageVersion, subscribeImageGenLanguage } from './helpers.ts'
10
+
11
+ /** Re-render the calling component whenever the DSH language changes. */
12
+ export function useImageGenLanguageTick(): number {
13
+ return useSyncExternalStore(subscribeImageGenLanguage, getImageGenLanguageVersion)
14
+ }
package/src/engine.ts CHANGED
@@ -84,6 +84,13 @@ function isZhipuImage(model: string): boolean {
84
84
  return modelFamily(model) === 'zhipu'
85
85
  }
86
86
 
87
+ /** Whether the model is Alibaba Qwen-Image, which speaks the DashScope native
88
+ * multimodal-generation contract (NOT OpenAI-compatible): a chat-style
89
+ * messages body, `宽*高` pixel sizes, and image URLs in the reply content. */
90
+ function isQwenImage(model: string): boolean {
91
+ return modelFamily(model) === 'qwen'
92
+ }
93
+
87
94
  function isGlmImage(model: string): boolean {
88
95
  return /^glm-image(?:-|$)/i.test(model.trim())
89
96
  }
@@ -101,6 +108,42 @@ function seedreamSize(quality: string): string {
101
108
  return '2K'
102
109
  }
103
110
 
111
+ /** The panel's aspect ratios mapped to Qwen-Image's `宽*高` pixel sizes.
112
+ * The classic series (qwen-image / -plus / -max) documents this fixed list;
113
+ * 2.0 / 3.0-series models accept any size within their pixel budget and
114
+ * recommend the larger set. */
115
+ const QWEN_SIZE_CLASSIC: Readonly<Record<string, string>> = {
116
+ '16:9': '1664*928',
117
+ '21:9': '1664*928',
118
+ '4:3': '1472*1104',
119
+ '3:2': '1472*1104',
120
+ '1:1': '1328*1328',
121
+ '3:4': '1104*1472',
122
+ '2:3': '1104*1472',
123
+ '9:16': '928*1664',
124
+ }
125
+
126
+ const QWEN_SIZE_HD: Readonly<Record<string, string>> = {
127
+ '16:9': '2688*1536',
128
+ '21:9': '2688*1536',
129
+ '4:3': '2368*1728',
130
+ '3:2': '2368*1728',
131
+ '1:1': '2048*2048',
132
+ '3:4': '1728*2368',
133
+ '2:3': '1728*2368',
134
+ '9:16': '1536*2688',
135
+ }
136
+
137
+ /** Versioned ids (qwen-image-2.0 / -3.0-pro / …) take the large size set. */
138
+ function isVersionedQwenImage(model: string): boolean {
139
+ return /^qwen-image-\d+\.\d/i.test(model.trim())
140
+ }
141
+
142
+ function qwenSize(model: string, ratio: string): string | undefined {
143
+ if (ratio === '' || ratio === 'auto') return undefined
144
+ return (isVersionedQwenImage(model) ? QWEN_SIZE_HD : QWEN_SIZE_CLASSIC)[ratio]
145
+ }
146
+
104
147
  /** The panel's aspect ratios mapped to the closest OpenAI pixel size
105
148
  * (gpt-image-2 / generic OpenAI-compatible endpoints). */
106
149
  const OPENAI_SIZE_BY_RATIO: Readonly<Record<string, string>> = {
@@ -472,6 +515,110 @@ async function requestOneImage(
472
515
  }))
473
516
  }
474
517
 
518
+ /**
519
+ * Qwen-Image (DashScope native multimodal-generation): one chat-style request
520
+ * carries the prompt (plus the reference image for edit mode) and answers
521
+ * synchronously with image URLs in the reply content. The versioned series
522
+ * batches natively (n ≤ 6; the panel caps at 4), the classic series is
523
+ * single-image per call.
524
+ */
525
+ async function generateQwenImage(
526
+ baseUrl: string,
527
+ upstream: UpstreamConfig,
528
+ request: GenerateRequest,
529
+ options: { signal?: AbortSignal },
530
+ ): Promise<GenerateResult> {
531
+ const model = wireModel(request)
532
+ const content: Array<Record<string, unknown>> = []
533
+ if (request.mode === 'edit') {
534
+ if (typeof request.image !== 'string' || request.image === '') {
535
+ throw new ImageGenError('图生图需要上传参考图片', 'edit-image-missing')
536
+ }
537
+ // DashScope multimodal messages take the reference image as a content
538
+ // item; a base64 data URI rides in the same field as a remote URL.
539
+ const parsed = parseDataUrl(request.image)
540
+ if (parsed === undefined) throw new ImageGenError('参考图片格式无效', 'edit-image-invalid')
541
+ const bytes = Buffer.from(parsed.base64, 'base64')
542
+ if (bytes.byteLength > MAX_EDIT_IMAGE_BYTES) {
543
+ throw new ImageGenError('参考图片超过 10MB 上限', 'edit-image-too-large')
544
+ }
545
+ content.push({ image: request.image })
546
+ }
547
+ content.push({ text: request.prompt })
548
+
549
+ const batchable = isVersionedQwenImage(model)
550
+ const count = batchable ? clampCount(request.n) : 1
551
+ const size = qwenSize(model, request.size)
552
+ const body = {
553
+ model,
554
+ input: { messages: [{ role: 'user', content }] },
555
+ parameters: {
556
+ ...size !== undefined ? { size } : {},
557
+ ...count > 1 ? { n: count } : {},
558
+ },
559
+ }
560
+
561
+ const budget = requestSignal(options.signal, UPSTREAM_TIMEOUT_MS)
562
+ let response: Response
563
+ try {
564
+ response = await fetch(`${baseUrl}/services/aigc/multimodal-generation/generation`, {
565
+ method: 'POST',
566
+ headers: {
567
+ authorization: `Bearer ${upstream.apiKey.trim()}`,
568
+ 'content-type': 'application/json',
569
+ },
570
+ body: JSON.stringify(body),
571
+ signal: budget.signal,
572
+ })
573
+ } catch (error) {
574
+ const message = error instanceof Error ? error.message : String(error)
575
+ if (/aborter/i.test(message) || /timeout/i.test(message)) {
576
+ throw new ImageGenError('上游接口响应超时(240 秒)', 'upstream-timeout')
577
+ }
578
+ throw new ImageGenError(`无法连接上游接口:${message}`, 'upstream-unreachable')
579
+ } finally {
580
+ budget.dispose()
581
+ }
582
+
583
+ let payload: unknown
584
+ try {
585
+ payload = await response.json()
586
+ } catch {
587
+ throw new ImageGenError(`上游接口返回了非 JSON 响应(HTTP ${response.status})`, 'upstream-invalid')
588
+ }
589
+ if (!response.ok || payload === null || typeof payload !== 'object') {
590
+ throw new ImageGenError(upstreamMessage(payload, response.status), 'upstream-rejected')
591
+ }
592
+
593
+ // output.choices[].message.content[] mixes text and { image: url } items.
594
+ const record = payload as Record<string, unknown>
595
+ const output = record.output as Record<string, unknown> | undefined
596
+ const choices = output !== undefined && Array.isArray(output.choices) ? output.choices : []
597
+ const urls: string[] = []
598
+ for (const choice of choices) {
599
+ const message = choice !== null && typeof choice === 'object'
600
+ ? (choice as Record<string, unknown>).message
601
+ : undefined
602
+ const items = message !== null && typeof message === 'object' && Array.isArray((message as Record<string, unknown>).content)
603
+ ? (message as Record<string, unknown>).content as unknown[]
604
+ : []
605
+ for (const item of items) {
606
+ if (item !== null && typeof item === 'object') {
607
+ const image = (item as Record<string, unknown>).image
608
+ if (typeof image === 'string' && image !== '') urls.push(image)
609
+ }
610
+ }
611
+ }
612
+ if (urls.length === 0) {
613
+ throw new ImageGenError('上游响应缺少图片内容', 'upstream-empty')
614
+ }
615
+ const images = await Promise.all(urls.map(async url => {
616
+ const normalized = await normalizeItem({ url }, upstream)
617
+ return { b64: normalized.b64, mime: normalized.mime }
618
+ }))
619
+ return { images }
620
+ }
621
+
475
622
  /**
476
623
  * Forward one generate request to the configured endpoint. The requested image
477
624
  * count is satisfied with N parallel single-image requests (the `n` batch
@@ -482,6 +629,7 @@ export async function generateImage(upstream: UpstreamConfig, request: GenerateR
482
629
  const baseUrl = upstream.apiUrl.trim().replace(/\/+$/, '')
483
630
  if (baseUrl === '') throw new ImageGenError('api_url 未配置:请先在「设置 → 插件 → 可配置」中填写', 'config-missing')
484
631
  if (upstream.apiKey.trim() === '') throw new ImageGenError('api_key 未配置:请先在「设置 → 插件 → 可配置」中填写', 'config-missing')
632
+ if (isQwenImage(wireModel(request))) return generateQwenImage(baseUrl, upstream, request, options)
485
633
  if (request.mode === 'edit' && isZhipuImage(wireModel(request))) {
486
634
  throw new ImageGenError('智谱 GLM-Image 当前仅支持文生图,请切换到文生图模式或选择支持图生图的模型', 'edit-unsupported')
487
635
  }
package/src/index.ts CHANGED
@@ -117,7 +117,7 @@ const DEFAULT_ALLOW_AGENT_IMAGE_GENERATION = true
117
117
  const SECTION_ORDER = 150
118
118
 
119
119
  /** Model-facing announcement: plugin presence, capabilities, and limits. */
120
- export const IMAGEGEN_GUIDANCE = '本机已安装 dsh-imagegen 插件(DSH AI 生图):侧边栏「AI 生图」入口。能力:通过「渠道」对接 OpenAI 兼容图像生成 API(每个渠道 = 一个 API 端点 + 各自的模型目录),支持文生图(/images/generations)与图生图(/images/edits,上传参考图,grok-imagine 模型按官方 JSON image_url 协议发送,nanobanana 系列按 aspect_ratio / image_size 参数协议发送;seedream 系列统一走 /images/generations,参考图以 JSON image 数组发送;智谱 `glm-image` 使用官方 `/api/paas/v4/images/generations`,当前仅支持文生图)。API 地址与密钥在 GUI 设置中按渠道配置,密钥仅存于本机设置文档;生成请求由本地宿主代理转发,结果以 base64 返回面板,可预览与下载。模型只能使用用户在各渠道配置目录中的模型;检测模型时会过滤聊天、Embedding 等非图片模型,但模型出现在 /models 中仍不等于其网关原生支持生图协议,遇到 Qwen、Gemini 等非 OpenAI 生图协议时应如实说明上游兼容性。可一键把满意的图片加入「画廊」。内置「提示词模板库」(面板提示词框左下角「模板库」按钮):多来源标签页(精选案例库 / 沧河案例库,后续可扩展),打包 awesome-gpt-image-2 的数百条提示词案例,可搜索、筛选、收藏(星标,宿主持久化)与复用;各来源列表独立刷新,宿主每 12 小时后台自动同步一次。Agent 可直接调用 `generate_image` 提交文生图,也可用 `edit_image` 图生图;默认保持工具调用等待直到任务完成,完成图片显示在工具调用对应的左侧结果区域,模型收到状态和附件引用,不会额外伪造用户消息。用户也可以使用 `/edit_image <修改描述>`,命令会直接读取当前对话最近图片并调用插件图片模型,不经过对话模型的图片能力检查。若明确需要后台执行,可传 `wait_for_completion: false`,之后再用 `get_image_generation_task` 查询;不要反复轮询。限制:生成消耗上游 API 额度;图片内容由上游模型生成,可能不符合预期或包含不适宜内容;api_key 以明文存储在设置文档中;参考图会发送至所配置的 API 服务;模板库在线刷新与参考图首次加载需要访问对应来源站点(vibeui.top / gpt-image2.canghe.ai)。用户提到「生图 / 绘画 / 生成图片 / 文生图 / 图生图 / 画廊 / 提示词模板」时即指本插件,请据此协作。'
120
+ export const IMAGEGEN_GUIDANCE = '本机已安装 dsh-imagegen 插件(DSH AI 生图):侧边栏「AI 生图」入口。能力:通过「渠道」对接 OpenAI 兼容图像生成 API(每个渠道 = 一个 API 端点 + 各自的模型目录),支持文生图(/images/generations)与图生图(/images/edits,上传参考图,grok-imagine 模型按官方 JSON image_url 协议发送,nanobanana 系列按 aspect_ratio / image_size 参数协议发送;seedream 系列统一走 /images/generations,参考图以 JSON image 数组发送;智谱 `glm-image` 使用官方 `/api/paas/v4/images/generations`,当前仅支持文生图;qwen-image 系列使用阿里云 DashScope 原生接口(api_url 填 https://dashscope.aliyuncs.com/api/v1,不支持 OpenAI 兼容模式,该渠道不可复用于提示词增强,尺寸自动映射为宽*高)。API 地址与密钥在 GUI 设置中按渠道配置,密钥仅存于本机设置文档;生成请求由本地宿主代理转发,结果以 base64 返回面板,可预览与下载。模型只能使用用户在各渠道配置目录中的模型;检测模型时会过滤聊天、Embedding 等非图片模型,但模型出现在 /models 中仍不等于其网关原生支持生图协议,遇到 Qwen、Gemini 等非 OpenAI 生图协议时应如实说明上游兼容性。可一键把满意的图片加入「画廊」。内置「提示词模板库」(面板提示词框左下角「模板库」按钮):多来源标签页(精选案例库 / 沧河案例库,后续可扩展),打包 awesome-gpt-image-2 的数百条提示词案例,可搜索、筛选、收藏(星标,宿主持久化)与复用;各来源列表独立刷新,宿主每 12 小时后台自动同步一次。Agent 可直接调用 `generate_image` 提交文生图,也可用 `edit_image` 图生图;默认保持工具调用等待直到任务完成,完成图片显示在工具调用对应的左侧结果区域,模型收到状态和附件引用,不会额外伪造用户消息。用户也可以使用 `/edit_image <修改描述>`,命令会直接读取当前对话最近图片并调用插件图片模型,不经过对话模型的图片能力检查。若明确需要后台执行,可传 `wait_for_completion: false`,之后再用 `get_image_generation_task` 查询;不要反复轮询。限制:生成消耗上游 API 额度;图片内容由上游模型生成,可能不符合预期或包含不适宜内容;api_key 以明文存储在设置文档中;参考图会发送至所配置的 API 服务;模板库在线刷新与参考图首次加载需要访问对应来源站点(vibeui.top / gpt-image2.canghe.ai)。用户提到「生图 / 绘画 / 生成图片 / 文生图 / 图生图 / 画廊 / 提示词模板」时即指本插件,请据此协作。'
121
121
 
122
122
  /** Append the live channel × model table so an Agent can honor user choices. */
123
123
  function guidanceFor(channels: RuntimeChannel[], defaultChannelId: string): string {