|
36 | 36 | { |
37 | 37 | @if (conversation.Message.Role != "System") |
38 | 38 | { |
39 | | - @if (Chat.Visual) |
40 | | - { |
41 | | - <FluentBadge Class="@(conversation.Message.Role == "User" ? "message-role-user" : "message-role-bot")" |
42 | | - Appearance="Appearance.Accent"> |
43 | | - @(conversation.Message.Role == "User" ? "User" : Utils.Model) |
44 | | - </FluentBadge> |
| 39 | + <FluentCard class="@(conversation.Message.Role == "User" ? "message-card user-message" : "message-card bot-message")"> |
45 | 40 | @if (conversation.Message.Role == "User") |
46 | 41 | { |
47 | | - <FluentCard class="message-card user-message"> |
48 | | - @conversation.Message.Content |
49 | | - </FluentCard> |
50 | | - } |
51 | | - else |
52 | | - { |
53 | | - <FluentCard class="message-card-img bot-message" |
54 | | - Style="height: 30rem !important; width: 30rem !important; "> |
55 | | - <div> |
56 | | - <a href="data:image/png;base64,@Convert.ToBase64String(conversation.Message.Image!)" |
57 | | - style="cursor: -webkit-zoom-in; cursor: zoom-in;" target="_blank"> |
58 | | - <img src="data:image/png;base64,@Convert.ToBase64String(conversation.Message.Image!)" |
59 | | - style="object-fit: fill; width:100%; height:100%;" |
60 | | - alt="imageResponse"/> |
61 | | - </a> |
62 | | - </div> |
63 | | - </FluentCard> |
64 | | - } |
65 | | - } |
66 | | - else |
67 | | - { |
68 | | - <FluentCard class="@(conversation.Message.Role == "User" ? "message-card user-message" : "message-card bot-message")"> |
69 | | - @if (conversation.Message.Role == "User" && (conversation.AttachedFiles.Any() || conversation.AttachedImages.Any())) |
| 42 | + @if (conversation.AttachedFiles.Any() || conversation.AttachedImages.Any()) |
70 | 43 | { |
71 | 44 | <div class="attached-files-display"> |
72 | 45 | @foreach (var image in conversation.AttachedImages) |
|
84 | 57 | } |
85 | 58 | </div> |
86 | 59 | } |
87 | | - @if (conversation.Message.Role == "User") |
| 60 | + <div> |
| 61 | + @((MarkupString)Markdown.ToHtml(conversation.Message.Content ?? string.Empty, _markdownPipeline)) |
| 62 | + </div> |
| 63 | + } |
| 64 | + else |
| 65 | + { |
| 66 | + @if (conversation.Message.Images?.Any() == true) |
88 | 67 | { |
89 | | - <div> |
90 | | - @((MarkupString)Markdown.ToHtml(conversation.Message.Content ?? string.Empty, _markdownPipeline)) |
| 68 | + <div class="generated-images"> |
| 69 | + @foreach (var imageBytes in conversation.Message.Images) |
| 70 | + { |
| 71 | + var b64 = Convert.ToBase64String(imageBytes); |
| 72 | + <div class="image-wrapper"> |
| 73 | + <a href="data:image/png;base64,@b64" target="_blank"> |
| 74 | + <img src="data:image/png;base64,@b64" class="generated-image" alt="generated image" /> |
| 75 | + </a> |
| 76 | + <div class="image-actions"> |
| 77 | + <a href="data:image/png;base64,@b64" download="generated-image.png" |
| 78 | + class="image-action-btn" title="Download"> |
| 79 | + <FluentIcon Value="@(new Icons.Filled.Size24.ArrowDownload())" |
| 80 | + Style="fill: var(--accent-base-color);" /> |
| 81 | + </a> |
| 82 | + <span class="image-action-btn" title="Copy to clipboard" |
| 83 | + @onclick="@(async () => await CopyImageToClipboard(b64))"> |
| 84 | + <FluentIcon Value="@(new Icons.Filled.Size24.Copy())" |
| 85 | + Style="fill: var(--accent-base-color);" /> |
| 86 | + </span> |
| 87 | + </div> |
| 88 | + </div> |
| 89 | + } |
91 | 90 | </div> |
92 | 91 | } |
93 | 92 | else |
|
114 | 113 | @((MarkupString)Markdown.ToHtml(GetMessageContent(conversation.Message), _markdownPipeline)) |
115 | 114 | </div> |
116 | 115 | } |
117 | | - </FluentCard> |
118 | | - } |
| 116 | + } |
| 117 | + </FluentCard> |
119 | 118 | } |
120 | 119 | } |
121 | 120 | @if (_isLoading) |
122 | 121 | { |
123 | | - @if (Chat.Visual) |
| 122 | + @if (Utils.Visual) |
124 | 123 | { |
125 | 124 | <span class="message-role-bot" style="font-style: italic; font-size: small">This might take a while...</span> |
126 | 125 | } |
127 | | - else |
| 126 | + else if (_incomingMessage != null || _incomingReasoning != null) |
128 | 127 | { |
129 | | - @if (_incomingMessage != null || _incomingReasoning != null) |
130 | | - { |
131 | | - <FluentCard class="message-card bot-message"> |
132 | | - @if (_isThinking) |
133 | | - { |
134 | | - <span class="thinker"> |
135 | | - @((MarkupString)Markdown.ToHtml(_incomingReasoning ?? string.Empty, _markdownPipeline)) |
136 | | - </span> |
137 | | - } |
138 | | - else |
139 | | - { |
140 | | - @((MarkupString)Markdown.ToHtml(_incomingMessage ?? string.Empty, _markdownPipeline)) |
141 | | - } |
142 | | - </FluentCard> |
143 | | - } |
| 128 | + <FluentCard class="message-card bot-message"> |
| 129 | + @if (_isThinking) |
| 130 | + { |
| 131 | + <span class="thinker"> |
| 132 | + @((MarkupString)Markdown.ToHtml(_incomingReasoning ?? string.Empty, _markdownPipeline)) |
| 133 | + </span> |
| 134 | + } |
| 135 | + else |
| 136 | + { |
| 137 | + @((MarkupString)Markdown.ToHtml(_incomingMessage ?? string.Empty, _markdownPipeline)) |
| 138 | + } |
| 139 | + </FluentCard> |
144 | 140 | } |
145 | 141 | } |
146 | 142 | <div id="bottom" @ref="_bottomElement"></div> |
|
277 | 273 | } |
278 | 274 |
|
279 | 275 | ctx = Utils.Visual |
280 | | - ? AIHub.Chat().EnableVisual() |
| 276 | + ? AIHub.Chat().WithModel(model).EnableVisual() |
281 | 277 | : AIHub.Chat().WithModel(model); |
282 | 278 | } |
283 | 279 | catch (MaINCustomException ex) |
|
628 | 624 | }).ToList(); |
629 | 625 | } |
630 | 626 |
|
| 627 | + private async Task CopyImageToClipboard(string base64) |
| 628 | + { |
| 629 | + try |
| 630 | + { |
| 631 | + await JS.InvokeVoidAsync("editorManager.copyImageToClipboard", base64); |
| 632 | + } |
| 633 | + catch |
| 634 | + { |
| 635 | + // silently ignore — clipboard API may not be available in all browsers |
| 636 | + } |
| 637 | + } |
| 638 | + |
631 | 639 | public void Dispose() |
632 | 640 | { |
633 | 641 | _cancellationTokenSource?.Dispose(); |
|
0 commit comments