| 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 | * Generate a summary using a dedicated Connection Manager profile instead of the active model. | 744 | * Runs a callback with a specific Connection Manager profile temporarily active, 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 | * prompt role by delivering the instruction as a SYSTEM or USER message. | 747 | * a context-free Connection Manager request that some providers run with the wrong 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 | async function summarizeWithConnectionProfile(context, profileId, prompt) { | 758 | async function withConnectionProfile(targetProfileId, callback) { |
| 760 | const { rawPrompt, lastUsedIndex } = await getRawSummaryPrompt(context, prompt); | 759 | const select = /** @type {HTMLSelectElement} */ (document.getElementById('connection_profiles')); |
| | 760 | const connectionManager = extension_settings.connectionManager; |
| | 761 | const currentProfileId = connectionManager?.selectedProfile; |
| | 762 | |
| | 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(); |
| | 778 | } |
| | 779 | |
| | 780 | const switchToProfile = async (profileId) => { |
| | 781 | const loaded = new Promise(resolve => eventSource.once(event_types.CONNECTION_PROFILE_LOADED, resolve)); |
| | 782 | const timeout = new Promise(resolve => setTimeout(resolve, 10000)); |
| | 783 | const index = Array.from(select.options).findIndex(o => o.value === profileId); |
| | 784 | select.selectedIndex = index >= 0 ? index : 0; |
| | 785 | select.dispatchEvent(new Event('change')); |
| | 786 | // Wait for the profile's commands to finish applying (don't hang forever if the event never fires). |
| | 787 | await Promise.race([loaded, timeout]); |
| | 788 | // Applying a profile reconnects the API asynchronously; generating before it is |
| | 789 | // re-established fails instantly, so wait for the connection to come back up |
| | 790 | // (mirrors the built-in /profile command). rejectOnTimeout:false -> proceed anyway after the timeout. |
| | 791 | await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100, { rejectOnTimeout: false }); |
| | 792 | }; |
| 761 | | 793 | |
| 762 | if (lastUsedIndex === null || lastUsedIndex === -1) { | 794 | await switchToProfile(targetProfileId); |
| 763 | // Nothing to summarize; mirror the raw builder's guard. | 795 | try { |
| 764 | return { summary: '', index: lastUsedIndex }; | 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 | } |
| 765 | } | 803 | } |
| 766 | | | |
| 767 | const useUserRole = getSummaryPromptRole() === extension_prompt_roles.USER; | | |
| 768 | // sendRequest accepts an array of chat-completion messages, so deliver the instruction | | |
| 769 | // as a separate SYSTEM or USER message (per the configured role) with the transcript as | | |
| 770 | // a USER message. For text-completion profiles these are concatenated downstream. | | |
| 771 | const messages = useUserRole | | |
| 772 | ? [{ role: 'user', content: [prompt, rawPrompt].filter(x => x).join('\n\n') }] | | |
| 773 | : [{ role: 'system', content: prompt }, { role: 'user', content: rawPrompt }]; | | |
| 774 | const filteredMessages = messages.filter(m => m.content); | | |
| 775 | | | |
| 776 | const data = await ConnectionManagerRequestService.sendRequest(profileId, filteredMessages, getSummaryProfileMaxTokens()); | | |
| 777 | // Non-streaming requests resolve to ExtractedData ({ content, reasoning }); extract the plain text. | | |
| 778 | const text = typeof data === 'object' && data !== null ? String(data.content ?? '') : ''; | | |
| 779 | return { summary: removeReasoningFromString(text), index: lastUsedIndex }; | | |
| 780 | } | 804 | } |
| 781 | | 805 | |
| 782 | async function summarizeChatMain(context, force, skipWIAN) { | 806 | async function summarizeChatMain(context, force, skipWIAN) { |