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
1 change: 1 addition & 0 deletions CrestApps.Core.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<Folder Name="/src/Primitives/">
<Project Path="src/Primitives/CrestApps.Core.AI.A2A/CrestApps.Core.AI.A2A.csproj" />
<Project Path="src/Primitives/CrestApps.Core.AI.AzureAIInference/CrestApps.Core.AI.AzureAIInference.csproj" />
<Project Path="src/Primitives/CrestApps.Core.AI.Claude/CrestApps.Core.AI.Claude.csproj" />
<Project Path="src/Primitives/CrestApps.Core.AI.Chat/CrestApps.Core.AI.Chat.csproj" />
<Project Path="src/Primitives/CrestApps.Core.AI.Copilot/CrestApps.Core.AI.Copilot.csproj" />
<Project Path="src/Primitives/CrestApps.Core.AI.Ftp/CrestApps.Core.AI.Ftp.csproj" />
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<PackageVersion Include="YesSql.Provider.Sqlite" Version="5.4.7" />
<PackageVersion Include="A2A" Version="0.3.4-preview" />
<PackageVersion Include="A2A.AspNetCore" Version="0.3.4-preview" />
<PackageVersion Include="Anthropic" Version="12.13.0" />
<PackageVersion Include="ModelContextProtocol" Version="$(ModelContextProtocolVersion)" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="$(ModelContextProtocolVersion)" />
<PackageVersion Include="ModelContextProtocol.Core" Version="$(ModelContextProtocolVersion)" />
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Most teams start AI integration with a provider SDK, then quickly run into the r
- **MCP server and MCP client capabilities** including resource, prompt, and external host management
- **Custom AI functions** for application-specific actions and tool calling
- **Copilot orchestrator integration**
- **Claude orchestrator integration**
- **Template-driven prompts and profile definitions** for cleaner code and reusable system behavior
- **Chat widgets and business workflows** including chat metrics, data extraction, goal conversion, post-session processing, and live-agent handoff
- **Usage and lead reporting** for AI consumption and customer engagement scenarios
Expand Down
3 changes: 2 additions & 1 deletion src/CrestApps.Core.Docs/docs/changelog/v1.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ description: Initial standalone release notes for the CrestApps.Core repository.
- centralizes reusable MCP runtime registration in `AddCoreAIMcpServices()`, moves the shared MCP metadata, capability-resolution, tool-registry, SSE settings-handler, and invoke-function services into `CrestApps.Core.AI.Mcp`, and splits optional StdIO transport registration so hosts can enable it only where needed
- treats aborted and canceled request-stream failures in the Aspire AppHost as observed task exceptions so local development no longer floods the console with benign unobserved-task noise
- generates external `.map` source map files for all JS and CSS assets in the gulp build pipeline, copies them into `dist/` during npm package preparation, and includes them in the `@crestapps/ai-chat-ui` package exports
- adds per-message text-to-speech play/pause button on assistant messages in the AI Chat and Chat Interaction UIs when a TTS deployment is configured, allowing users to hear any message read aloud regardless of whether the profile uses Conversation mode
- adds per-message text-to-speech play/pause controls on assistant messages in the AI Chat and Chat Interaction UIs, keeps the action toolbar pinned to the bottom-right of each response without reserving a separate action row, automatically stops other message players before starting a new one, and hides manual playback controls during Conversation mode
- upgrades the MVC sample host to Font Awesome 7 and adds draggable, resizable AI Chat widget layout persistence with a reset-size control that hosts can disable through widget config
- adds Chat History page listing previous sessions per AI Profile sorted by creation date, with resume, delete, delete-all, and new-chat actions
- adds Test page for Utility and Agent AI Profiles providing a single-prompt/single-response streamed UI
- renames the "Chat" button to "New Chat" on the AI Profile list and adds "Chat History" and "Test" buttons for applicable profile types
26 changes: 24 additions & 2 deletions src/CrestApps.Core.Docs/docs/core/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -762,10 +762,32 @@ Both widgets require the following libraries to be loaded on the page **before**
|---------|---------|-------------|
| [Chart.js](https://www.chartjs.org/) | Interactive chart rendering | `https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js` |
| [highlight.js](https://highlightjs.org/) | Code syntax highlighting | `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js` |
| [Font Awesome 7](https://fontawesome.com/) | Message action icons and brand icons such as Claude | `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7/css/all.min.css` |

### Widget layout behavior

The AI Chat widget now supports draggable and resizable floating shells through the shared `widget` config. These behaviors are enabled by default for widgets, persist the user-selected position and size in `localStorage`, and can be turned off per host if needed.

```js
window.openAIChatManager.initialize({
// ... other config ...
widget: {
chatWidgetContainer: '#widget-panel',
chatWidgetStateName: 'support-chat',
toggleButtonSelector: '#widget-toggle',
resetSizeButtonSelector: '#widget-reset-size',
enableDragging: true,
enableResizing: true,
persistLayout: true
}
});
```

When `enableDragging` is on, both the widget window and its floating toggle button can be repositioned. When `enableResizing` is on, the widget can be resized from the browser resize handle and the optional reset-size button can restore the default dimensions. Set either flag to `false` to opt out for a specific host.

### Text-to-Speech Play Icon

When a text-to-speech (TTS) deployment is configured, each assistant message shows a play icon that reads the message aloud via server-side speech synthesis. Clicking the icon calls `SynthesizeSpeech` on the SignalR hub, which streams audio chunks back to the client and plays them as MP3. Clicking the icon again pauses playback.
When a text-to-speech (TTS) deployment is configured, each completed assistant message shows a play icon that reads the message aloud via server-side speech synthesis. Clicking the icon calls `SynthesizeSpeech` on the SignalR hub, which streams audio chunks back to the client and plays them as MP3. While audio is active, the icon switches to pause, the action buttons stay anchored at the bottom-right of the message just above the divider, and starting playback on a different message automatically stops the current player.

To enable this feature, pass `textToSpeechEnabled: true` in the widget initialization config:

Expand All @@ -777,5 +799,5 @@ window.openAIChatManager.initialize({
});
```

The play icon appears on all completed assistant messages regardless of whether the profile uses Conversation mode. The only requirement is that a TTS deployment is available on the server.
The play icon appears on completed assistant messages when a TTS deployment is available on the server. In Conversation mode, the per-message playback icon is hidden so the live voice exchange is not interrupted by manual playback controls.

115 changes: 115 additions & 0 deletions src/CrestApps.Core.Docs/docs/core/claude.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
sidebar_label: Claude
sidebar_position: 17
title: Claude Orchestrator
description: Configure Claude as a first-class CrestApps.Core orchestrator using the official Anthropic C# SDK and the MVC sample host.
---

# Claude Orchestrator

> A Claude-powered `IOrchestrator` implementation built on the official `Anthropic` NuGet package. It runs entirely through managed SDK dependencies, so hosts do not need to install any external executable or CLI.

## Quick start

```csharp
builder.Services.AddCrestAppsCore(crestApps => crestApps
.AddAISuite(ai => ai
.AddClaudeOrchestrator()
)
);
```

Or register the lower-level service directly:

```csharp
builder.Services.AddCoreAIClaudeOrchestrator();
```

Resolve it by name:

```csharp
public sealed class MyController(IOrchestratorResolver resolver)
{
public async IAsyncEnumerable<string> StreamAsync(OrchestrationContext context)
{
var orchestrator = resolver.Resolve("anthropic");

await foreach (var update in orchestrator.ExecuteStreamingAsync(context))
{
yield return update.Text;
}
}
}
```

## What it adds

The `CrestApps.Core.AI.Claude` package mirrors the Copilot integration points that matter for host applications:

- registers a named orchestrator (`"anthropic"`) with the title **Claude**
- adds an orchestration-context handler that reads `ClaudeSessionMetadata`
- adds a chat-interaction settings handler so SignalR/MVC chat settings persist Claude model overrides
- exposes `ClaudeClientService` for live model discovery through the Anthropic Models API
- keeps all dependencies inside normal NuGet restore and application publish flows

Unlike Copilot, Claude does **not** use GitHub OAuth, a credential store contract, or build-transitive CLI download targets.

## Configuration

### `ClaudeOptions`

```csharp
public sealed class ClaudeOptions
{
public string ApiKey { get; set; }
public string BaseUrl { get; set; } = "https://api.anthropic.com";
public string DefaultModel { get; set; }
}
```

### `appsettings.json`

```json
{
"ClaudeOptions": {
"ApiKey": "sk-ant-...",
"BaseUrl": "https://api.anthropic.com",
"DefaultModel": "claude-sonnet-4-6"
}
}
```

The orchestrator requires an API key. A default model is optional when profiles, templates, or chat interactions provide their own Claude model override.

Per-profile, per-template, and per-chat overrides are stored in `ClaudeSessionMetadata`.

## Services registered by `AddCoreAIClaudeOrchestrator()`

| Service | Implementation | Lifetime | Purpose |
| --- | --- | --- | --- |
| `IOrchestrator` | `ClaudeOrchestrator` (name: `"anthropic"`) | Scoped | Streams Claude responses through the shared orchestration pipeline |
| `ClaudeClientService` | `ClaudeClientService` | Scoped | Creates configured SDK clients and lists available models |
| `IChatInteractionSettingsHandler` | `ClaudeChatInteractionSettingsHandler` | Scoped | Persists Anthropic-specific chat settings |
| `IOrchestrationContextBuilderHandler` | `ClaudeOrchestrationContextHandler` | Scoped | Pushes Anthropic metadata into orchestration context |

## MVC sample host

`CrestApps.Core.Mvc.Web` registers the Claude orchestrator by default:

```csharp
builder.Services.AddCrestAppsCore(crestApps => crestApps
.AddAISuite(ai => ai
.AddClaudeOrchestrator()
.AddCopilotOrchestrator()
)
);
```

The MVC sample also wires Claude into:

1. **Admin → Settings** for Claude authentication mode, API key, base URL, and the optional default model
2. **AI Profiles** for profile-level model overrides
3. **AI Templates** for profile-source template overrides
4. **Chat Interactions** for interactive Claude model selection

When the API key is configured, the MVC editors load available Claude models from the Anthropic Models API. If model discovery is unavailable, the same editors fall back to free-form model entry.
6 changes: 3 additions & 3 deletions src/CrestApps.Core.Docs/docs/core/copilot.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
sidebar_label: GitHub Copilot
sidebar_label: Copilot
sidebar_position: 16
title: GitHub Copilot Orchestrator
title: Copilot Orchestrator
description: An alternative IOrchestrator implementation that uses the GitHub Copilot Extensions SDK for AI completions, supporting GitHub OAuth and Bring Your Own Key (BYOK) authentication modes.
---

# GitHub Copilot Orchestrator
# Copilot Orchestrator

> An alternative orchestrator that uses the GitHub Copilot Extensions SDK instead of the default orchestration pipeline, supporting both GitHub OAuth and BYOK authentication modes.

Expand Down
3 changes: 2 additions & 1 deletion src/CrestApps.Core.Docs/docs/core/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ The quickest way to validate the setup is to use **Chat Interactions** first, th
| Templates | `AddTemplating()` | `CrestApps.Core.Templates` | [AI Templates](./ai-templates.md) |
| Custom tools | `AddCoreAITool<T>()` | `CrestApps.Core.AI` | [Custom AI Tools](./tools.md) |
| Agents | Agent and orchestration registrations | `CrestApps.Core.AI` | [AI Agents](./agents.md) |
| Copilot orchestration | `AddCoreAICopilotOrchestrator()` | `CrestApps.Core.AI.Copilot` | [GitHub Copilot Orchestrator](./copilot.md) |
| Copilot orchestration | `AddCoreAICopilotOrchestrator()` | `CrestApps.Core.AI.Copilot` | [Copilot Orchestrator](./copilot.md) |
| Claude orchestration | `AddCoreAIClaudeOrchestrator()` | `CrestApps.Core.AI.Claude` | [Claude Orchestrator](./claude.md) |
| SignalR and widgets | `AddCoreSignalR()` | `CrestApps.Core.SignalR` | [SignalR](./signalr.md) |
| Data storage | Store registration extensions | `CrestApps.Core.Data.YesSql` | [Data Storage](./data-storage.md) |
| Providers | Provider-specific extensions | Provider packages | [AI Providers](../providers/index.md) |
Expand Down
7 changes: 4 additions & 3 deletions src/CrestApps.Core.Docs/docs/core/mvc-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ The core framework registration chain:
builder.Services.AddCrestAppsCore(crestApps => crestApps
.AddAISuite(ai => ai
.AddMarkdown()
.AddClaudeOrchestrator()
.AddCopilotOrchestrator()
.AddChatInteractions(chat => chat.ConfigureChatHubOptions<ChatInteractionHub>())
.AddDocumentProcessing(documentProcessing => documentProcessing
Expand Down Expand Up @@ -134,6 +135,8 @@ builder.Services.AddCrestAppsCore(crestApps => crestApps

`AddAISuite(...)` always wires the shared foundation, AI runtime, and orchestration together. `AddChatInteractions()` inside that suite then registers the shared `DataSourceChatInteractionSettingsHandler`, so Chat Interactions persist the selected data source and RAG metadata through the framework settings pipeline instead of MVC-only wiring. The provider service blocks also pull in the shared data-source RAG registrations, which register both `DataSourceOrchestrationHandler` and `DataSourcePreemptiveRagHandler` at the framework level so source availability instructions and preemptive RAG stay aligned with the saved chat settings.

The MVC sample now also registers both the **Claude** and **Copilot** orchestrators. Claude uses the official Anthropic SDK with a site-level authentication mode, API key, and live model discovery, while Copilot keeps its dedicated OAuth/BYOK flow. Admins can choose either orchestrator from the same AI Profile, AI Template, and Chat Interaction editors.

Documents, memory, and data sources now remain fully independent orchestration sources in the shared framework. Each source injects its own availability instructions and preemptive-RAG context, so the orchestrator can compose them together without the document prompts needing to know whether memory or data sources are also attached.

The MVC sample explicitly calls `AddMarkdown()` inside `AddAISuite(...)`. That keeps Markdown-aware normalization opt-in at the host level instead of making `CrestApps.Core.AI` depend on the Markdig-backed package automatically.
Expand Down Expand Up @@ -192,7 +195,6 @@ Provider-grouped connection settings under `CrestApps:Providers:{ProviderName}:C
"ProviderName": "AzureSpeech",
"Name": "whisper",
"Type": "SpeechToText",
"IsDefault": true,
"Endpoint": "https://eastus.stt.speech.microsoft.com",
"AuthenticationType": "ApiKey",
"ApiKey": "YOUR_API_KEY"
Expand All @@ -201,7 +203,6 @@ Provider-grouped connection settings under `CrestApps:Providers:{ProviderName}:C
"ProviderName": "AzureSpeech",
"Name": "AzureTextToSpeech",
"Type": "TextToSpeech",
"IsDefault": true,
"Endpoint": "https://eastus.tts.speech.microsoft.com",
"AuthenticationType": "ApiKey",
"ApiKey": "YOUR_API_KEY"
Expand Down Expand Up @@ -262,7 +263,7 @@ If an administrator already deleted the remote index directly in Elasticsearch o



The MVC admin chat widget now stays bound to the configured admin-chat profile instead of exposing a profile picker, restores its open/closed state and active session across page navigation, and reuses the stored session automatically when the next admin page loads. **Settings → AI Settings** now includes an **Admin widget** card where administrators choose that profile; leaving it empty disables the widget entirely. The same card also lets administrators change the widget accent color, which now defaults to the admin theme secondary color (`#6c757d`) instead of a hard-coded green. The widget now boots a real chat session immediately, so profiles with an **Initial prompt** show that assistant message first; otherwise it falls back to the welcome message and then **What do you want to know?** when no welcome text is configured.
The MVC admin chat widget now stays bound to the configured admin-chat profile instead of exposing a profile picker, restores its open/closed state and active session across page navigation, and reuses the stored session automatically when the next admin page loads. **Settings → AI Settings** now includes an **Admin widget** card where administrators choose that profile; leaving it empty disables the widget entirely. The same card also lets administrators change the widget accent color, which now defaults to the admin theme secondary color (`#6c757d`) instead of a hard-coded green. The widget now boots a real chat session immediately, so profiles with an **Initial prompt** show that assistant message first; otherwise it falls back to the welcome message and then **What do you want to know?** when no welcome text is configured. The shared widget runtime also now lets users drag both the floating toggle button and the widget shell, resize the widget, restore the default size from the header, and persist that layout in browser storage unless a host opts out through the widget config.

The MVC sample also now records provider usage in a dedicated **AI Usage Analytics** report. The report groups tracked completion calls by authenticated username or **Anonymous**, then breaks usage down by completion client and resolved model/deployment while showing total calls, distinct sessions, distinct chat interactions, token totals, and average latency. Session analytics now keep token totals and user-visible response latency separate so the main chat analytics page still shows per-session performance while the usage report captures provider activity more directly.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>CrestApps.Core.AI.Claude</RootNamespace>
<Title>CrestApps AI Anthropic</Title>
<Description>
$(CrestAppsDescription)

Anthropic SDK-based orchestrator for Claude chat sessions.
</Description>
<PackageTags>$(PackageTags) ai anthropic claude orchestration</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Anthropic" />
<PackageReference Include="Microsoft.Extensions.AI" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../CrestApps.Core.AI/CrestApps.Core.AI.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Text.Json;
using CrestApps.Core.AI.Claude.Models;
using CrestApps.Core.AI.Chat;
using CrestApps.Core.AI.Models;

namespace CrestApps.Core.AI.Claude.Handlers;

internal sealed class ClaudeChatInteractionSettingsHandler : IChatInteractionSettingsHandler
{
public Task UpdatingAsync(ChatInteraction interaction, JsonElement settings)
{
var orchestratorName = GetString(settings, "orchestratorName") ?? interaction.OrchestratorName;
if (!string.Equals(orchestratorName, Services.ClaudeOrchestrator.OrchestratorName, StringComparison.OrdinalIgnoreCase))
{
interaction.Remove<ClaudeSessionMetadata>();
return Task.CompletedTask;
}

interaction.Alter<ClaudeSessionMetadata>(metadata =>
{
metadata.ClaudeModel = GetString(settings, "anthropicModel");
});

return Task.CompletedTask;
}

public Task UpdatedAsync(ChatInteraction interaction, JsonElement settings)
{
return Task.CompletedTask;
}

private static string GetString(JsonElement element, string propertyName)
{
if (element.TryGetProperty(propertyName, out var prop) && prop.ValueKind == JsonValueKind.String)
{
return prop.GetString();
}

return null;
}
}
Loading
Loading