Auto-add OpenRouter captioning models
| @@ -1,6 +1,6 @@ | |||
| 1 | import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; | 1 | import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; |
| 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; | 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; | 3 | import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, main_api, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; |
| 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; | 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; |
| 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; | 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 6 | import { getMultimodalCaption } from '../shared.js'; | 6 | import { getMultimodalCaption } from '../shared.js'; |
| @@ -431,14 +431,9 @@ jQuery(async function () { | |||
| 431 | $('#form_sheld').append(imgForm); | 431 | $('#form_sheld').append(imgForm); |
| 432 | $('#img_file').on('change', (e) => onSelectImage(e.originalEvent, '', false)); | 432 | $('#img_file').on('change', (e) => onSelectImage(e.originalEvent, '', false)); |
| 433 | } | 433 | } |
| 434 | function switchMultimodalBlocks() { | 434 | async function switchMultimodalBlocks() { |
| 435 | await addOpenRouterModels(); | ||
| 435 | const isMultimodal = extension_settings.caption.source === 'multimodal'; | 436 | const isMultimodal = extension_settings.caption.source === 'multimodal'; |
| 436 | $('#caption_ollama_pull').on('click', (e) => { | ||
| 437 | const presetModel = extension_settings.caption.multimodal_model !== 'ollama_current' ? extension_settings.caption.multimodal_model : ''; | ||
| 438 | e.preventDefault(); | ||
| 439 | $('#ollama_download_model').trigger('click'); | ||
| 440 | $('#dialogue_popup_input').val(presetModel); | ||
| 441 | }); | ||
| 442 | $('#caption_multimodal_block').toggle(isMultimodal); | 437 | $('#caption_multimodal_block').toggle(isMultimodal); |
| 443 | $('#caption_prompt_block').toggle(isMultimodal); | 438 | $('#caption_prompt_block').toggle(isMultimodal); |
| 444 | $('#caption_multimodal_api').val(extension_settings.caption.multimodal_api); | 439 | $('#caption_multimodal_api').val(extension_settings.caption.multimodal_api); |
| @@ -448,30 +443,48 @@ jQuery(async function () { | |||
| 448 | const types = type.split(','); | 443 | const types = type.split(','); |
| 449 | $(this).toggle(types.includes(extension_settings.caption.multimodal_api)); | 444 | $(this).toggle(types.includes(extension_settings.caption.multimodal_api)); |
| 450 | }); | 445 | }); |
| 451 | $('#caption_multimodal_api').on('change', () => { | ||
| 452 | const api = String($('#caption_multimodal_api').val()); | ||
| 453 | const model = String($(`#caption_multimodal_model option[data-type="${api}"]`).first().val()); | ||
| 454 | extension_settings.caption.multimodal_api = api; | ||
| 455 | extension_settings.caption.multimodal_model = model; | ||
| 456 | saveSettingsDebounced(); | ||
| 457 | switchMultimodalBlocks(); | ||
| 458 | }); | ||
| 459 | $('#caption_multimodal_model').on('change', () => { | ||
| 460 | extension_settings.caption.multimodal_model = String($('#caption_multimodal_model').val()); | ||
| 461 | saveSettingsDebounced(); | ||
| 462 | }); | ||
| 463 | } | 446 | } |
| 464 | async function addSettings() { | 447 | async function addSettings() { |
| 465 | const html = await renderExtensionTemplateAsync('caption', 'settings', { TEMPLATE_DEFAULT, PROMPT_DEFAULT }); | 448 | const html = await renderExtensionTemplateAsync('caption', 'settings', { TEMPLATE_DEFAULT, PROMPT_DEFAULT }); |
| 466 | $('#caption_container').append(html); | 449 | $('#caption_container').append(html); |
| 467 | } | 450 | } |
| 451 | async function addOpenRouterModels() { | ||
| 452 | const dropdown = document.getElementById('caption_multimodal_model'); | ||
| 453 | if (!(dropdown instanceof HTMLSelectElement)) { | ||
| 454 | return; | ||
| 455 | } | ||
| 456 | if (extension_settings.caption.source !== 'multimodal' || extension_settings.caption.multimodal_api !== 'openrouter') { | ||
| 457 | return; | ||
| 458 | } | ||
| 459 | const options = Array.from(dropdown.options); | ||
| 460 | const response = await fetch('/api/openrouter/models/multimodal', { | ||
| 461 | method: 'POST', | ||
| 462 | headers: getRequestHeaders(), | ||
| 463 | }); | ||
| 464 | if (!response.ok) { | ||
| 465 | return; | ||
| 466 | } | ||
| 467 | const modelIds = await response.json(); | ||
| 468 | if (Array.isArray(modelIds) && modelIds.length > 0) { | ||
| 469 | modelIds.forEach((modelId) => { | ||
| 470 | if (!modelId || typeof modelId !== 'string' || options.some(o => o.value === modelId)) { | ||
| 471 | return; | ||
| 472 | } | ||
| 473 | const option = document.createElement('option'); | ||
| 474 | option.value = modelId; | ||
| 475 | option.textContent = modelId; | ||
| 476 | option.dataset.type = 'openrouter'; | ||
| 477 | dropdown.add(option); | ||
| 478 | }); | ||
| 479 | } | ||
| 480 | } | ||
| 468 | 481 | ||
| 469 | await addSettings(); | 482 | await addSettings(); |
| 470 | addPictureSendForm(); | 483 | addPictureSendForm(); |
| 471 | addSendPictureButton(); | 484 | addSendPictureButton(); |
| 472 | setImageIcon(); | 485 | setImageIcon(); |
| 473 | migrateSettings(); | 486 | migrateSettings(); |
| 474 | switchMultimodalBlocks(); | 487 | await switchMultimodalBlocks(); |
| 475 | 488 | ||
| 476 | $('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode)); | 489 | $('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode)); |
| 477 | $('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy)); | 490 | $('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy)); |
| @@ -506,6 +519,24 @@ jQuery(async function () { | |||
| 506 | extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked'); | 519 | extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked'); |
| 507 | saveSettingsDebounced(); | 520 | saveSettingsDebounced(); |
| 508 | }); | 521 | }); |
| 522 | $('#caption_ollama_pull').on('click', (e) => { | ||
| 523 | const presetModel = extension_settings.caption.multimodal_model !== 'ollama_current' ? extension_settings.caption.multimodal_model : ''; | ||
| 524 | e.preventDefault(); | ||
| 525 | $('#ollama_download_model').trigger('click'); | ||
| 526 | $('#dialogue_popup_input').val(presetModel); | ||
| 527 | }); | ||
| 528 | $('#caption_multimodal_api').on('change', () => { | ||
| 529 | const api = String($('#caption_multimodal_api').val()); | ||
| 530 | const model = String($(`#caption_multimodal_model option[data-type="${api}"]`).first().val()); | ||
| 531 | extension_settings.caption.multimodal_api = api; | ||
| 532 | extension_settings.caption.multimodal_model = model; | ||
| 533 | saveSettingsDebounced(); | ||
| 534 | switchMultimodalBlocks(); | ||
| 535 | }); | ||
| 536 | $('#caption_multimodal_model').on('change', () => { | ||
| 537 | extension_settings.caption.multimodal_model = String($('#caption_multimodal_model').val()); | ||
| 538 | saveSettingsDebounced(); | ||
| 539 | }); | ||
| 509 | 540 | ||
| 510 | const onMessageEvent = async (index) => { | 541 | const onMessageEvent = async (index) => { |
| 511 | if (!extension_settings.caption.auto_mode) { | 542 | if (!extension_settings.caption.auto_mode) { |
| @@ -531,14 +562,15 @@ jQuery(async function () { | |||
| 531 | await captionExistingMessage(data); | 562 | await captionExistingMessage(data); |
| 532 | appendMediaToMessage(data, messageBlock, false); | 563 | appendMediaToMessage(data, messageBlock, false); |
| 533 | await saveChatConditional(); | 564 | await saveChatConditional(); |
| 534 | } catch(e) { | 565 | } catch (e) { |
| 535 | console.error('Message image recaption failed', e); | 566 | console.error('Message image recaption failed', e); |
| 536 | } finally { | 567 | } finally { |
| 537 | messageImg.removeClass(animationClass); | 568 | messageImg.removeClass(animationClass); |
| 538 | } | 569 | } |
| 539 | }); | 570 | }); |
| 540 | 571 | ||
| 541 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'caption', | 572 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 573 | name: 'caption', | ||
| 542 | callback: captionCommandCallback, | 574 | callback: captionCommandCallback, |
| 543 | returns: 'caption', | 575 | returns: 'caption', |
| 544 | namedArgumentList: [ | 576 | namedArgumentList: [ |
| @@ -610,6 +610,9 @@ app.use('/api/search', require('./src/endpoints/search').router); | |||
| 610 | // Ooba/OpenAI text completions | 610 | // Ooba/OpenAI text completions |
| 611 | app.use('/api/backends/text-completions', require('./src/endpoints/backends/text-completions').router); | 611 | app.use('/api/backends/text-completions', require('./src/endpoints/backends/text-completions').router); |
| 612 | 612 | ||
| 613 | // OpenRouter | ||
| 614 | app.use('/api/openrouter', require('./src/endpoints/openrouter').router); | ||
| 615 | |||
| 613 | // KoboldAI | 616 | // KoboldAI |
| 614 | app.use('/api/backends/kobold', require('./src/endpoints/backends/kobold').router); | 617 | app.use('/api/backends/kobold', require('./src/endpoints/backends/kobold').router); |
| 615 | 618 | ||
| @@ -0,0 +1,32 @@ | |||
| 1 | const express = require('express'); | ||
| 2 | const { jsonParser } = require('../express-common'); | ||
| 3 | |||
| 4 | const router = express.Router(); | ||
| 5 | const API_OPENROUTER = 'https://openrouter.ai/api/v1'; | ||
| 6 | |||
| 7 | router.post('/models/multimodal', jsonParser, async (_req, res) => { | ||
| 8 | try { | ||
| 9 | // The endpoint is available without authentication | ||
| 10 | const response = await fetch(`${API_OPENROUTER}/models`, { | ||
| 11 | method: 'GET', | ||
| 12 | headers: { | ||
| 13 | 'Accept': 'application/json', | ||
| 14 | }, | ||
| 15 | }); | ||
| 16 | |||
| 17 | if (!response.ok) { | ||
| 18 | return res.json([]); | ||
| 19 | } | ||
| 20 | |||
| 21 | const data = await response.json(); | ||
| 22 | const models = data?.data || []; | ||
| 23 | const multimodalModels = models.filter(m => m?.architecture?.modality === 'text+image->text').map(m => m.id); | ||
| 24 | |||
| 25 | return res.json(multimodalModels); | ||
| 26 | } catch (error) { | ||
| 27 | console.error(error); | ||
| 28 | return res.sendStatus(500); | ||
| 29 | } | ||
| 30 | }); | ||
| 31 | |||
| 32 | module.exports = { router }; | ||