| 735 | 736 | } |
| 736 | 737 | } |
| 737 | 738 | |
| 738 | | -/** |
| 739 | +// Warn at most once per session if a summary connection profile is selected but cannot be |
| 739 | | - * Resolve the maximum response length (in tokens) to request from a connection profile. |
| 740 | +// applied because there is no active connection profile to restore afterward. |
| 740 | | - * Uses the configured override when it is a positive number; otherwise falls back to a |
| 741 | +let summaryProfileWarnedNoBaseProfile = false; |
| 741 | | - * sensible default cap for a summary. |
| 742 | | - * @returns {number} Maximum tokens for the summary response |
| 743 | | - */ |
| 744 | | -function getSummaryProfileMaxTokens() { |
| 745 | | - const override = Number(extension_settings.memory.overrideResponseLength); |
| 746 | | - return override > 0 ? override : 1024; |
| 747 | | -} |
| 748 | 742 | |
| 749 | 743 | /** |
| 750 | 744 | * GenerateRuns a summarycallback usingwith a dedicatedspecific Connection Manager profile instead of thetemporarily active, model.then restores |
| 751 | | - * Assembles the chat transcript (via getRawSummaryPrompt) together with the summary instruction |
| 745 | + * the previously active profile. This lets the summary be generated by the chosen LLM using the |
| 752 | | - * so the chosen model actually receives the chat to summarize. Honors the configured summary |
| 746 | + * full summarization pipeline (generateQuietPrompt/generateRaw) under that connection, instead of |
| 753 | 747 | * prompta rolecontext-free byConnection deliveringManager therequest instructionthat assome aproviders SYSTEMrun orwith USERthe messagewrong model. |
| 754 | | - * @param {object} context ST context |
| 748 | + * |
| 755 | | - * @param {string} profileId Connection Manager profile id |
| 749 | + * The switch is only performed when a real connection profile is currently active (so it can be |
| 756 | | - * @param {string} prompt Summary instruction |
| 750 | + * reliably restored). When none is active — i.e. the user drives the API panel manually — |
| 757 | | - * @returns {Promise<{summary: string, index: number}>} Generated summary and the index it covers up to |
| 751 | + * switching to a profile could not be undone without clobbering those manual settings, so we leave |
| 752 | + * the active model in place and warn once. |
| 753 | + * |
| 754 | + * @param {string} targetProfileId Profile to activate for the duration of the callback. |
| 755 | + * @param {() => Promise<any>} callback Work to run while the target profile is active. |
| 756 | + * @returns {Promise<any>} The callback's result. |
| 758 | 757 | */ |
| 759 | 758 | async function summarizeWithConnectionProfilewithConnectionProfile(context, profileIdtargetProfileId, promptcallback) { |
| 760 | | - const { rawPrompt, lastUsedIndex } = await getRawSummaryPrompt(context, prompt); |
| 759 | + const select = /** @type {HTMLSelectElement} */ (document.getElementById('connection_profiles')); |
| 761 | | - |
| 760 | + const connectionManager = extension_settings.connectionManager; |
| 762 | | - if (lastUsedIndex === null || lastUsedIndex === -1) { |
| 761 | + const currentProfileId = connectionManager?.selectedProfile; |
| 763 | | - // Nothing to summarize; mirror the raw builder's guard. |
| 762 | + |
| 764 | | - return { summary: '', index: lastUsedIndex }; |
| 763 | + const canSwitch = !!select |
| 764 | + && !!connectionManager |
| 765 | + && Array.isArray(connectionManager.profiles) |
| 766 | + && connectionManager.profiles.some(p => p.id === targetProfileId) |
| 767 | + && Array.from(select.options).some(o => o.value === targetProfileId) |
| 768 | + && !!currentProfileId // a real profile is active, so it can be restored afterwards |
| 769 | + && currentProfileId !== targetProfileId; |
| 770 | + |
| 771 | + if (!canSwitch) { |
| 772 | + // Selected but no base profile to restore from -> use the active model and warn once. |
| 773 | + if (targetProfileId && !currentProfileId && !summaryProfileWarnedNoBaseProfile) { |
| 774 | + summaryProfileWarnedNoBaseProfile = true; |
| 775 | + toastr.info('The summary connection profile is only applied while a connection profile is active (so the original can be restored). Using the current model.', 'Summarize'); |
| 776 | + } |
| 777 | + return await callback(); |
| 765 | 778 | } |
| 766 | 779 | |
| 767 | 780 | const useUserRoleswitchToProfile = getSummaryPromptRoleasync (profileId) ===> extension_prompt_roles.USER;{ |
| 768 | | - // sendRequest accepts an array of chat-completion messages, so deliver the instruction |
| 781 | + const loaded = new Promise(resolve => eventSource.once(event_types.CONNECTION_PROFILE_LOADED, resolve)); |
| 769 | | - // as a separate SYSTEM or USER message (per the configured role) with the transcript as |
| 782 | + const timeout = new Promise(resolve => setTimeout(resolve, 10000)); |
| 770 | | - // a USER message. For text-completion profiles these are concatenated downstream. |
| 783 | + const index = Array.from(select.options).findIndex(o => o.value === profileId); |
| 771 | | - const messages = useUserRole |
| 784 | + select.selectedIndex = index >= 0 ? index : 0; |
| 772 | | - ? [{ role: 'user', content: [prompt, rawPrompt].filter(x => x).join('\n\n') }] |
| 785 | + select.dispatchEvent(new Event('change')); |
| 773 | | - : [{ role: 'system', content: prompt }, { role: 'user', content: rawPrompt }]; |
| 786 | + // Wait for the profile's commands to finish applying (don't hang forever if the event never fires). |
| 774 | | - const filteredMessages = messages.filter(m => m.content); |
| 787 | + await Promise.race([loaded, timeout]); |
| 775 | | - |
| 788 | + // Applying a profile reconnects the API asynchronously; generating before it is |
| 776 | | - const data = await ConnectionManagerRequestService.sendRequest(profileId, filteredMessages, getSummaryProfileMaxTokens()); |
| 789 | + // re-established fails instantly, so wait for the connection to come back up |
| 777 | | - // Non-streaming requests resolve to ExtractedData ({ content, reasoning }); extract the plain text. |
| 790 | + // (mirrors the built-in /profile command). rejectOnTimeout:false -> proceed anyway after the timeout. |
| 778 | | - const text = typeof data === 'object' && data !== null ? String(data.content ?? '') : ''; |
| 791 | + await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100, { rejectOnTimeout: false }); |
| 779 | | - return { summary: removeReasoningFromString(text), index: lastUsedIndex }; |
| 792 | + }; |
| 793 | + |
| 794 | + await switchToProfile(targetProfileId); |
| 795 | + try { |
| 796 | + return await callback(); |
| 797 | + } finally { |
| 798 | + try { |
| 799 | + await switchToProfile(currentProfileId); |
| 800 | + } catch (err) { |
| 801 | + console.error('Summarize: failed to restore the previous connection profile after summarization', err); |
| 802 | + } |
| 803 | + } |
| 780 | 804 | } |
| 781 | 805 | |
| 782 | 806 | async function summarizeChatMain(context, force, skipWIAN) { |