Merge pull request #2489 from bdashore3/staging Add tabbyAPI model downloader
Signed| @@ -2348,6 +2348,16 @@ | ||
| 2348 | 2348 | <small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small> |
| 2349 | 2349 | <input id="tabby_api_url_text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-server-history="tabby"> |
| 2350 | 2350 | </div> |
| 2351 | + <div class="flex1"> | |
| 2352 | + <h4> | |
| 2353 | + <span data-i18n="Tabby Model">Tabby Model</span> | |
| 2354 | + </h4> | |
| 2355 | + </h4> | |
| 2356 | + <div id="tabby_download_model" class="menu_button menu_button_icon"> | |
| 2357 | + <i class="fa-solid fa-download"></i> | |
| 2358 | + <span data-i18n="Download">Download</span> | |
| 2359 | + </div> | |
| 2360 | + </div> | |
| 2351 | 2361 | </div> |
| 2352 | 2362 | <div data-tg-type="koboldcpp"> |
| 2353 | 2363 | <div class="flex-container flexFlowColumn"> |
| @@ -0,0 +1,61 @@ | ||
| 1 | +<div id="tabby_downloader_popup"> | |
| 2 | + <div> | |
| 3 | + <h3><strong data-i18n="">Download Model</strong> | |
| 4 | + <a href="https://github.com/theroyallab/async-hf-downloader" class="notes-link" target="_blank"> | |
| 5 | + <span class="note-link-span">?</span> | |
| 6 | + </a> | |
| 7 | + </h3> | |
| 8 | + | |
| 9 | + <small class="flex-container extensions_info justifyCenter"> | |
| 10 | + Download a HuggingFace model with TabbyAPI | |
| 11 | + </small> | |
| 12 | + <small class="flex-container extensions_info justifyCenter"> | |
| 13 | + (Requires an admin key) | |
| 14 | + </small> | |
| 15 | + <hr /> | |
| 16 | + | |
| 17 | + <!-- Model parameter textboxes --> | |
| 18 | + Repo ID | |
| 19 | + <div class="flex-container"> | |
| 20 | + <input name="hf_repo_id" class="text_pole" type="text" placeholder="Ex. turboderp/Llama-3-8B-exl2" /> | |
| 21 | + </div> | |
| 22 | + <div class="range-block-title justifyCenter"> | |
| 23 | + <span data-i18n="Downloader Options">Downloader Options</span> | |
| 24 | + <div class="margin5 fa-solid fa-circle-info opacity50p " data-i18n="[title]Extra parameters for downloading/HuggingFace API" title="Extra parameters for downloading/HuggingFace API. If unsure, leave these blank."></div> | |
| 25 | + </div> | |
| 26 | + <div class="flex-container"> | |
| 27 | + <div class="flex1"> | |
| 28 | + <label for="revision"> | |
| 29 | + <small data-i18n="Revision">Revision</small> | |
| 30 | + </label> | |
| 31 | + <input name="revision" class="text_pole" type="text" placeholder="Ex. 6.0bpw" /> | |
| 32 | + </div> | |
| 33 | + <div class="flex1"> | |
| 34 | + <label for="folder_name"> | |
| 35 | + <small data-i18n="Folder Name">Output Folder Name</small> | |
| 36 | + </label> | |
| 37 | + <input name="folder_name" class="text_pole" type="text" /> | |
| 38 | + </div> | |
| 39 | + <div class="flex1"> | |
| 40 | + <label for="hf_token"> | |
| 41 | + <small data-i18n="HF Token">HF Token</small> | |
| 42 | + </label> | |
| 43 | + <input name="hf_token" class="text_pole" type="text" placeholder="For gated models" /> | |
| 44 | + </div> | |
| 45 | + </div> | |
| 46 | + <div class="range-block-title justifyCenter"> | |
| 47 | + <span data-i18n="Include Patterns">Include Patterns</span> | |
| 48 | + <div class="margin5 fa-solid fa-circle-info opacity50p" data-i18n="[title]Glob patterns of files to include in the download." title="Glob patterns of files to include in the download. Separate each pattern by a newline."></div> | |
| 49 | + </div> | |
| 50 | + <div class="flex-container"> | |
| 51 | + <textarea class="text_pole textarea_compact" name="tabby_download_include" placeholder="Ex. *.txt"></textarea> | |
| 52 | + </div> | |
| 53 | + <div class="range-block-title justifyCenter"> | |
| 54 | + <span data-i18n="Exclude Patterns">Exclude Patterns</span> | |
| 55 | + <div class="margin5 fa-solid fa-circle-info opacity50p" data-i18n="[title]Glob patterns of files to exclude in the download." title="Glob patterns of files to exclude in the download. Separate each pattern by a newline."></div> | |
| 56 | + </div> | |
| 57 | + <div class="flex-container"> | |
| 58 | + <textarea class="text_pole textarea_compact" name="tabby_download_exclude" placeholder="Ex. *.txt"></textarea> | |
| 59 | + </div> | |
| 60 | + </div> | |
| 61 | +</div> | |
| 61 | \ No newline at end of file | |
| @@ -1,7 +1,9 @@ | ||
| 1 | 1 | import { isMobile } from './RossAscends-mods.js'; |
| 2 | 2 | import { amount_gen, callPopup, eventSource, event_types, getRequestHeaders, max_context, online_status, setGenerationParamsFromPreset } from '../script.js'; |
| 3 | 3 | import { textgenerationwebui_settings as textgen_settings, textgen_types } from './textgen-settings.js'; |
| 4 | 4 | import { tokenizers } from './tokenizers.js'; |
| 5 | +import { renderTemplateAsync } from './templates.js'; | |
| 6 | +import { POPUP_TYPE, callGenericPopup } from './popup.js'; | |
| 5 | 7 | |
| 6 | 8 | let mancerModels = []; |
| 7 | 9 | let togetherModels = []; |
| @@ -470,6 +472,74 @@ async function downloadOllamaModel() { | ||
| 470 | 472 | } |
| 471 | 473 | } |
| 472 | 474 | |
| 475 | +async function downloadTabbyModel() { | |
| 476 | + try { | |
| 477 | + const serverUrl = textgen_settings.server_urls[textgen_types.TABBY]; | |
| 478 | + | |
| 479 | + if (online_status === 'no_connection' || !serverUrl) { | |
| 480 | + toastr.info('Please connect to a TabbyAPI server first.'); | |
| 481 | + return; | |
| 482 | + } | |
| 483 | + | |
| 484 | + const downloadHtml = $(await renderTemplateAsync('tabbyDownloader')); | |
| 485 | + const popupResult = await callGenericPopup(downloadHtml, POPUP_TYPE.CONFIRM, '', { okButton: 'Download', cancelButton: 'Cancel' }); | |
| 486 | + | |
| 487 | + // User cancelled the download | |
| 488 | + if (!popupResult) { | |
| 489 | + return; | |
| 490 | + } | |
| 491 | + | |
| 492 | + const repoId = downloadHtml.find('input[name="hf_repo_id"]').val().toString() | |
| 493 | + if (!repoId) { | |
| 494 | + toastr.error('A HuggingFace repo ID must be provided. Skipping Download.'); | |
| 495 | + return; | |
| 496 | + } | |
| 497 | + | |
| 498 | + if (repoId.split("/").length !== 2) { | |
| 499 | + toastr.error('A HuggingFace repo ID must be formatted as Author/Name. Please try again.'); | |
| 500 | + return; | |
| 501 | + } | |
| 502 | + | |
| 503 | + const params = { | |
| 504 | + repo_id: repoId, | |
| 505 | + folder_name: downloadHtml.find('input[name="folder_name"]').val() || undefined, | |
| 506 | + revision: downloadHtml.find('input[name="revision"]').val() || undefined, | |
| 507 | + token: downloadHtml.find('input[name="hf_token"]').val() || undefined, | |
| 508 | + } | |
| 509 | + | |
| 510 | + for (const suffix of ["include", "exclude"]) { | |
| 511 | + const patterns = downloadHtml.find(`textarea[name="tabby_download_${suffix}"]`).val().toString(); | |
| 512 | + if (patterns) { | |
| 513 | + params[suffix] = patterns.split("\n"); | |
| 514 | + } | |
| 515 | + } | |
| 516 | + | |
| 517 | + // Params for the server side of ST | |
| 518 | + params['api_server'] = serverUrl; | |
| 519 | + params['api_type'] = textgen_settings.type | |
| 520 | + | |
| 521 | + toastr.info('Downloading. Check the Tabby console for progress reports.'); | |
| 522 | + | |
| 523 | + const response = await fetch('/api/backends/text-completions/tabby/download', { | |
| 524 | + method: 'POST', | |
| 525 | + headers: getRequestHeaders(), | |
| 526 | + body: JSON.stringify(params), | |
| 527 | + }); | |
| 528 | + | |
| 529 | + if (response.status === 403) { | |
| 530 | + toastr.error("The provided key has invalid permissions. Please use an admin key for downloading."); | |
| 531 | + return; | |
| 532 | + } else if (!response.ok) { | |
| 533 | + throw new Error(response.statusText); | |
| 534 | + } | |
| 535 | + | |
| 536 | + toastr.success('Download complete.'); | |
| 537 | + } catch (err) { | |
| 538 | + console.error(err); | |
| 539 | + toastr.error('Failed to download HuggingFace model in TabbyAPI. Please try again.') | |
| 540 | + } | |
| 541 | +} | |
| 542 | + | |
| 473 | 543 | function calculateOpenRouterCost() { |
| 474 | 544 | if (textgen_settings.type !== textgen_types.OPENROUTER) { |
| 475 | 545 | return; |
| @@ -538,6 +608,7 @@ jQuery(function () { | ||
| 538 | 608 | $('#vllm_model').on('change', onVllmModelSelect); |
| 539 | 609 | $('#aphrodite_model').on('change', onAphroditeModelSelect); |
| 540 | 610 | $('#featherless_model').on('change', onFeatherlessModelSelect); |
| 611 | + $('#tabby_download_model').on('click', downloadTabbyModel); | |
| 541 | 612 | |
| 542 | 613 | const providersSelect = $('.openrouter_providers'); |
| 543 | 614 | for (const provider of OPENROUTER_PROVIDERS) { |
| @@ -588,7 +588,53 @@ llamacpp.post('/slots', jsonParser, async function (request, response) { | ||
| 588 | 588 | } |
| 589 | 589 | }); |
| 590 | 590 | |
| 591 | +const tabby = express.Router(); | |
| 592 | + | |
| 593 | +tabby.post('/download', jsonParser, async function (request, response) { | |
| 594 | + try { | |
| 595 | + const baseUrl = String(request.body.api_server).replace(/\/$/, ''); | |
| 596 | + | |
| 597 | + const args = { | |
| 598 | + method: 'POST', | |
| 599 | + headers: { 'Content-Type': 'application/json' }, | |
| 600 | + body: JSON.stringify(request.body), | |
| 601 | + timeout: 0, | |
| 602 | + } | |
| 603 | + | |
| 604 | + setAdditionalHeaders(request, args, baseUrl); | |
| 605 | + | |
| 606 | + // Check key permissions | |
| 607 | + const permissionResponse = await fetch(`${baseUrl}/v1/auth/permission`, { | |
| 608 | + headers: args.headers | |
| 609 | + }); | |
| 610 | + | |
| 611 | + if (permissionResponse.ok) { | |
| 612 | + const permissionJson = await permissionResponse.json(); | |
| 613 | + | |
| 614 | + if (permissionJson['permission'] !== 'admin') { | |
| 615 | + return response.status(403).send({ error: true }); | |
| 616 | + } | |
| 617 | + } else { | |
| 618 | + console.log('API Permission error:', permissionResponse.status, permissionResponse.statusText); | |
| 619 | + return response.status(permissionResponse.status).send({ error: true }); | |
| 620 | + } | |
| 621 | + | |
| 622 | + const fetchResponse = await fetch(`${baseUrl}/v1/download`, args); | |
| 623 | + | |
| 624 | + if (!fetchResponse.ok) { | |
| 625 | + console.log('Download error:', fetchResponse.status, fetchResponse.statusText); | |
| 626 | + return response.status(fetchResponse.status).send({ error: true }); | |
| 627 | + } | |
| 628 | + | |
| 629 | + return response.send({ ok: true }); | |
| 630 | + } catch (error) { | |
| 631 | + console.error(error); | |
| 632 | + return response.status(500); | |
| 633 | + } | |
| 634 | +}); | |
| 635 | + | |
| 591 | 636 | router.use('/ollama', ollama); |
| 592 | 637 | router.use('/llamacpp', llamacpp); |
| 638 | +router.use('/tabby', tabby); | |
| 593 | 639 | |
| 594 | 640 | module.exports = { router }; |