Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions PanoramicData.Blazor.Demo/Pages/PDChatDocumentation.razor
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,34 @@
<code>ToastEnabled</code> / <code>ToastDisplayDurationSeconds</code> for backward compatibility.
</p>
</section>
<section id="input-toolbar">
<h2 class="doc-section">Input toolbar, voice and wake phrase</h2>
<p>
A thin toolbar sits above the text box. It holds an agent picker and a model picker (each shown only when
the service lists two or more), the notification sounds bell (<code>IsMuted</code>), and, when the service
supplies <code>VoiceEndpoints</code>, a read-aloud speaker and a microphone. Dictated words are typed into
the text box and sent after a pause unless the user is editing. All of these are configured on
<code>IChatService</code> and have defaults, so an existing service needs no change.
</p>
<table class="table table-sm doc-props-table">
<thead><tr><th>IChatService member</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>Agents</code> / <code>SelectedAgentId</code></td><td><code>IReadOnlyList&lt;PDChatAgentOption&gt;?</code> / <code>string?</code></td><td><code>null</code></td><td>Who the user is talking to.</td></tr>
<tr><td><code>Models</code> / <code>SelectedModelId</code></td><td><code>IReadOnlyList&lt;PDChatModelOption&gt;?</code> / <code>string?</code></td><td><code>null</code></td><td>Which model answers.</td></tr>
<tr><td><code>VoiceEndpoints</code></td><td><code>PDChatVoiceEndpoints?</code></td><td><code>null</code></td><td>Where speech is sent and answers fetched; <code>null</code> hides the voice controls. <code>PDChatVoiceEndpoints.Simulated</code> needs no speech service.</td></tr>
<tr><td><code>IsReadAloudEnabled</code></td><td><code>bool</code></td><td><code>false</code></td><td>Reads the answer to each sent message aloud. Independent of the microphone and of notification sounds.</td></tr>
<tr><td><code>VoiceAutoSendDelay</code></td><td><code>TimeSpan</code></td><td>1 second</td><td>How long after a pause dictation is sent, when the user is not editing.</td></tr>
<tr><td><code>WakePhrases</code></td><td><code>IReadOnlyList&lt;string&gt;?</code></td><td><code>null</code> (off)</td><td>Phrases such as "Hey DumbBot" that wake a dormant microphone. Case and punctuation are ignored, and the phrase itself is never typed.</td></tr>
<tr><td><code>VoiceIdleTimeout</code></td><td><code>TimeSpan</code></td><td>10 seconds</td><td>How long the microphone may hear nothing before it goes dormant. Applies only with wake phrases.</td></tr>
</tbody>
</table>
<p>
With wake phrases set, Voice Mode goes <code>Dormant</code> after the idle timeout (never while the user is
editing or a send is pending). While dormant nothing is typed or sent until a wake phrase is heard; the words
after it are typed as usual. The microphone keeps streaming to the speech service while dormant, since it
must hear the phrase, so tell your users that a dormant microphone is still listening. Try it with the
<strong>Voice and Assistant</strong> settings on this page: the simulated microphone starts every other
utterance with "Hey DumbBot".
</p>
</section>
</div>
59 changes: 59 additions & 0 deletions PanoramicData.Blazor.Demo/Pages/PDChatPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,65 @@
<input type="text" @bind="ChatService.InputDisabledMessage" @bind:event="oninput" class="form-control form-control-sm" placeholder="(blank = notifications-only, nothing shown)">
<div class="form-text">Uncheck "Enable Message Input" above to see this. Leave blank for pure toast-only mode (the input simply disappears with no message).</div>
</div>

<hr />
<h6 class="small text-uppercase text-muted mb-2"><i class="fas fa-microphone me-1"></i>Voice and Assistant</h6>
<div class="form-text mb-2">The input toolbar above the text box. The simulated microphone makes up what it hears, and starts every other utterance with "Hey DumbBot".</div>

<div class="row">
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="IsVoiceOffered" id="voiceOfferedCheck" disabled="@(DemoService is null)">
<label class="form-check-label small" for="voiceOfferedCheck">Voice (simulated)</label>
</div>
</div>
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="ChatService.IsReadAloudEnabled" id="readAloudCheck">
<label class="form-check-label small" for="readAloudCheck">Read Answers Aloud</label>
</div>
</div>
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="IsAgentPickerShown" id="agentPickerCheck" disabled="@(DemoService is null)">
<label class="form-check-label small" for="agentPickerCheck">Agent Picker (two agents)</label>
</div>
</div>
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="IsModelPickerShown" id="modelPickerCheck" disabled="@(DemoService is null)">
<label class="form-check-label small" for="modelPickerCheck">Model Picker (two models)</label>
</div>
</div>
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="IsWakePhraseOn" id="wakePhraseCheck">
<label class="form-check-label small" for="wakePhraseCheck">Wake Phrase</label>
</div>
</div>
</div>

<div class="row mt-2">
<div class="col-6 mb-3">
<label class="form-label small">Auto-Send Delay (ms):</label>
<input type="number" @bind="AutoSendDelayMs" min="0" max="10000" step="100" class="form-control form-control-sm">
</div>
<div class="col-6 mb-3">
<label class="form-label small">Idle Before Dormant (seconds):</label>
<input type="number" @bind="IdleTimeoutSeconds" min="1" max="300" step="1" class="form-control form-control-sm">
</div>
</div>

<div class="mb-3">
<label class="form-label small">Wake Phrases (comma-separated):</label>
<input type="text" @bind="WakePhraseText" class="form-control form-control-sm" placeholder="Hey DumbBot, OK DumbBot">
<div class="form-text">With Wake Phrase on, the microphone goes dormant after the idle time and types nothing until it hears one of these. It is still streaming audio while dormant.</div>
</div>

<div class="small text-muted">
Agent: <code>@(ChatService.SelectedAgentId ?? "(default)")</code>,
Model: <code>@(ChatService.SelectedModelId ?? "(default)")</code>
</div>
</div>
</div>
</div>
Expand Down
61 changes: 60 additions & 1 deletion PanoramicData.Blazor.Demo/Pages/PDChatPage.razor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace PanoramicData.Blazor.Demo.Pages;
using PanoramicData.Blazor.Services;

namespace PanoramicData.Blazor.Demo.Pages;

public partial class PDChatPage : IDisposable
{
Expand All @@ -23,6 +25,63 @@ private PDChatDockMode CurrentDockMode

private readonly Action<PDChatDockMode> _onDockModeChanged;

private string _wakePhraseText = string.Join(", ", DumbChatService.DemoWakePhrases);

// Voice endpoints, agents and models are read-only on IChatService, so the demo sets them on its own service.
private DumbChatService? DemoService => ChatService as DumbChatService;

private bool IsVoiceOffered
{
get => ChatService.VoiceEndpoints is not null;
set => DemoService?.VoiceEndpoints = value ? PDChatVoiceEndpoints.Simulated : null;
}

private bool IsAgentPickerShown
{
get => ChatService.Agents is { Count: > 1 };
set => DemoService?.Agents = value ? DumbChatService.DemoAgents : null;
}

private bool IsModelPickerShown
{
get => ChatService.Models is { Count: > 1 };
set => DemoService?.Models = value ? DumbChatService.DemoModels : null;
}

private int AutoSendDelayMs
{
get => (int)ChatService.VoiceAutoSendDelay.TotalMilliseconds;
set => ChatService.VoiceAutoSendDelay = TimeSpan.FromMilliseconds(Math.Max(0, value));
}

private double IdleTimeoutSeconds
{
get => ChatService.VoiceIdleTimeout.TotalSeconds;
set => ChatService.VoiceIdleTimeout = TimeSpan.FromSeconds(Math.Max(1, value));
}

private bool IsWakePhraseOn
{
get => ChatService.WakePhrases is { Count: > 0 };
set => ChatService.WakePhrases = value ? ParseWakePhrases(_wakePhraseText) : null;
}

private string WakePhraseText
{
get => _wakePhraseText;
set
{
_wakePhraseText = value;
if (IsWakePhraseOn)
{
ChatService.WakePhrases = ParseWakePhrases(value);
}
}
}

private static string[] ParseWakePhrases(string text)
=> text.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

public PDChatPage()
{
// The new dock mode is read from the service when re-rendering
Expand Down
Loading
Loading