mirror of
https://github.com/bal-spec/sillytavern-character-memory.git
synced 2026-04-26 10:50:55 +00:00
feat: add connection type selector to Setup Wizard
Step 1 of the wizard now offers a toggle between "Dedicated API" (default) and "Connection Profile" before configuring the LLM. Previously, Connection Profile was only available in the Settings Modal after completing the wizard. - Source toggle buttons with active/inactive styling - Profile section: dropdown via CMRS.handleDropdown(), Test Connection - Step 3 summary adapts to show profile name or provider/model - Updated getting-started.md with both paths and new screenshot - Updated CHANGELOG.md with improvements and tooltip fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cb460e734f
commit
2fc514c0b2
6 changed files with 191 additions and 14 deletions
|
|
@ -2,10 +2,16 @@
|
|||
|
||||
## 2.1.9
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Setup Wizard connection type selector**: The wizard's Step 1 now lets you choose between **Dedicated API** (default) and **Connection Profile** before configuring the LLM connection. Previously, Connection Profile was only available in the Settings Modal after completing the wizard.
|
||||
- **Documentation**: Added Connection Profile docs to providers.md, getting-started.md, README.md, and architecture.md. New screenshots for Settings Modal tabs and Connection Profile creation. Documented "Protect Recent Messages" feature in managing-memories.md.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Fix message swipes counting toward extraction interval**: Swiping to a different AI response variant was incrementing the extraction counter as if a new message had been sent, causing extraction to trigger sooner than the configured interval. The `CHARACTER_MESSAGE_RENDERED` event handler now checks the `type` argument ST passes and returns early when `type === 'swipe'`. Fixes [#9](https://github.com/bal-spec/sillytavern-character-memory/issues/9).
|
||||
- **Fix Settings and Troubleshooter modals cramped on mobile**: The left-nav layout used a fixed-width sidebar that consumed nearly half the popup width on narrow screens, leaving the content panel too narrow for readable text. In phone mode, the nav now switches to horizontal tabs above the content panel, giving it the full popup width.
|
||||
- **Fix tooltip**: Injection Viewer button tooltip said "Toggle Injection Sidebar" — corrected to "Toggle Injection Viewer".
|
||||
|
||||
## 2.1.8
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,14 @@ If the wizard didn't open automatically, click the **wand icon** (✦) in the Ch
|
|||
|
||||
CharMemory needs its own LLM connection — separate from your main chat. This keeps the extraction prompt clean and uncontaminated by chat personas, jailbreaks, or system prompts.
|
||||
|
||||

|
||||

|
||||
|
||||
The wizard offers two connection types at the top:
|
||||
|
||||
- **Dedicated API** (default) — connect directly to an API provider with its own key and model
|
||||
- **Connection Profile** — reuse a saved SillyTavern connection (see [Providers → Connection Profiles](providers.md#connection-profiles))
|
||||
|
||||
### Dedicated API (default)
|
||||
|
||||
**1. Choose a provider** from the dropdown, e.g. **NanoGPT**. If you're not sure, **Pollinations** is free and requires no API key. See [Providers](providers.md) for a full list with model recommendations.
|
||||
|
||||
|
|
@ -26,10 +33,16 @@ CharMemory needs its own LLM connection — separate from your main chat. This k
|
|||
|
||||
> **Running an LLM locally?** Select **Local Server** from the provider dropdown. Enter your server URL (e.g., `http://localhost:11434/v1` for Ollama). No API key needed. See [Providers → Local Servers](providers.md#local-servers) for port numbers by backend.
|
||||
|
||||
> **Already have a connection configured in SillyTavern?** You can skip the wizard's provider setup and use a **Connection Profile** instead. After completing the wizard, open **Settings** (gear icon) → **Connection** → change **LLM Used for Extraction** to **Connection Profile** and select your saved profile. See [Providers → Connection Profiles](providers.md#connection-profiles).
|
||||
|
||||
> **If your provider is not listed** Many providers have an OpenAI compatible API endpoint. See if you can configure it that way.
|
||||
|
||||
### Connection Profile
|
||||
|
||||
If you already have a connection saved in SillyTavern's Connection Manager, click **Connection Profile** at the top to switch to that mode.
|
||||
|
||||

|
||||
|
||||
Select your profile from the dropdown and click **Test Connection**. The profile's API, model, and credentials are used automatically — no separate setup needed. See [Providers → Connection Profiles](providers.md#connection-profiles) for how to create one.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
|
|
|||
BIN
images/wizard-step1-profile.png
Normal file
BIN
images/wizard-step1-profile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 30 KiB |
157
index.js
157
index.js
|
|
@ -5040,6 +5040,9 @@ async function showSetupWizard(startStep = 1) {
|
|||
|
||||
// Step 1: LLM Connection
|
||||
const noChatWarnStyle = getCharacterName() ? 'display:none;' : '';
|
||||
const cmAvailable = isConnectionManagerAvailable();
|
||||
const currentSource = s.source || EXTRACTION_SOURCE.PROVIDER;
|
||||
const showProfile = currentSource === EXTRACTION_SOURCE.PROFILE;
|
||||
const step1Html = `
|
||||
<div class="charMemory_wizardStep" data-step="1">
|
||||
<div id="cm_wiz_noChatWarn" class="charMemory_wizardCallout charMemory_wizardCallout--warn" style="${noChatWarnStyle}">
|
||||
|
|
@ -5048,8 +5051,16 @@ async function showSetupWizard(startStep = 1) {
|
|||
</div>
|
||||
<div class="charMemory_wizardExplanation">
|
||||
<strong>CharMemory</strong> automatically extracts structured memories from your roleplay chats and stores them so your characters can recall past events.
|
||||
It needs access to an LLM to read your messages and create memory summaries. This can be any OpenAI-compatible provider.
|
||||
It needs access to an LLM to read your messages and create memory summaries.
|
||||
</div>
|
||||
<div class="charMemory_modalFieldGroup">
|
||||
<label><small>Connection type</small></label>
|
||||
<div class="charMemory_wizSourceToggle">
|
||||
<button type="button" class="menu_button charMemory_wizSourceBtn${!showProfile ? ' active' : ''}" data-source="provider">Dedicated API</button>
|
||||
<button type="button" class="menu_button charMemory_wizSourceBtn${showProfile ? ' active' : ''}" data-source="profile" ${!cmAvailable ? 'disabled title="Enable the Connection Manager extension to use saved profiles"' : ''}>Connection Profile</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cm_wiz_providerSection" style="${showProfile ? 'display:none;' : ''}">
|
||||
<div class="charMemory_modalFieldGroup">
|
||||
<label><small>Provider</small></label>
|
||||
<select id="cm_wiz_provider" class="text_pole">${providerOptions}</select>
|
||||
|
|
@ -5087,6 +5098,20 @@ async function showSetupWizard(startStep = 1) {
|
|||
</div>
|
||||
<small id="cm_wiz_modelStatus" class="charMemory_helperText" style="display:none;"></small>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cm_wiz_profileSection" style="${showProfile ? '' : 'display:none;'}">
|
||||
<div class="charMemory_modalFieldGroup">
|
||||
<label><small>Connection Profile</small></label>
|
||||
<select id="cm_wiz_profileSelect" class="text_pole">
|
||||
<option value="">Select a Connection Profile</option>
|
||||
</select>
|
||||
<small class="charMemory_helperText">Uses credentials and settings from your saved SillyTavern connection profile.</small>
|
||||
</div>
|
||||
<div class="charMemory_modalFieldGroup">
|
||||
<input type="button" id="cm_wiz_profileTest" class="menu_button charMemory_fullWidth" value="Test Connection" />
|
||||
<small id="cm_wiz_profileTestStatus" class="charMemory_helperText" style="display:none;margin-bottom:6px;"></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="charMemory_wizardNav">
|
||||
<input type="button" id="cm_wiz_next1" class="menu_button" value="Next \u2192" disabled />
|
||||
</div>
|
||||
|
|
@ -5246,6 +5271,90 @@ async function showSetupWizard(startStep = 1) {
|
|||
$wizard.find('#cm_wiz_modelRow').hide();
|
||||
}
|
||||
|
||||
// --- Source toggle (Dedicated API vs Connection Profile) ---
|
||||
$wizard.on('click', '.charMemory_wizSourceBtn', function () {
|
||||
const source = $(this).data('source');
|
||||
$wizard.find('.charMemory_wizSourceBtn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
const isProfile = source === 'profile';
|
||||
$wizard.find('#cm_wiz_providerSection').toggle(!isProfile);
|
||||
$wizard.find('#cm_wiz_profileSection').toggle(isProfile);
|
||||
|
||||
extension_settings[MODULE_NAME].source = isProfile ? EXTRACTION_SOURCE.PROFILE : EXTRACTION_SOURCE.PROVIDER;
|
||||
saveSettingsDebounced();
|
||||
|
||||
// Reset connection state for the new source
|
||||
wizConnectionOk = false;
|
||||
$wizard.find('#cm_wiz_next1').prop('disabled', true);
|
||||
$wizard.find('#cm_wiz_connectStatus, #cm_wiz_profileTestStatus').hide().text('');
|
||||
});
|
||||
|
||||
// --- Connection Profile dropdown & test ---
|
||||
if (cmAvailable) {
|
||||
try {
|
||||
const context = getContext();
|
||||
const CMRS = context.ConnectionManagerRequestService;
|
||||
if (CMRS) {
|
||||
CMRS.handleDropdown(
|
||||
'#cm_wiz_profileSelect',
|
||||
s.selectedProfileId || '',
|
||||
(profile) => {
|
||||
extension_settings[MODULE_NAME].selectedProfileId = profile?.id || '';
|
||||
saveSettingsDebounced();
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`${LOG_PREFIX} Failed to initialize wizard profile dropdown:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
$wizard.on('click', '#cm_wiz_profileTest', async function () {
|
||||
const profileId = extension_settings[MODULE_NAME].selectedProfileId;
|
||||
const $status = $wizard.find('#cm_wiz_profileTestStatus');
|
||||
const $btn = $(this);
|
||||
|
||||
if (!profileId) {
|
||||
$status.text('Select a connection profile first.').css('color', '#e74c3c').show();
|
||||
return;
|
||||
}
|
||||
|
||||
$btn.prop('disabled', true).val('Testing...');
|
||||
$status.text('Testing connection...').css('color', '').show();
|
||||
|
||||
try {
|
||||
const context = getContext();
|
||||
const CMRS = context.ConnectionManagerRequestService;
|
||||
const profile = CMRS.getProfile(profileId);
|
||||
const profileName = profile?.name || profileId;
|
||||
|
||||
const t0 = performance.now();
|
||||
const result = await CMRS.sendRequest(
|
||||
profileId,
|
||||
[{ role: 'user', content: 'Respond with exactly: CHARMEMORY_TEST_OK' }],
|
||||
20,
|
||||
{ stream: false, extractData: true },
|
||||
);
|
||||
const elapsed = ((performance.now() - t0) / 1000).toFixed(1);
|
||||
const reply = (result?.content || '').trim();
|
||||
|
||||
if (reply.includes('CHARMEMORY_TEST_OK')) {
|
||||
$status.text(`\u2714 ${profileName} responded correctly (${elapsed}s)`).css('color', '#2ecc71').show();
|
||||
} else {
|
||||
$status.html(`\u2714 ${escapeHtml(profileName)} connected (${elapsed}s). It may still work for extraction.`).css('color', '#27ae60').show();
|
||||
}
|
||||
wizConnectionOk = true;
|
||||
$wizard.find('#cm_wiz_next1').prop('disabled', false);
|
||||
} catch (err) {
|
||||
$status.text(`\u2718 ${err.message || 'Test failed'}`).css('color', '#e74c3c').show();
|
||||
wizConnectionOk = false;
|
||||
$wizard.find('#cm_wiz_next1').prop('disabled', true);
|
||||
} finally {
|
||||
$btn.prop('disabled', false).val('Test Connection');
|
||||
}
|
||||
});
|
||||
|
||||
$wizard.on('change', '#cm_wiz_provider', function () {
|
||||
const key = String($(this).val());
|
||||
extension_settings[MODULE_NAME].selectedProvider = key;
|
||||
|
|
@ -5595,13 +5704,46 @@ async function showSetupWizard(startStep = 1) {
|
|||
|
||||
// --- Step 3: Review & Go ---
|
||||
function initStep3() {
|
||||
const source = extension_settings[MODULE_NAME].source;
|
||||
const isProfile = source === EXTRACTION_SOURCE.PROFILE;
|
||||
const pk = extension_settings[MODULE_NAME].selectedProvider;
|
||||
const p = PROVIDER_PRESETS[pk] || {};
|
||||
const ps = getProviderSettings(pk);
|
||||
const modelName = ps.model || p.defaultModel || '(default)';
|
||||
const modelShort = modelName.length > 40 ? modelName.slice(0, 40) + '\u2026' : modelName;
|
||||
const interval = extension_settings[MODULE_NAME].interval || 20;
|
||||
|
||||
// Build connection summary rows based on source type
|
||||
let connectionRows;
|
||||
if (isProfile) {
|
||||
let profileName = '(none selected)';
|
||||
try {
|
||||
const context = getContext();
|
||||
const CMRS = context.ConnectionManagerRequestService;
|
||||
const profile = CMRS?.getProfile(extension_settings[MODULE_NAME].selectedProfileId);
|
||||
if (profile?.name) profileName = profile.name;
|
||||
} catch { /* ignore */ }
|
||||
connectionRows = `
|
||||
<div class="charMemory_wizardSummaryRow">
|
||||
<span class="label">Source</span>
|
||||
<span>Connection Profile</span>
|
||||
</div>
|
||||
<div class="charMemory_wizardSummaryRow">
|
||||
<span class="label">Profile</span>
|
||||
<span>${escapeHtml(profileName)}</span>
|
||||
</div>`;
|
||||
} else {
|
||||
const modelName = ps.model || p.defaultModel || '(default)';
|
||||
const modelShort = modelName.length > 40 ? modelName.slice(0, 40) + '\u2026' : modelName;
|
||||
connectionRows = `
|
||||
<div class="charMemory_wizardSummaryRow">
|
||||
<span class="label">Provider</span>
|
||||
<span>${escapeHtml(p.name || pk)}</span>
|
||||
</div>
|
||||
<div class="charMemory_wizardSummaryRow">
|
||||
<span class="label">Model</span>
|
||||
<span>${escapeHtml(modelShort)}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// VS summary from extension_settings.vectors
|
||||
// Check DOM for VS extension UI to avoid false-positive when VS is disabled.
|
||||
const vecSettings = extension_settings.vectors;
|
||||
|
|
@ -5621,14 +5763,7 @@ async function showSetupWizard(startStep = 1) {
|
|||
}
|
||||
|
||||
$wizard.find('#cm_wiz_summary').html(`
|
||||
<div class="charMemory_wizardSummaryRow">
|
||||
<span class="label">Provider</span>
|
||||
<span>${escapeHtml(p.name || pk)}</span>
|
||||
</div>
|
||||
<div class="charMemory_wizardSummaryRow">
|
||||
<span class="label">Model</span>
|
||||
<span>${escapeHtml(modelShort)}</span>
|
||||
</div>
|
||||
${connectionRows}
|
||||
<div class="charMemory_wizardSummaryRow">
|
||||
<span class="label">Connection</span>
|
||||
<span>${wizConnectionOk ? '<span style="color:#4a4;">\u2714 Connected</span>' : '<span style="color:#e8a33d;">\u26A0 Not tested</span>'}</span>
|
||||
|
|
|
|||
23
style.css
23
style.css
|
|
@ -1602,6 +1602,29 @@ body.charMemory-phone-mode .charMemory_modalNavItem.active {
|
|||
border-left: 3px solid var(--SmartThemeQuoteColor, #888);
|
||||
}
|
||||
|
||||
.charMemory_wizSourceToggle {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.charMemory_wizSourceBtn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
opacity: 0.6;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
transition: opacity 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.charMemory_wizSourceBtn.active {
|
||||
opacity: 1;
|
||||
border-color: var(--SmartThemeQuoteColor);
|
||||
}
|
||||
|
||||
.charMemory_wizSourceBtn:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.charMemory_wizConnectRow {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue