|
318 | 318 | outline: 1px solid var(--color-base-50);
|
319 | 319 | outline-offset: 1px;
|
320 | 320 | }
|
| 321 | +#prompt-container #prompt-overlay > .preview { |
| 322 | + color: var(--color-dark); |
| 323 | + opacity: 40%; |
| 324 | +} |
| 325 | +html.serif-dark #prompt-container #prompt-overlay > .preview, |
| 326 | +html.monospace-dark #prompt-container #prompt-overlay > .preview, |
| 327 | +html.nockoffAI #prompt-container #prompt-overlay > .preview { |
| 328 | + color: var(--color-light); |
| 329 | + opacity: 30%; |
| 330 | +} |
| 331 | +#prompt-container #prompt-overlay > .nudge { |
| 332 | + outline: 1px solid var(--color-dark); |
| 333 | + margin-left: 5px; |
| 334 | + padding-bottom: 2px; |
| 335 | + padding: 0 4px; |
| 336 | + font-size: calc(11px * var(--font-size-multiplier)); |
| 337 | + background-color: transparent; |
| 338 | + border-radius: 3px; |
| 339 | + position: relative; |
| 340 | + top: -2px; |
| 341 | +} |
| 342 | +html.serif-dark #prompt-container #prompt-overlay > .nudge, |
| 343 | +html.monospace-dark #prompt-container #prompt-overlay > .nudge, |
| 344 | +html.nockoffAI #prompt-container #prompt-overlay > .nudge { |
| 345 | + border: 1px solid var(--color-light); |
| 346 | +} |
321 | 347 |
|
322 | 348 | #probs {
|
323 | 349 | position: absolute;
|
|
5488 | 5514 | grammar: '',
|
5489 | 5515 | chatAPI: false,
|
5490 | 5516 | tokenStreaming: true,
|
| 5517 | + promptPreview: false, |
| 5518 | + promptPreviewTokens: 20, |
5491 | 5519 | };
|
5492 | 5520 |
|
5493 | 5521 | function joinPrompt(prompt) {
|
|
5690 | 5718 | const sessionReconnectTimer = useRef();
|
5691 | 5719 | const useScrollSmoothing = useRef(true);
|
5692 | 5720 | const hordeTaskId = useRef();
|
| 5721 | + const promptPreviewElement = useRef(); |
5693 | 5722 | const [templates, setTemplates] = useDBTemplates(defaultPresets.instructTemplates);
|
5694 | 5723 | const [templateReplacements, setTemplateReplacements] = useState(false);
|
5695 | 5724 | const [templatesImport, setTemplatesImport] = useState(false);
|
|
5775 | 5804 | const [hordeProcessing, setHordeProcessing] = useState(false);
|
5776 | 5805 | const [useChatAPI, setUseChatAPI] = useSessionState('chatAPI', defaultPresets.chatAPI);
|
5777 | 5806 | const [useTokenStreaming, setUseTokenStreaming] = useSessionState('tokenStreaming', defaultPresets.tokenStreaming);
|
| 5807 | + const [promptPreviewChunks, setPromptPreviewChunks] = useState([]); |
| 5808 | + const [promptPreviewReroll, setPromptPreviewReroll] = useState(0); |
| 5809 | + const [showPromptPreview, setShowPromptPreview] = useSessionState('promptPreview', defaultPresets.promptPreview); |
| 5810 | + const [promptPreviewTokens, setPromptPreviewTokens] = useSessionState('promptPreviewTokens', defaultPresets.promptPreviewTokens); |
5778 | 5811 |
|
5779 | 5812 | function replacePlaceholders(string,placeholders) {
|
5780 | 5813 | // give placeholders as json object
|
|
6176 | 6209 | return replacePlaceholders(additionalContextPrompt, templateReplacements);
|
6177 | 6210 | }, [additionalContextPrompt, templates, selectedTemplate]);
|
6178 | 6211 |
|
| 6212 | + // predicts the prompt preview |
| 6213 | + useEffect(() => { |
| 6214 | + if (fimPromptInfo !== undefined || cancel || !showPromptPreview) |
| 6215 | + return; |
| 6216 | + |
| 6217 | + setPromptPreviewChunks([]); |
| 6218 | + |
| 6219 | + const ac = new AbortController(); |
| 6220 | + const to = setTimeout(async () => { |
| 6221 | + const customParams = { |
| 6222 | + n_predict: promptPreviewTokens |
| 6223 | + }; |
| 6224 | + |
| 6225 | + const predicted = await predict(finalPromptText, promptChunks.length, (chunk) => { |
| 6226 | + setPromptPreviewChunks((c) => [...c, chunk]); |
| 6227 | + return true; |
| 6228 | + }, ac, customParams); |
| 6229 | + }, 500); |
| 6230 | + |
| 6231 | + ac.signal.addEventListener('abort', () => clearTimeout(to)); |
| 6232 | + return () => ac.abort(); |
| 6233 | + }, [finalPromptText, showPromptPreview, promptPreviewReroll, promptPreviewTokens, cancel, endpoint, endpointAPI, endpointAPIKey]); |
| 6234 | + |
| 6235 | + const promptPreviewText = useMemo(() => joinPrompt(promptPreviewChunks), [promptPreviewChunks]); |
| 6236 | + |
6179 | 6237 | // predicts one {fill} placeholder
|
6180 | 6238 | async function fillPredict() {
|
6181 | 6239 | if (fimPromptInfo === undefined)
|
|
6270 | 6328 | return messages;
|
6271 | 6329 | }
|
6272 | 6330 |
|
6273 |
| - async function predict(prompt = finalPromptText, chunkCount = promptChunks.length, callback = undefined) { |
6274 |
| - if (cancel) { |
| 6331 | + async function predict(prompt = finalPromptText, chunkCount = promptChunks.length, callback = undefined, abortController = undefined, customParams = {}) { |
| 6332 | + if (!abortController && cancel) { |
6275 | 6333 | cancel?.();
|
6276 | 6334 |
|
6277 | 6335 | // llama.cpp server sometimes generates gibberish if we stop and
|
|
6287 | 6345 | if (!callback && !restartedPredict && await fillPredict())
|
6288 | 6346 | return true;
|
6289 | 6347 |
|
6290 |
| - const ac = new AbortController(); |
6291 |
| - const cancelThis = () => { |
6292 |
| - abortCompletion({ |
6293 |
| - endpoint, |
6294 |
| - endpointAPI, |
6295 |
| - ...(endpointAPI == API_AI_HORDE ? { hordeTaskId: hordeTaskId.current } : {}), |
6296 |
| - ...(isMikupadEndpoint ? { proxyEndpoint: sessionStorage.proxyEndpoint } : {}) |
6297 |
| - }); |
6298 |
| - ac.abort(); |
6299 |
| - }; |
6300 |
| - setCancel(() => cancelThis); |
| 6348 | + let ac; |
| 6349 | + let cancelThis; |
| 6350 | + if (!abortController) { |
| 6351 | + ac = new AbortController(); |
| 6352 | + cancelThis = () => { |
| 6353 | + abortCompletion({ |
| 6354 | + endpoint, |
| 6355 | + endpointAPI, |
| 6356 | + ...(endpointAPI == API_AI_HORDE ? { hordeTaskId: hordeTaskId.current } : {}), |
| 6357 | + ...(isMikupadEndpoint ? { proxyEndpoint: sessionStorage.proxyEndpoint } : {}) |
| 6358 | + }); |
| 6359 | + ac.abort(); |
| 6360 | + }; |
| 6361 | + setCancel(() => cancelThis); |
| 6362 | + } else { |
| 6363 | + ac = abortController; |
| 6364 | + cancelThis = () => { |
| 6365 | + abortCompletion({ |
| 6366 | + endpoint, |
| 6367 | + endpointAPI, |
| 6368 | + ...(endpointAPI == API_AI_HORDE ? { hordeTaskId: hordeTaskId.current } : {}), |
| 6369 | + ...(isMikupadEndpoint ? { proxyEndpoint: sessionStorage.proxyEndpoint } : {}) |
| 6370 | + }); |
| 6371 | + }; |
| 6372 | + ac.signal.addEventListener('abort', cancelThis); |
| 6373 | + } |
6301 | 6374 | setLastError(undefined);
|
6302 | 6375 |
|
6303 | 6376 | let predictCount = 0;
|
|
6425 | 6498 | stream: useTokenStreaming,
|
6426 | 6499 | ...(JSON.parse(stoppingStrings).length ? { stop: JSON.parse(stoppingStrings) } : {}),
|
6427 | 6500 | signal: ac.signal,
|
6428 |
| - ...(isMikupadEndpoint ? { proxyEndpoint: sessionStorage.proxyEndpoint } : {}) |
| 6501 | + ...(isMikupadEndpoint ? { proxyEndpoint: sessionStorage.proxyEndpoint } : {}), |
| 6502 | + ...customParams |
6429 | 6503 | })) {
|
6430 | 6504 | ac.signal.throwIfAborted();
|
6431 | 6505 | if (chunk.stopping_word)
|
|
6478 | 6552 | return false;
|
6479 | 6553 | } finally {
|
6480 | 6554 | setCancel(c => c === cancelThis ? null : c);
|
| 6555 | + if (abortController) |
| 6556 | + ac.signal.removeEventListener('abort', cancelThis); |
6481 | 6557 | if (!callback) {
|
6482 | 6558 | if (predictCount === 0)
|
6483 | 6559 | undoStack.current.pop();
|
|
6681 | 6757 | }, [promptText]);
|
6682 | 6758 |
|
6683 | 6759 | useLayoutEffect(() => {
|
6684 |
| - if (cancel) |
| 6760 | + const elem = promptArea.current; |
| 6761 | + const previewElem = promptPreviewElement.current; |
| 6762 | + if (!elem || !previewElem) |
| 6763 | + return; |
| 6764 | + const oldHeight = elem.scrollHeight; |
| 6765 | + const atBottom = (elem.scrollTarget ?? elem.scrollTop) + elem.clientHeight + 1 > oldHeight; |
| 6766 | + previewElem.textContent = promptPreviewText; |
| 6767 | + elem.style.paddingBottom = previewElem.offsetHeight + 'px'; |
| 6768 | + requestAnimationFrame(() => { |
| 6769 | + const newHeight = elem.scrollHeight; |
| 6770 | + if (atBottom && oldHeight !== newHeight) { |
| 6771 | + if (elem.scrollHeight - (elem.scrollTop + elem.clientHeight + 1) >= 100) { |
| 6772 | + // smooth scroll isn't keeping up with prediction speed =( |
| 6773 | + useScrollSmoothing.current = false; |
| 6774 | + } |
| 6775 | + elem.scrollTarget = newHeight - elem.clientHeight; |
| 6776 | + elem.scrollTo({ |
| 6777 | + top: newHeight - elem.clientHeight, |
| 6778 | + behavior: useScrollSmoothing.current ? 'smooth' : 'instant', |
| 6779 | + }); |
| 6780 | + } |
| 6781 | + }); |
| 6782 | + }, [promptPreviewText]); |
| 6783 | + |
| 6784 | + useLayoutEffect(() => { |
| 6785 | + if (cancel || promptPreviewText) |
6685 | 6786 | return;
|
6686 | 6787 | promptArea.current.scrollTarget = undefined;
|
6687 | 6788 | promptArea.current.scrollTop = savedScrollTop;
|
|
6755 | 6856 | predict();
|
6756 | 6857 | break;
|
6757 | 6858 | case 'false:false:false:Escape':
|
6758 |
| - cancel(); |
| 6859 | + if (cancel) { |
| 6860 | + cancel(); |
| 6861 | + } else if (promptPreviewText.length !== 0) { |
| 6862 | + setPromptPreviewReroll((r) => r + 1); |
| 6863 | + } |
| 6864 | + break; |
| 6865 | + case 'false:false:false:Tab': |
| 6866 | + if (promptPreviewText.length === 0) |
| 6867 | + break; |
| 6868 | + |
| 6869 | + setPromptChunks(p => [ |
| 6870 | + ...p, |
| 6871 | + ...promptPreviewChunks |
| 6872 | + ]); |
| 6873 | + setTokens(t => t + promptPreviewChunks.length); |
| 6874 | + setPromptPreviewChunks([]); |
6759 | 6875 | break;
|
6760 | 6876 | case 'false:true:false:r':
|
6761 | 6877 | case 'false:false:true:r':
|
|
7090 | 7206 | redoStack.current = [];
|
7091 | 7207 | undoStack.current = [];
|
7092 | 7208 | setUndoHovered(false);
|
| 7209 | + setPromptPreviewChunks([]); |
7093 | 7210 | setTitleToSession();
|
7094 | 7211 | }
|
7095 | 7212 | function onSessionError() {
|
|
7194 | 7311 | id="prompt-area"
|
7195 | 7312 | onInput=${onInput}
|
7196 | 7313 | onScroll=${onScroll}
|
7197 |
| - onContextMenu=${onContextMenu}/> |
7198 |
| - <div ref=${promptOverlay} id="prompt-overlay" aria-hidden> |
| 7314 | + onContextMenu=${onContextMenu} |
| 7315 | + ...${showPromptPreview && { style: { 'padding-bottom': promptPreviewElement.current?.offsetHeight ?? '0px' } }}/> |
| 7316 | + <div |
| 7317 | + ref=${promptOverlay} |
| 7318 | + id="prompt-overlay" |
| 7319 | + aria-hidden |
| 7320 | + ...${showPromptPreview && { style: { 'padding-bottom': promptPreviewElement.current?.offsetHeight ?? '0px' } }}> |
7199 | 7321 | ${tokenHighlightMode !== -1 ? html`
|
7200 | 7322 | ${promptChunks.map((chunk, i) => {
|
7201 | 7323 | const getRatioColor = (ratio) => {
|
|
7228 | 7350 | data-promptchunk=${i}
|
7229 | 7351 | style=${bgColor ? { '--bg-color': bgColor } : {}}
|
7230 | 7352 | className=${`${(tokenHighlightMode === 1 && !isCurrent) || chunk.type === 'user' ? 'user' : 'machine'} ${isCurrent ? 'current' : ''} ${isNextUndo ? 'erase' : ''}`}>
|
7231 |
| - ${(chunk.content === '\n' ? ' \n' : chunk.content) + (i === promptChunks.length - 1 && chunk.content.endsWith('\n') ? '\u00a0' : '')} |
| 7353 | + ${(chunk.content === '\n' ? ' \n' : chunk.content) + (i === promptChunks.length - 1 && chunk.content.endsWith('\n') && promptPreviewText?.length === 0 ? '\u00a0' : '')} |
7232 | 7354 | </span>`;
|
7233 | 7355 | })}` : null}
|
| 7356 | + ${(showPromptPreview && promptPreviewText?.length) ? html` |
| 7357 | + <span ref=${promptPreviewElement} className="preview"></span> |
| 7358 | + <span class="preview nudge">Tab</span>` : null} |
7234 | 7359 | </div>
|
7235 | 7360 | <${SearchAndReplaceWidget}
|
7236 | 7361 | isOpen=${modalState.searchAndReplace}
|
|
7320 | 7445 | title="If enabled, the chat API endpoint will be used, and the prompt will be split into chat messages based on the delimiters defined in the selected instruct template."
|
7321 | 7446 | disabled=${!!cancel} value=${useChatAPI} onValueChange=${setUseChatAPI}/>`}
|
7322 | 7447 | <${Checkbox} label="Token Streaming"
|
7323 |
| - disabled=${!!cancel} value=${useTokenStreaming} onValueChange=${setUseTokenStreaming}/>`} |
| 7448 | + disabled=${!!cancel} value=${useTokenStreaming} onValueChange=${setUseTokenStreaming}/> |
| 7449 | + <${Checkbox} label="Prediction Preview" |
| 7450 | + disabled=${!!cancel} value=${showPromptPreview} onValueChange=${setShowPromptPreview}/> |
| 7451 | + ${showPromptPreview && html` |
| 7452 | + <${InputBox} label="Max Preview Tokens" type="text" inputmode="numeric" |
| 7453 | + readOnly=${!!cancel} value=${promptPreviewTokens} onValueChange=${setPromptPreviewTokens}/>`}`} |
7324 | 7454 | <div className="buttons instructTemplateSidebar">
|
7325 | 7455 | <${SelectBoxTemplate}
|
7326 | 7456 | label="Instruct Template"
|
|
0 commit comments