Make dynamic reroll available without use of modifier key Linting
| @@ -72,6 +72,20 @@ | |||
| 72 | opacity: 0.5; | 72 | opacity: 0.5; |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | .logprobs_output_prefix:hover { | ||
| 76 | background-color: rgba(255, 0, 50, 0.4); | ||
| 77 | } | ||
| 78 | |||
| 79 | .logprobs_output_prefix:hover ~ .logprobs_output_prefix { | ||
| 80 | background-color: rgba(255, 0, 50, 0.4); | ||
| 81 | } | ||
| 82 | |||
| 83 | #logprobsReroll { | ||
| 84 | float: right; /* Position the button to the right */ | ||
| 85 | margin: 5px 0 5px 10px; /* Add spacing (top, right, bottom, left) */ | ||
| 86 | clear: right; /* Ensure it starts on a new line */ | ||
| 87 | } | ||
| 88 | |||
| 75 | .logprobs_candidate_list { | 89 | .logprobs_candidate_list { |
| 76 | grid-row-start: 3; | 90 | grid-row-start: 3; |
| 77 | grid-row-end: 4; | 91 | grid-row-end: 4; |
| @@ -1,6 +1,5 @@ | |||
| 1 | import { | 1 | import { |
| 2 | animation_duration, | 2 | animation_duration, |
| 3 | callPopup, | ||
| 4 | chat, | 3 | chat, |
| 5 | cleanUpMessage, | 4 | cleanUpMessage, |
| 6 | event_types, | 5 | event_types, |
| @@ -13,6 +12,8 @@ import { | |||
| 13 | import { debounce, delay, getStringHash } from './utils.js'; | 12 | import { debounce, delay, getStringHash } from './utils.js'; |
| 14 | import { decodeTextTokens, getTokenizerBestMatch } from './tokenizers.js'; | 13 | import { decodeTextTokens, getTokenizerBestMatch } from './tokenizers.js'; |
| 15 | import { power_user } from './power-user.js'; | 14 | import { power_user } from './power-user.js'; |
| 15 | import { callGenericPopup, POPUP_TYPE } from './popup.js'; | ||
| 16 | import { t } from './i18n.js'; | ||
| 16 | 17 | ||
| 17 | const TINTS = 4; | 18 | const TINTS = 4; |
| 18 | const MAX_MESSAGE_LOGPROBS = 100; | 19 | const MAX_MESSAGE_LOGPROBS = 100; |
| @@ -43,17 +44,26 @@ const MAX_MESSAGE_LOGPROBS = 100; | |||
| 43 | * @property {Candidate[]} topLogprobs - Array of top candidate tokens | 44 | * @property {Candidate[]} topLogprobs - Array of top candidate tokens |
| 44 | */ | 45 | */ |
| 45 | 46 | ||
| 46 | let state = { | 47 | /** |
| 47 | /** @type {TokenLogprobs | null} */ | 48 | * State object for Token Probabilities |
| 49 | * @typedef {Object} LogprobsState | ||
| 50 | * @property {?TokenLogprobs} selectedTokenLogprobs Log probabilities for | ||
| 51 | * currently-selected token. | ||
| 52 | * @property {Map<number, MessageLogprobData>} messageLogprobs Log probabilities for | ||
| 53 | * each message, keyed by message hash. | ||
| 54 | */ | ||
| 55 | |||
| 56 | /** | ||
| 57 | * @type {LogprobsState} state | ||
| 58 | */ | ||
| 59 | const state = { | ||
| 48 | selectedTokenLogprobs: null, | 60 | selectedTokenLogprobs: null, |
| 49 | /** @type {Map<number, MessageLogprobData>} */ | ||
| 50 | messageLogprobs: new Map(), | 61 | messageLogprobs: new Map(), |
| 51 | }; | 62 | }; |
| 52 | 63 | ||
| 53 | /** | 64 | /** |
| 54 | * renderAlternativeTokensView renders the Token Probabilities UI and all | 65 | * Renders the Token Probabilities UI and all subviews with the active message's |
| 55 | * subviews with the active message's logprobs data. If the message has no token | 66 | * logprobs data. If the message has no token logprobs, a message is displayed. |
| 56 | * logprobs, a zero-state is rendered. | ||
| 57 | */ | 67 | */ |
| 58 | function renderAlternativeTokensView() { | 68 | function renderAlternativeTokensView() { |
| 59 | const view = $('#logprobs_generation_output'); | 69 | const view = $('#logprobs_generation_output'); |
| @@ -68,13 +78,14 @@ function renderAlternativeTokensView() { | |||
| 68 | const usingSmoothStreaming = isStreamingEnabled() && power_user.smooth_streaming; | 78 | const usingSmoothStreaming = isStreamingEnabled() && power_user.smooth_streaming; |
| 69 | if (!messageLogprobs?.length || usingSmoothStreaming) { | 79 | if (!messageLogprobs?.length || usingSmoothStreaming) { |
| 70 | const emptyState = $('<div></div>'); | 80 | const emptyState = $('<div></div>'); |
| 71 | const noTokensMsg = usingSmoothStreaming | 81 | const noTokensMsg = !power_user.request_token_probabilities |
| 72 | ? 'Token probabilities are not available when using Smooth Streaming.' | 82 | ? '<span>Enable <b>Request token probabilities</b> in the User Settings menu to use this feature.</span>' |
| 73 | : 'No token probabilities available for the current message.'; | 83 | : usingSmoothStreaming |
| 74 | const msg = power_user.request_token_probabilities | 84 | ? t`Token probabilities are not available when using Smooth Streaming.` |
| 75 | ? noTokensMsg | 85 | : is_send_press |
| 76 | : '<span>Enable <b>Request token probabilities</b> in the User Settings menu to use this feature.</span>'; | 86 | ? t`Generation in progress...` |
| 77 | emptyState.html(msg); | 87 | : t`No token probabilities available for the current message.`; |
| 88 | emptyState.html(noTokensMsg); | ||
| 78 | emptyState.addClass('logprobs_empty_state'); | 89 | emptyState.addClass('logprobs_empty_state'); |
| 79 | view.append(emptyState); | 90 | view.append(emptyState); |
| 80 | return; | 91 | return; |
| @@ -84,16 +95,39 @@ function renderAlternativeTokensView() { | |||
| 84 | const tokenSpans = []; | 95 | const tokenSpans = []; |
| 85 | 96 | ||
| 86 | if (prefix) { | 97 | if (prefix) { |
| 87 | const prefixSpan = $('<span></span>'); | 98 | const rerollButton = $('<button id="logprobsReroll" class="menu_button">' + |
| 88 | prefixSpan.text(prefix); | 99 | ' <span class="fa-solid fa-redo logprobs_reroll"></span>' + |
| 89 | prefixSpan.html(prefixSpan.html().replace(/\n/g, '<br>')); | 100 | '</button>'); |
| 90 | prefixSpan.addClass('logprobs_output_prefix'); | 101 | rerollButton.attr('title', t`Reroll with the entire prefix`); |
| 91 | prefixSpan.attr('title', 'Select to reroll the last \'Continue\' generation.\nHold the CTRL key when clicking to reroll from before that word.'); | 102 | rerollButton.on('click', () => onPrefixClicked(prefix.length)); |
| 92 | prefixSpan.click(onPrefixClicked); | 103 | tokenSpans.push(rerollButton); |
| 93 | addKeyboardProps(prefixSpan); | 104 | |
| 94 | tokenSpans.push(...withVirtualWhitespace(prefix, prefixSpan)); | 105 | let cumulativeOffset = 0; |
| 106 | const words = prefix.split(/\s+/); | ||
| 107 | const delimiters = prefix.match(/\s+/g) || []; // Capture the actual delimiters | ||
| 108 | |||
| 109 | words.forEach((word, i) => { | ||
| 110 | const span = $('<span></span>'); | ||
| 111 | span.text(`${word} `); | ||
| 112 | |||
| 113 | span.addClass('logprobs_output_prefix'); | ||
| 114 | span.attr('title', t`Reroll from this point`); | ||
| 115 | |||
| 116 | let offset = cumulativeOffset; | ||
| 117 | span.on('click', () => onPrefixClicked(offset)); | ||
| 118 | addKeyboardProps(span); | ||
| 119 | |||
| 120 | tokenSpans.push(span); | ||
| 121 | tokenSpans.push(delimiters[i]?.includes('\n') | ||
| 122 | ? document.createElement('br') | ||
| 123 | : document.createTextNode(delimiters[i] || ' '), | ||
| 124 | ); | ||
| 125 | |||
| 126 | cumulativeOffset += word.length + (delimiters[i]?.length || 0); | ||
| 127 | }); | ||
| 95 | } | 128 | } |
| 96 | 129 | ||
| 130 | |||
| 97 | messageLogprobs.forEach((tokenData, i) => { | 131 | messageLogprobs.forEach((tokenData, i) => { |
| 98 | const { token } = tokenData; | 132 | const { token } = tokenData; |
| 99 | const span = $('<span></span>'); | 133 | const span = $('<span></span>'); |
| @@ -101,7 +135,7 @@ function renderAlternativeTokensView() { | |||
| 101 | span.text(text); | 135 | span.text(text); |
| 102 | span.addClass('logprobs_output_token'); | 136 | span.addClass('logprobs_output_token'); |
| 103 | span.addClass('logprobs_tint_' + (i % TINTS)); | 137 | span.addClass('logprobs_tint_' + (i % TINTS)); |
| 104 | span.click(() => onSelectedTokenChanged(tokenData, span)); | 138 | span.on('click', () => onSelectedTokenChanged(tokenData, span)); |
| 105 | addKeyboardProps(span); | 139 | addKeyboardProps(span); |
| 106 | tokenSpans.push(...withVirtualWhitespace(token, span)); | 140 | tokenSpans.push(...withVirtualWhitespace(token, span)); |
| 107 | }); | 141 | }); |
| @@ -129,6 +163,10 @@ function addKeyboardProps(element) { | |||
| 129 | /** | 163 | /** |
| 130 | * renderTopLogprobs renders the top logprobs subview with the currently | 164 | * renderTopLogprobs renders the top logprobs subview with the currently |
| 131 | * selected token highlighted. If no token is selected, the subview is hidden. | 165 | * selected token highlighted. If no token is selected, the subview is hidden. |
| 166 | * | ||
| 167 | * Callers: | ||
| 168 | * - renderAlternativeTokensView, to render the entire view | ||
| 169 | * - onSelectedTokenChanged, to update the view when a token is selected | ||
| 132 | */ | 170 | */ |
| 133 | function renderTopLogprobs() { | 171 | function renderTopLogprobs() { |
| 134 | $('#logprobs_top_logprobs_hint').hide(); | 172 | $('#logprobs_top_logprobs_hint').hide(); |
| @@ -150,8 +188,7 @@ function renderTopLogprobs() { | |||
| 150 | const probability = Math.exp(log); | 188 | const probability = Math.exp(log); |
| 151 | sum += probability; | 189 | sum += probability; |
| 152 | return [text, probability, log]; | 190 | return [text, probability, log]; |
| 153 | } | 191 | } else { |
| 154 | else { | ||
| 155 | return [text, log, null]; | 192 | return [text, log, null]; |
| 156 | } | 193 | } |
| 157 | }); | 194 | }); |
| @@ -175,7 +212,7 @@ function renderTopLogprobs() { | |||
| 175 | } | 212 | } |
| 176 | addKeyboardProps(container); | 213 | addKeyboardProps(container); |
| 177 | if (token !== '<others>') { | 214 | if (token !== '<others>') { |
| 178 | container.click(() => onAlternativeClicked(state.selectedTokenLogprobs, token)); | 215 | container.on('click', () => onAlternativeClicked(state.selectedTokenLogprobs, token)); |
| 179 | } else { | 216 | } else { |
| 180 | container.prop('disabled', true); | 217 | container.prop('disabled', true); |
| 181 | } | 218 | } |
| @@ -192,9 +229,8 @@ function renderTopLogprobs() { | |||
| 192 | } | 229 | } |
| 193 | 230 | ||
| 194 | /** | 231 | /** |
| 195 | * onSelectedTokenChanged is called when the user clicks on a token in the | 232 | * User clicks on a token in the token output view. It updates the selected token state |
| 196 | * token output view. It updates the selected token state and re-renders the | 233 | * and re-renders the top logprobs view, or deselects the token if it was already selected. |
| 197 | * top logprobs view, or deselects the token if it was already selected. | ||
| 198 | * @param {TokenLogprobs} logprobs - logprob data for the selected token | 234 | * @param {TokenLogprobs} logprobs - logprob data for the selected token |
| 199 | * @param {Element} span - target span node that was clicked | 235 | * @param {Element} span - target span node that was clicked |
| 200 | */ | 236 | */ |
| @@ -223,7 +259,10 @@ function onAlternativeClicked(tokenLogprobs, alternative) { | |||
| 223 | } | 259 | } |
| 224 | 260 | ||
| 225 | if (getGeneratingApi() === 'openai') { | 261 | if (getGeneratingApi() === 'openai') { |
| 226 | return callPopup('<h3>Feature unavailable</h3><p>Due to API limitations, rerolling a token is not supported with OpenAI. Try switching to a different API.</p>', 'text'); | 262 | const title = t`Feature unavailable`; |
| 263 | const message = t`Due to API limitations, rerolling a token is not supported with OpenAI. Try switching to a different API.`; | ||
| 264 | const content = `<h3>${title}</h3><p>${message}</p>`; | ||
| 265 | return callGenericPopup(content, POPUP_TYPE.TEXT); | ||
| 227 | } | 266 | } |
| 228 | 267 | ||
| 229 | const { messageLogprobs, continueFrom } = getActiveMessageLogprobData(); | 268 | const { messageLogprobs, continueFrom } = getActiveMessageLogprobData(); |
| @@ -234,79 +273,29 @@ function onAlternativeClicked(tokenLogprobs, alternative) { | |||
| 234 | 273 | ||
| 235 | const prefix = continueFrom || ''; | 274 | const prefix = continueFrom || ''; |
| 236 | const prompt = prefix + tokens.join(''); | 275 | const prompt = prefix + tokens.join(''); |
| 237 | const messageId = chat.length - 1; | 276 | addGeneration(prompt); |
| 238 | createSwipe(messageId, prompt); | ||
| 239 | |||
| 240 | $('.swipe_right:last').click(); // :see_no_evil: | ||
| 241 | |||
| 242 | Generate('continue').then(_ => void _); | ||
| 243 | } | 277 | } |
| 244 | 278 | ||
| 245 | /** | 279 | /** |
| 246 | * getTextBeforeClickedWord retrieves the portion of text within a span | 280 | * User clicks on the reroll button in the token output view, or on a word in the |
| 247 | * that appears before the word clicked by the user. Using the x and y | 281 | * prefix. Retrieve the prefix for the current message and truncate it at the |
| 248 | * coordinates from a PointerEvent, this function identifies the exact | 282 | * offset for the selected word. Then request a `continue` completion from the |
| 249 | * word clicked and returns the text preceding it within the span. | 283 | * model with the new prompt. |
| 250 | * | ||
| 251 | * If the clicked position does not resolve to a valid word or text node, | ||
| 252 | * the entire span text is returned as a fallback. | ||
| 253 | * | 284 | * |
| 254 | * @param {PointerEvent} event - The click event containing the x and y coordinates. | 285 | * If no offset is provided, the entire prefix will be rerolled. |
| 255 | * @param {string} spanText - The full text content of the span element. | ||
| 256 | * @returns {string} The text before the clicked word, or the entire span text as fallback. | ||
| 257 | */ | ||
| 258 | function getTextBeforeClickedWord(event, spanText) { | ||
| 259 | const x = event.clientX; | ||
| 260 | const y = event.clientY; | ||
| 261 | const range = document.caretRangeFromPoint(x, y); | ||
| 262 | |||
| 263 | if (range && range.startContainer.nodeType === Node.TEXT_NODE) { | ||
| 264 | const textNode = range.startContainer; | ||
| 265 | const offset = range.startOffset; | ||
| 266 | |||
| 267 | // Get the full text content of the text node | ||
| 268 | const text = textNode.nodeValue; | ||
| 269 | |||
| 270 | // Find the boundaries of the clicked word | ||
| 271 | const start = text.lastIndexOf(' ', offset - 1) + 1; | ||
| 272 | |||
| 273 | // Return the text before the clicked word | ||
| 274 | return text.slice(0, start); | ||
| 275 | } | ||
| 276 | |||
| 277 | // If we can't determine the exact word, return the full span text as a fallback | ||
| 278 | return spanText; | ||
| 279 | } | ||
| 280 | |||
| 281 | |||
| 282 | /** | ||
| 283 | * onPrefixClicked is called when the user clicks on the carried-over prefix | ||
| 284 | * in the token output view. It allows them to reroll the last 'continue' | ||
| 285 | * completion with none of the output generated from it, in case they don't | ||
| 286 | * like the results. | ||
| 287 | * | 286 | * |
| 288 | * If the user holds the Ctrl key while clicking, only the portion of text | 287 | * @param {number} offset - index of the token in the prefix to reroll from |
| 289 | * before the clicked word is retained as the prefix for rerolling | 288 | * @returns {void} |
| 289 | * @param offset | ||
| 290 | */ | 290 | */ |
| 291 | function onPrefixClicked() { | 291 | function onPrefixClicked(offset = undefined) { |
| 292 | if (!checkGenerateReady()) { | 292 | if (!checkGenerateReady()) { |
| 293 | return; | 293 | return; |
| 294 | } | 294 | } |
| 295 | 295 | ||
| 296 | const { continueFrom } = getActiveMessageLogprobData(); | 296 | const { continueFrom } = getActiveMessageLogprobData() || {}; |
| 297 | const messageId = chat.length - 1; | 297 | const prefix = continueFrom ? continueFrom.substring(0, offset) : ''; |
| 298 | 298 | addGeneration(prefix); | |
| 299 | // Check if Ctrl key is pressed during the click | ||
| 300 | let prefix = continueFrom || ''; | ||
| 301 | if (event.ctrlKey) { | ||
| 302 | // Ctrl is pressed - use the text before the clicked word | ||
| 303 | prefix = getTextBeforeClickedWord(event, continueFrom); | ||
| 304 | } | ||
| 305 | |||
| 306 | // Use the determined `prefix` | ||
| 307 | createSwipe(messageId, prefix); | ||
| 308 | $('.swipe_right:last').click(); | ||
| 309 | Generate('continue').then(_ => void _); | ||
| 310 | } | 299 | } |
| 311 | 300 | ||
| 312 | function checkGenerateReady() { | 301 | function checkGenerateReady() { |
| @@ -317,6 +306,22 @@ function checkGenerateReady() { | |||
| 317 | return true; | 306 | return true; |
| 318 | } | 307 | } |
| 319 | 308 | ||
| 309 | /** | ||
| 310 | * Generates a new swipe as a continuation of the given prompt, when user selects | ||
| 311 | * an alternative token or rerolls from a prefix. | ||
| 312 | * | ||
| 313 | * @param prompt | ||
| 314 | */ | ||
| 315 | function addGeneration(prompt) { | ||
| 316 | const messageId = chat.length - 1; | ||
| 317 | if (prompt && prompt.length > 0) { | ||
| 318 | createSwipe(messageId, prompt); | ||
| 319 | $('.swipe_right:last').trigger('click'); | ||
| 320 | void Generate('continue'); | ||
| 321 | } else { | ||
| 322 | $('.swipe_right:last').trigger('click'); | ||
| 323 | } | ||
| 324 | } | ||
| 320 | 325 | ||
| 321 | /** | 326 | /** |
| 322 | * onToggleLogprobsPanel is called when the user performs an action that toggles | 327 | * onToggleLogprobsPanel is called when the user performs an action that toggles |
| @@ -356,8 +361,7 @@ function onToggleLogprobsPanel() { | |||
| 356 | } | 361 | } |
| 357 | 362 | ||
| 358 | /** | 363 | /** |
| 359 | * createSwipe appends a new swipe to the target chat message with the given | 364 | * Appends a new swipe to the target chat message with the given text. |
| 360 | * text. | ||
| 361 | * @param {number} messageId - target chat message ID | 365 | * @param {number} messageId - target chat message ID |
| 362 | * @param {string} prompt - initial prompt text which will be continued | 366 | * @param {string} prompt - initial prompt text which will be continued |
| 363 | */ | 367 | */ |
| @@ -400,9 +404,10 @@ function toVisibleWhitespace(input) { | |||
| 400 | * allow text to wrap despite whitespace characters being replaced with a dot. | 404 | * allow text to wrap despite whitespace characters being replaced with a dot. |
| 401 | * @param {string} text - token text being evaluated for whitespace | 405 | * @param {string} text - token text being evaluated for whitespace |
| 402 | * @param {Element} span - target span node to be wrapped | 406 | * @param {Element} span - target span node to be wrapped |
| 403 | * @returns {Element[]} array of nodes to be appended to the DOM | 407 | * @returns {Node[]} - array of nodes to be appended to the parent element |
| 404 | */ | 408 | */ |
| 405 | function withVirtualWhitespace(text, span) { | 409 | function withVirtualWhitespace(text, span) { |
| 410 | /** @type {Node[]} */ | ||
| 406 | const result = [span]; | 411 | const result = [span]; |
| 407 | if (text.match(/^\s/)) { | 412 | if (text.match(/^\s/)) { |
| 408 | result.unshift(document.createTextNode('\u200b')); | 413 | result.unshift(document.createTextNode('\u200b')); |
| @@ -430,12 +435,16 @@ function withVirtualWhitespace(text, span) { | |||
| 430 | } | 435 | } |
| 431 | 436 | ||
| 432 | /** | 437 | /** |
| 433 | * saveLogprobsForActiveMessage receives an array of TokenLogprobs objects | 438 | * Receives the top logprobs for each token in a message and associates it with the active message. |
| 434 | * representing the top logprobs for each token in a message and associates it | 439 | * |
| 435 | * with the active message. | 440 | * Ensure the active message has been updated and rendered before calling this function |
| 441 | * or the logprobs data will be saved to the wrong message. | ||
| 442 | * | ||
| 443 | * Callers: | ||
| 444 | * - Generate:onSuccess via saveLogprobsForActiveMessage, for non-streaming text completion | ||
| 445 | * - StreamingProcessor:onFinishStreaming, for streaming text completion | ||
| 446 | * - sendOpenAIRequest, for non-streaming chat completion | ||
| 436 | * | 447 | * |
| 437 | * **Ensure the active message has been updated and rendered before calling | ||
| 438 | * this function or the logprobs data will be saved to the wrong message.** | ||
| 439 | * @param {TokenLogprobs[]} logprobs - array of logprobs data for each token | 448 | * @param {TokenLogprobs[]} logprobs - array of logprobs data for each token |
| 440 | * @param {string | null} continueFrom - for 'continue' generations, the prompt | 449 | * @param {string | null} continueFrom - for 'continue' generations, the prompt |
| 441 | */ | 450 | */ |
| @@ -445,7 +454,10 @@ export function saveLogprobsForActiveMessage(logprobs, continueFrom) { | |||
| 445 | return; | 454 | return; |
| 446 | } | 455 | } |
| 447 | 456 | ||
| 448 | convertTokenIdLogprobsToText(logprobs); | 457 | // NovelAI only returns token IDs in logprobs data; convert to text tokens in-place |
| 458 | if (getGeneratingApi() === 'novel') { | ||
| 459 | convertTokenIdLogprobsToText(logprobs); | ||
| 460 | } | ||
| 449 | 461 | ||
| 450 | const msgId = chat.length - 1; | 462 | const msgId = chat.length - 1; |
| 451 | /** @type {MessageLogprobData} */ | 463 | /** @type {MessageLogprobData} */ |
| @@ -491,17 +503,18 @@ function getActiveMessageLogprobData() { | |||
| 491 | return state.messageLogprobs.get(hash) || null; | 503 | return state.messageLogprobs.get(hash) || null; |
| 492 | } | 504 | } |
| 493 | 505 | ||
| 506 | |||
| 494 | /** | 507 | /** |
| 495 | * convertLogprobTokenIdsToText mutates the given logprobs data's topLogprobs | 508 | * convertLogprobTokenIdsToText replaces token IDs in logprobs data with text tokens, |
| 496 | * field keyed by token text instead of token ID. This is only necessary for | 509 | * for APIs that return token IDs instead of text tokens, to wit: NovelAI. |
| 497 | * APIs which only return token IDs in their logprobs data; for others this | 510 | * |
| 498 | * function is a no-op. | ||
| 499 | * @param {TokenLogprobs[]} input - logprobs data with numeric token IDs | 511 | * @param {TokenLogprobs[]} input - logprobs data with numeric token IDs |
| 500 | */ | 512 | */ |
| 501 | function convertTokenIdLogprobsToText(input) { | 513 | function convertTokenIdLogprobsToText(input) { |
| 502 | const api = getGeneratingApi(); | 514 | const api = getGeneratingApi(); |
| 503 | if (api !== 'novel') { | 515 | if (api !== 'novel') { |
| 504 | return input; | 516 | // should have been checked by the caller |
| 517 | throw new Error('convertTokenIdLogprobsToText should only be called for NovelAI'); | ||
| 505 | } | 518 | } |
| 506 | 519 | ||
| 507 | const tokenizerId = getTokenizerBestMatch(api); | 520 | const tokenizerId = getTokenizerBestMatch(api); |
| @@ -512,6 +525,7 @@ function convertTokenIdLogprobsToText(input) { | |||
| 512 | ))); | 525 | ))); |
| 513 | 526 | ||
| 514 | // Submit token IDs to tokenizer to get token text, then build ID->text map | 527 | // Submit token IDs to tokenizer to get token text, then build ID->text map |
| 528 | // noinspection JSCheckFunctionSignatures - mutates input in-place | ||
| 515 | const { chunks } = decodeTextTokens(tokenizerId, tokenIds); | 529 | const { chunks } = decodeTextTokens(tokenizerId, tokenIds); |
| 516 | const tokenIdText = new Map(tokenIds.map((id, i) => [id, chunks[i]])); | 530 | const tokenIdText = new Map(tokenIds.map((id, i) => [id, chunks[i]])); |
| 517 | 531 | ||
| @@ -526,8 +540,8 @@ function convertTokenIdLogprobsToText(input) { | |||
| 526 | 540 | ||
| 527 | export function initLogprobs() { | 541 | export function initLogprobs() { |
| 528 | const debouncedRender = debounce(renderAlternativeTokensView); | 542 | const debouncedRender = debounce(renderAlternativeTokensView); |
| 529 | $('#logprobsViewerClose').click(onToggleLogprobsPanel); | 543 | $('#logprobsViewerClose').on('click', onToggleLogprobsPanel); |
| 530 | $('#option_toggle_logprobs').click(onToggleLogprobsPanel); | 544 | $('#option_toggle_logprobs').on('click', onToggleLogprobsPanel); |
| 531 | eventSource.on(event_types.CHAT_CHANGED, debouncedRender); | 545 | eventSource.on(event_types.CHAT_CHANGED, debouncedRender); |
| 532 | eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, debouncedRender); | 546 | eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, debouncedRender); |
| 533 | eventSource.on(event_types.IMPERSONATE_READY, debouncedRender); | 547 | eventSource.on(event_types.IMPERSONATE_READY, debouncedRender); |