Skip to content

Commit 1c00f56

Browse files
Convert to Vuetify
1 parent 0a51883 commit 1c00f56

File tree

7 files changed

+239
-246
lines changed

7 files changed

+239
-246
lines changed

EssentialCSharp.Web/Controllers/ChatController.cs

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Text.Json;
22
using EssentialCSharp.Chat.Common.Services;
3-
using EssentialCSharp.Web.Services;
43
using Microsoft.AspNetCore.Authorization;
54
using Microsoft.AspNetCore.Mvc;
65
using Microsoft.AspNetCore.RateLimiting;
@@ -15,13 +14,11 @@ public class ChatController : ControllerBase
1514
{
1615
private readonly AIChatService _AiChatService;
1716
private readonly ILogger<ChatController> _Logger;
18-
private readonly ICaptchaService _CaptchaService;
1917

20-
public ChatController(ILogger<ChatController> logger, AIChatService aiChatService, ICaptchaService captchaService)
18+
public ChatController(ILogger<ChatController> logger, AIChatService aiChatService)
2119
{
2220
_AiChatService = aiChatService;
2321
_Logger = logger;
24-
_CaptchaService = captchaService;
2522
}
2623

2724
[HttpPost("message")]
@@ -44,17 +41,6 @@ public async Task<IActionResult> SendMessage([FromBody] ChatMessageRequest reque
4441
{
4542
return Unauthorized(new { error = "User must be logged in to use chat." });
4643
}
47-
// For now, we rely on ASP.NET Core Rate Limiting for protection
48-
// Future enhancement: Add captcha verification after X number of requests
49-
var captchaResult = await _CaptchaService.VerifyAsync(request.CaptchaResponse);
50-
if (captchaResult == null || !captchaResult.Success)
51-
{
52-
return BadRequest(new
53-
{
54-
error = "Captcha verification failed. Please try again.",
55-
requiresCaptcha = true
56-
});
57-
}
5844

5945
var (response, responseId) = await _AiChatService.GetChatCompletion(
6046
prompt: request.Message,
@@ -107,23 +93,10 @@ public async Task StreamMessage([FromBody] ChatMessageRequest request, Cancellat
10793
await Response.WriteAsync(JsonSerializer.Serialize(new { error = "User must be logged in to use chat." }), cancellationToken);
10894
return;
10995
}
110-
// For now, we rely on ASP.NET Core Rate Limiting for protection
111-
// Future enhancement: Add captcha verification after X number of requests
112-
var captchaResult = await _CaptchaService.VerifyAsync(request.CaptchaResponse);
113-
if (captchaResult == null || !captchaResult.Success)
114-
{
115-
Response.StatusCode = 400;
116-
await Response.WriteAsync(JsonSerializer.Serialize(new
117-
{
118-
error = "Captcha verification failed. Please try again.",
119-
requiresCaptcha = true
120-
}), cancellationToken);
121-
return;
122-
}
12396

12497
Response.ContentType = "text/event-stream";
125-
Response.Headers["Cache-Control"] = "no-cache";
126-
Response.Headers["Connection"] = "keep-alive";
98+
Response.Headers.CacheControl = "no-cache";
99+
Response.Headers.Connection = "keep-alive";
127100

128101
await foreach (var (text, responseId) in _AiChatService.GetChatCompletionStream(
129102
prompt: request.Message,

EssentialCSharp.Web/Program.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ private static void Main(string[] args)
204204
// Custom response when rate limit is exceeded
205205
options.OnRejected = async (context, cancellationToken) =>
206206
{
207+
if (context.HttpContext.Request.Path.StartsWithSegments("/.well-known"))
208+
{
209+
return;
210+
}
207211
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
208212
context.HttpContext.Response.Headers.RetryAfter = "60";
209213
if (context.HttpContext.Request.Path.StartsWithSegments("/api/chat"))

EssentialCSharp.Web/Views/Shared/_Layout.cshtml

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414
var prodMap = new ImportMapDefinition(
1515
new Dictionary<string, string>
1616
{
17-
{ "vue", "./lib/vue/dist/vue.esm-browser.prod.js" },
17+
{ "vue", "https://cdn.jsdelivr.net/npm/vue@3.5.12/dist/vue.esm-browser.prod.js" },
1818
{ "vue-window-size", "./lib/vue-window-size/composition-api/dist/index.js" },
19+
{ "vuetify", "https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.esm.js" },
1920
}, null, null);
2021
var devMap = new ImportMapDefinition(
2122
new Dictionary<string, string>
2223
{
23-
{ "vue", "./lib/vue/dist/vue.esm-browser.js" },
24+
{ "vue", "https://cdn.jsdelivr.net/npm/vue@3.5.12/dist/vue.esm-browser.js" },
2425
{ "vue-window-size", "./lib/vue-window-size/composition-api/dist/index.js" },
26+
{ "vuetify", "https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.esm.js" },
2527
}, null, null);
2628
}
2729
<!DOCTYPE html>
@@ -49,7 +51,7 @@
4951
<meta property="og:image:height" content="500" />
5052
<meta name="twitter:title" property="og:title" content="@title" />
5153
<!-- Vuetify CSS -->
52-
<link href="https://cdn.jsdelivr.net/npm/vuetify@3.4.0/dist/vuetify.min.css" rel="stylesheet">
54+
<link href="https://cdn.jsdelivr.net/npm/vuetify@3.9.2/dist/vuetify.min.css" rel="stylesheet">
5355
<!-- Material Design Icons -->
5456
<link href="https://cdn.jsdelivr.net/npm/@@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
5557
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
@@ -290,6 +292,7 @@
290292
aria-labelledby="chat-dialog-title"
291293
aria-modal="true"
292294
tabindex="-1"
295+
v-cloak
293296
>
294297
<div class="chat-card elevation-12">
295298
<!-- Header -->
@@ -298,15 +301,41 @@
298301
<i class="mdi mdi-robot me-2" aria-hidden="true"></i>
299302
AI Assistant
300303
</h2>
301-
<button
302-
class="chat-close-button"
303-
v-on:click="closeChatDialog"
304-
aria-label="Close chat dialog"
305-
title="Close chat"
306-
type="button"
307-
>
308-
<i class="mdi mdi-close" aria-hidden="true"></i>
309-
</button>
304+
<div class="chat-header-actions">
305+
<!-- Vuetify Menu -->
306+
<v-menu>
307+
<template v-slot:activator="{ props }">
308+
<v-btn
309+
icon="mdi-dots-vertical"
310+
variant="text"
311+
size="small"
312+
v-bind="props"
313+
aria-label="Chat options menu"
314+
title="Chat options">
315+
</v-btn>
316+
</template>
317+
318+
<v-list>
319+
<v-list-item
320+
@@click="clearChatHistory"
321+
:disabled="chatMessages.length === 0"
322+
prepend-icon="mdi-delete-outline">
323+
<v-list-item-title>Clear History</v-list-item-title>
324+
</v-list-item>
325+
</v-list>
326+
</v-menu>
327+
328+
<!-- Close Button -->
329+
<button
330+
class="chat-close-button"
331+
v-on:click="closeChatDialog"
332+
aria-label="Close chat dialog"
333+
title="Close chat"
334+
type="button"
335+
>
336+
<i class="mdi mdi-close" aria-hidden="true"></i>
337+
</button>
338+
</div>
310339
</div>
311340

312341
<!-- Messages Area -->
@@ -418,37 +447,21 @@
418447
Type your question and press Enter or click send. Maximum 500 characters.
419448
</div>
420449
</form>
421-
422-
<!-- Actions -->
423-
<div class="chat-actions">
424-
<button
425-
v-on:click="clearChat"
426-
class="action-button"
427-
:disabled="chatMessages.length === 0"
428-
aria-label="Clear conversation history"
429-
title="Clear chat history"
430-
type="button"
431-
>
432-
<i class="mdi mdi-delete-outline" aria-hidden="true"></i>
433-
<span>Clear</span>
434-
</button>
435-
</div>
436450
</div>
437451
</div>
438452
</div>
439453
</div>
440454
}
441455

442456
<!-- Captcha Modal - Blocking Dialog -->
443-
@if (!Context.Request.Path.StartsWithSegments("/Identity"))
444-
{
445457
<div
446458
v-if="showCaptcha"
447459
class="captcha-modal-overlay"
448460
role="dialog"
449461
aria-labelledby="captcha-dialog-title"
450462
aria-modal="true"
451463
tabindex="-1"
464+
v-cloak
452465
>
453466
<div class="captcha-modal-card elevation-16">
454467
<!-- Header -->
@@ -484,8 +497,6 @@
484497
</div>
485498
</div>
486499
</div>
487-
}
488-
489500
</div>
490501
<footer class="border-top footer">
491502
<div class="row">

EssentialCSharp.Web/wwwroot/css/chat-widget.css

Lines changed: 20 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,9 @@
188188
background: white;
189189
border-radius: 16px;
190190
width: 90vw;
191-
max-width: 500px;
191+
max-width: 800px;
192192
height: 80vh;
193-
max-height: 600px;
193+
max-height: 700px;
194194
display: flex;
195195
flex-direction: column;
196196
box-shadow:
@@ -203,6 +203,16 @@
203203
border: 1px solid rgba(255, 255, 255, 0.2);
204204
}
205205

206+
/* Desktop-specific sizing for better space utilization */
207+
@media (min-width: 769px) {
208+
.chat-card {
209+
width: 80vw;
210+
max-width: 900px;
211+
height: 85vh;
212+
max-height: 800px;
213+
}
214+
}
215+
206216
@keyframes slideUpEnhanced {
207217
0% {
208218
opacity: 0;
@@ -278,6 +288,13 @@
278288
outline-offset: 2px;
279289
}
280290

291+
/* Header actions container */
292+
.chat-header-actions {
293+
display: flex;
294+
align-items: center;
295+
gap: 8px;
296+
}
297+
281298
/* Messages area */
282299
.chat-messages {
283300
flex: 1;
@@ -672,52 +689,6 @@
672689
transform: none;
673690
}
674691

675-
/* Chat actions */
676-
.chat-actions {
677-
display: flex;
678-
align-items: center;
679-
justify-content: space-between;
680-
}
681-
682-
.action-button {
683-
background: rgba(25, 118, 210, 0.04);
684-
border: 1px solid rgba(25, 118, 210, 0.12);
685-
color: #1976d2;
686-
cursor: pointer;
687-
display: flex;
688-
align-items: center;
689-
gap: 6px;
690-
padding: 10px 16px;
691-
border-radius: 20px;
692-
font-size: 12px;
693-
font-weight: 500;
694-
text-transform: uppercase;
695-
letter-spacing: 0.5px;
696-
transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1);
697-
}
698-
699-
.action-button:hover:not(:disabled) {
700-
background: rgba(25, 118, 210, 0.08);
701-
border-color: rgba(25, 118, 210, 0.2);
702-
transform: translateY(-1px);
703-
box-shadow: 0 2px 4px rgba(25, 118, 210, 0.2);
704-
}
705-
706-
.action-button:focus {
707-
outline: 3px solid rgba(25, 118, 210, 0.3);
708-
outline-offset: 1px;
709-
}
710-
711-
.action-button:disabled {
712-
color: rgba(0, 0, 0, 0.26);
713-
cursor: not-allowed;
714-
}
715-
716-
.message-count {
717-
font-size: 12px;
718-
color: rgba(0, 0, 0, 0.6);
719-
}
720-
721692
/* Elevation classes for consistency with Vuetify */
722693
.elevation-6 {
723694
box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12) !important;
@@ -747,8 +718,7 @@
747718
.chat-button,
748719
.chat-overlay,
749720
.chat-card,
750-
.send-button,
751-
.action-button {
721+
.send-button {
752722
animation: none;
753723
transition: none;
754724
}

0 commit comments

Comments
 (0)