Files
SillyTavern/public/css/streaming-display.css
T
Wolfsblvt 64c96e895c Add Streaming Display Utility and New Generation Slash Commands (/genstream, /reasoning-format) (#5438)
* Add StreamingDisplay class for live LLM generation output with floating toast panel

- Add StreamingDisplay class to show streaming reasoning and content in a floating toast panel
- Extract createModelIcon() helper from insertSVGIcon() for reusable API/model icon creation
- StreamingDisplay automatically appends inside topmost open dialog (same pattern as fixToastrForDialogs)
- Add CSS with fade-in animation, pulsating activity indicator, and separate reasoning/content sections
- Support optional model icon in header

* Add ConnectionManagerRequestService.getProfileIcon() method for retrieving profile API icons

- Add static getProfileIcon() method to ConnectionManagerRequestService
- Returns HTMLImageElement created via createModelIcon() for a given profile's API/model
- Accepts optional profileId parameter, defaults to currently selected profile
- Returns null if Connection Manager is disabled, profile not found, or profile has no API
- Import createModelIcon from script.js

* Use animation_duration directly in hide() and CSS transition instead of constant

- Remove ANIMATION_DURATION_MS constant and use animation_duration directly in hide() method
- Replace hardcoded 0.3s CSS transitions with CSS variable var(--animation-duration, 125ms)
- Read animation_duration value inline in hide() for accurate timing

* Add /genstream slash command with live streaming display and reasoning support

- Add /genstream slash command that generates text via Connection Manager with live streaming UI
- Add formatReasoning() helper function (inverse of parseReasoningFromString) to format reasoning/content into template-wrapped strings
- Add connectionProfiles enum provider for profile selection in slash commands
- StreamingDisplay: add delay parameter to hide() method (default 1000ms) to show final result before dismiss

* Add /reasoning-format slash command to format reasoning and content into template-wrapped strings

- Add /reasoning-format (alias: /format-reasoning) slash command that wraps reasoning/content using Reasoning Formatting settings
- Accept required 'reasoning' named argument and optional unnamed 'content' argument
- Validate that prefix/suffix are configured before formatting
- Return formatted string via formatReasoning() helper for use with /reasoning-parse
- Show warning toasts if prefix/suffix missing

* Rename /genstream command to /profile-genstream and move to appropriate module

* Apply messageFormatting to StreamingDisplay reasoning and content text for proper rendering

- Import messageFormatting from script.js
- Replace textContent with innerHTML using messageFormatting() in updateReasoning() and updateText()
- Pass isSystem=true for reasoning, isSystem=false for content to match formatting expectations
- Update css to utilize pre-formatted paragraphs correctly

* Strip auto-added quotes from <q> tags in StreamingDisplay and add 'mes_text' class for consistent chat message formatting

- Add CSS rules to remove browser-default quotes from <q> tags in reasoning and content sections
- Add 'mes_text' class to textContent div to match chat message formatting behavior
- Prevents double quotes when messageFormatting already adds them via <q> tags

* Add minimize/close buttons and complete state to StreamingDisplay with configurable auto-hide

- Add minimize button to collapse/restore content sections while keeping header visible
- Add close button to manually dismiss display (generation continues in background)
- Replace CSS pseudo-element with explicit LED indicator element for better state control
- Add complete() method to mark generation done: changes LED from pulsing orange to solid green
- Add configurable auto-hide delay after completion

* Add stop button to StreamingDisplay with abort support and onStop/onComplete closures for /profile-genstream

- Add stop button to StreamingDisplay when onStop handler is provided
- Add markStopped() method with solid red LED state indicator
- Add AbortController integration to /profile-genstream for request cancellation
- Add onStop and onComplete closure arguments to /profile-genstream command
- Update complete() method signature to use options object with label and delay
- Disable stop button immediately

* Position StreamingDisplay above bottom form block using CSS variable with fallback

- Change bottom positioning from fixed 20px to dynamic calculation
- Use max() to position above --bottomFormBlockSize + 5px or minimum 20px
- Ensures StreamingDisplay doesn't overlap with bottom UI elements

* Rename /profile-genstream arguments for clarity: label→generating, completedLabel→completed, hideDelay→delay

- Rename `label` argument to `generating` to better reflect its purpose as the in-progress state label
- Rename `completedLabel` to `completed` for consistency and brevity
- Rename `hideDelay` to `delay` for simpler naming
- Update all internal references and variable names to match new argument names
- Update argument descriptions and default values accordingly

* Remove variable resolution from /profile-genstream arguments: system, length, and delay

- Remove ARGUMENT_TYPE.VARIABLE_NAME from typeList for system, length, and delay arguments
- Replace resolveVariable() calls with direct argument access for system, length, and delay
- Simplify type checking to use typeof directly on args properties
- Maintain existing default values and validation logic

* Add warning toast and early return when connection profile not found in /profile-genstream

- Display toastr warning when fuzzy search fails to find matching profile
- Return empty string to prevent execution with invalid profile
- Improves user feedback for incorrect profile names or IDs

* Extract buildResultText() helper in /profile-genstream to return partial results when stopped

- Add buildResultText() helper function to centralize result formatting logic
- Return partial generated text when user stops generation instead of empty string
- Reuse buildResultText() for both stopped and completed states
- Maintains consistent reasoning formatting in both cases

* fix lint

* Update documentation to reflect argument name change from hideDelay to delay

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2026-04-15 21:38:13 +03:00

237 lines
6.0 KiB
CSS

/* ─────────────────────────────────────────────────────────────────────────────
Streaming Display — floating toast panel for live LLM generation output.
Shows reasoning (thinking) and content as they stream in.
Used by extensions that leverage ConnectionManagerRequestService streaming.
───────────────────────────────────────────────────────────────────────────── */
.streaming-display {
position: fixed;
bottom: max(calc(var(--bottomFormBlockSize) + 5px), 20px);
right: 20px;
width: min(550px, calc(100vw - 40px));
max-height: 70vh;
background: var(--SmartThemeBlurTintColor);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 10px;
padding: 14px;
z-index: 9000;
display: flex;
flex-direction: column;
gap: 10px;
opacity: 0;
transform: translateY(20px);
transition: opacity var(--animation-duration, 125ms) ease, transform var(--animation-duration, 125ms) ease;
overflow: hidden;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(12px);
}
.streaming-display-visible {
opacity: 1;
transform: translateY(0);
}
/* Header label with animated activity indicator */
.streaming-display-label {
font-weight: 600;
font-size: 0.95em;
color: var(--SmartThemeBodyColor);
display: flex;
align-items: center;
gap: 8px;
user-select: none;
}
/* LED status indicator - pulsing while streaming */
.streaming-display-led {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: rgb(225, 138, 36);
animation: streaming-display-pulse 1.5s ease-in-out infinite;
flex-shrink: 0;
}
/* Completed state: solid green LED */
.streaming-display-complete .streaming-display-led {
background: #4caf50;
animation: none;
opacity: 1;
box-shadow: 0 0 8px rgba(76, 175, 80, 0.6);
}
/* Stopped state: solid red LED */
.streaming-display-stopped .streaming-display-led {
background: #f44336;
animation: none;
opacity: 1;
box-shadow: 0 0 8px rgba(244, 67, 54, 0.6);
}
@keyframes streaming-display-pulse {
0%, 100% { opacity: 0.4; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.1); }
}
/* Label text takes available space */
.streaming-display-label-text {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Window control buttons container */
.streaming-display-controls {
display: flex;
align-items: center;
gap: 4px;
margin-left: auto;
flex-shrink: 0;
}
/* Window control buttons */
.streaming-display-btn {
width: 22px;
height: 22px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--SmartThemeBodyColor);
font-size: 14px;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.15s ease, background-color 0.15s ease;
}
.streaming-display-btn:hover {
opacity: 1;
background-color: rgba(255, 255, 255, 0.1);
}
.streaming-display-btn-close:hover {
background-color: rgba(244, 67, 54, 0.2);
}
.streaming-display-btn-stop {
font-size: 10px;
}
.streaming-display-btn-stop:hover {
background-color: rgba(244, 150, 36, 0.2);
color: rgb(225, 138, 36);
}
/* Content container - collapsible for minimize */
.streaming-display-content {
display: flex;
flex-direction: column;
gap: 10px;
overflow: hidden;
transition: max-height var(--animation-duration, 125ms) ease, opacity var(--animation-duration, 125ms) ease;
}
/* Minimized state - hide content sections */
.streaming-display-minimized .streaming-display-content {
max-height: 0;
opacity: 0;
}
.streaming-display-minimized {
gap: 0;
}
/* Model/API icon in the label */
.streaming-display-icon {
width: 1.1em;
height: 1.1em;
flex-shrink: 0;
}
/* Minimized state adjustments */
.streaming-display-minimized.streaming-display {
padding: 10px 14px;
}
/* Reasoning (thinking) section */
.streaming-display-reasoning {
background: color-mix(in srgb, var(--SmartThemeBodyColor) 5%, transparent);
border-radius: 6px;
padding: 8px 10px;
border-left: 3px solid color-mix(in srgb, var(--SmartThemeBodyColor) 25%, transparent);
}
.streaming-display-reasoning-label {
font-size: 0.8em;
font-weight: 600;
opacity: 0.5;
margin-bottom: 4px;
letter-spacing: 0.03em;
text-transform: uppercase;
}
.streaming-display-reasoning-content {
font-size: 0.82em;
opacity: 0.65;
max-height: 25vh;
overflow-y: auto;
word-break: break-word;
line-height: 1.45;
scrollbar-width: thin;
}
.streaming-display-reasoning-content p {
margin: 0.3em 0;
}
.streaming-display-reasoning-content p:first-child {
margin-top: 0;
}
.streaming-display-reasoning-content p:last-child {
margin-bottom: 0;
}
/* Main content section */
.streaming-display-text {
border-top: 1px solid color-mix(in srgb, var(--SmartThemeBorderColor) 50%, transparent);
padding-top: 8px;
min-height: 1.5em;
}
.streaming-display-text-content {
font-size: 0.9em;
color: var(--SmartThemeBodyColor);
max-height: 40vh;
overflow-y: auto;
word-break: break-word;
line-height: 1.5;
scrollbar-width: thin;
}
.streaming-display-text-content p {
margin: 0.4em 0;
}
.streaming-display-text-content p:first-child {
margin-top: 0;
}
.streaming-display-text-content p:last-child {
margin-bottom: 0;
}
/* Strip auto-added quotes from <q> tags, as message formatting adds them */
.streaming-display-reasoning-content q:before,
.streaming-display-reasoning-content q:after,
.streaming-display-text-content q:before,
.streaming-display-text-content q:after {
content: '';
}