|
3 | 3 | @using Telerik.Blazor |
4 | 4 | @using blazor_progress_rag_demo.Services |
5 | 5 | @inject NucliaSearchService NucliaService |
| 6 | +@inject NavigationManager NavigationManager |
6 | 7 |
|
7 | | -<div class="k-d-flex k-flex-col k-min-h-screen k-bg-surface k-pb-20"> |
8 | | - |
9 | | - @if (!HasSearched) |
| 8 | +<div class="ai-search-container k-pos-relative k-overflow-x-hidden k-overflow-y-auto"> |
| 9 | + @if (!HasResults) |
10 | 10 | { |
11 | | - @* Hero Section *@ |
12 | | - <HeroSection /> |
13 | | - } |
14 | | - else |
15 | | - { |
16 | | - @* Results Header *@ |
17 | | - <div class="k-d-flex k-flex-col k-align-items-center k-pt-10 k-px-4"> |
18 | | - <h1 class="k-h1 k-font-weight-bold k-mb-4 k-text-center"> |
19 | | - Discover <span class="text-gradient-purple-blue">Progress Agentic RAG Knowledge</span> |
20 | | - </h1> |
| 11 | + @* Decorative circle background - only show when no results *@ |
| 12 | + <div class="ai-search-decorative-circle k-pos-absolute"> |
| 13 | + <div class="ai-search-decorative-inner k-pos-absolute"> |
| 14 | + <div class="ai-search-gradient-bg k-w-full k-h-full" /> |
| 15 | + </div> |
21 | 16 | </div> |
| 17 | + |
| 18 | + @* Decorative network visualization image on the right *@ |
| 19 | + <VectorsBackground Show="true" /> |
22 | 20 | } |
23 | 21 |
|
24 | | - @* Search Section *@ |
25 | | - <div class="k-d-flex k-flex-col k-align-items-center @(HasSearched ? "k-mt-4" : "k-mt-10") k-w-full k-px-4"> |
26 | | - |
27 | | - @* Search Bar *@ |
28 | | - <div class="search-box-container k-w-full"> |
29 | | - <TelerikTextBox @bind-Value="@SearchQuery" |
30 | | - Placeholder="Ask about PARAG features, deployment, security, integrations..." |
31 | | - Size="@ThemeConstants.TextBox.Size.Large" |
32 | | - Rounded="@ThemeConstants.TextBox.Rounded.Full"> |
33 | | - <TextBoxPrefixTemplate> |
34 | | - <div class="k-pl-2 k-color-subtle"> |
35 | | - <TelerikSvgIcon Icon="@SvgIcon.Plus" Size="@ThemeConstants.SvgIcon.Size.Large" /> |
| 22 | + @* Main content container *@ |
| 23 | + <div class="ai-search-content k-d-flex k-flex-column k-pos-relative k-overflow-hidden"> |
| 24 | + @if (!HasResults) |
| 25 | + { |
| 26 | + @* Initial state: Large header with description *@ |
| 27 | + <div class="k-d-flex k-flex-column k-gap-8 rag-hero-wrapper"> |
| 28 | + <h1 class="ai-search-title !k-mb-0 k-h1"> |
| 29 | + Discover |
| 30 | + <br /> |
| 31 | + Progress Agentic RAG Knowledge |
| 32 | + </h1> |
| 33 | + <p class="ai-search-description !k-mb-0"> |
| 34 | + Search our comprehensive Nuclia knowledge base with AI-powered intelligent search for precise, contextual results about Nuclia features, capabilities, and best practices |
| 35 | + </p> |
| 36 | + </div> |
| 37 | + |
| 38 | + @* Search input section - centered horizontally *@ |
| 39 | + <div class="ai-search-input-wrapper k-d-flex k-flex-column k-gap-8 search-input-wrapper k-w-full"> |
| 40 | + <SearchInput Query="@Query" |
| 41 | + QueryChanged="@((value) => Query = value)" |
| 42 | + OnSearchClick="@HandleSearch" |
| 43 | + IsLoading="@IsLoading" |
| 44 | + Placeholder="Ask about PARAG features, deployment, security, integrations..." /> |
| 45 | + |
| 46 | + @* Popular searches section *@ |
| 47 | + <div class="k-d-flex k-flex-column k-w-full k-gap-3"> |
| 48 | + <p class="ai-search-popular-label !k-mb-0"> |
| 49 | + Popular searches: |
| 50 | + </p> |
| 51 | + <div class="k-d-flex k-flex-wrap k-gap-1.5 k-justify-content-flex-start"> |
| 52 | + @foreach (var searchText in PopularSearches) |
| 53 | + { |
| 54 | + <SearchPill Text="@searchText" |
| 55 | + OnClick="@(() => HandleExampleSearch(searchText))" |
| 56 | + Disabled="@IsLoading" /> |
| 57 | + } |
36 | 58 | </div> |
37 | | - </TextBoxPrefixTemplate> |
38 | | - <TextBoxSuffixTemplate> |
39 | | - <div class="k-d-flex k-align-items-center k-gap-2 k-pr-1"> |
40 | | - <TelerikButton FillMode="@ThemeConstants.Button.FillMode.Flat" |
41 | | - Icon="@SvgIcon.MicrophoneOutline" |
42 | | - Title="Voice Search" |
43 | | - Class="k-rounded-full k-color-subtle" /> |
44 | | - |
45 | | - <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary" |
46 | | - FillMode="@ThemeConstants.Button.FillMode.Solid" |
47 | | - Icon="@SvgIcon.ArrowUp" |
48 | | - Class="k-rounded-full k-w-10 k-h-10 k-p-0 k-d-flex k-justify-content-center k-align-items-center" |
49 | | - OnClick="@HandleSearch" /> |
| 59 | + </div> |
| 60 | + </div> |
| 61 | + } |
| 62 | + else |
| 63 | + { |
| 64 | + @* Results state: Compact header with integrated search *@ |
| 65 | + <div class="ai-search-results-hero k-d-flex k-flex-column k-gap-9 k-pos-relative hero"> |
| 66 | + @* Decorative elements container with overflow clipping *@ |
| 67 | + <div class="ai-search-results-decorative-container k-pos-absolute k-overflow-hidden"> |
| 68 | + @* Decorative gradient circle behind title *@ |
| 69 | + <div class="ai-search-results-gradient-circle k-pos-absolute"> |
| 70 | + <div class="ai-search-results-gradient-inner k-pos-absolute"> |
| 71 | + <div class="ai-search-gradient-bg k-w-full k-h-full" /> |
| 72 | + </div> |
50 | 73 | </div> |
51 | | - </TextBoxSuffixTemplate> |
52 | | - </TelerikTextBox> |
53 | | - </div> |
54 | 74 |
|
55 | | - @if (!HasSearched) |
56 | | - { |
57 | | - @* Popular Searches *@ |
58 | | - <div class="k-mt-10 k-d-flex k-flex-col k-align-items-center k-w-full k-max-w-screen-lg"> |
59 | | - <div class="k-font-size-sm k-color-subtle k-mb-4">Popular searches:</div> |
60 | | - |
61 | | - <div class="k-d-flex k-justify-content-center k-flex-wrap k-gap-2 k-w-full"> |
62 | | - @foreach (var item in PopularSearchItems) |
63 | | - { |
64 | | - <SearchPill Text="@item.Text" OnClick="@(() => OnPillClick(item.Text))" /> |
65 | | - } |
| 75 | + @* Vertical gradient shadow *@ |
| 76 | + <div class="ai-search-results-shadow k-pos-absolute" /> |
| 77 | + </div> |
| 78 | + |
| 79 | + <h1 class="ai-search-results-title !k-mb-0 k-text-center k-pos-relative"> |
| 80 | + Discover Progress Agentic RAG Knowledge |
| 81 | + </h1> |
| 82 | + |
| 83 | + @* Search input - centered and integrated *@ |
| 84 | + <div class="ai-search-results-input-wrapper k-w-full k-pos-relative"> |
| 85 | + <SearchInput Query="@Query" |
| 86 | + QueryChanged="@((value) => Query = value)" |
| 87 | + OnSearchClick="@HandleSearch" |
| 88 | + IsLoading="@IsLoading" |
| 89 | + Placeholder="What is PARAG and how does it work?" /> |
66 | 90 | </div> |
67 | 91 | </div> |
68 | 92 | } |
69 | | - else |
| 93 | + |
| 94 | + @* Results section *@ |
| 95 | + @if (HasResults) |
70 | 96 | { |
71 | | - @* Search Results *@ |
72 | | - <div class="k-mt-10 k-w-full k-max-w-screen-lg"> |
73 | | - <div class="k-p-6"> |
74 | | - @if (IsSearching && string.IsNullOrEmpty(SearchResult?.Response)) |
| 97 | + <div class="ai-search-results-section k-d-flex k-flex-column k-overflow-hidden k-w-full results-section"> |
| 98 | + <div class="ai-search-results-content-wrapper k-d-flex k-flex-column k-gap-16 k-w-full results-content"> |
| 99 | + @* Answer content *@ |
| 100 | + @if (IsLoading) |
75 | 101 | { |
76 | | - <div class="k-d-flex k-justify-content-center k-align-items-center k-p-4"> |
77 | | - <TelerikLoader Size="@ThemeConstants.Loader.Size.Large" /> |
78 | | - </div> |
| 102 | + <GradientLoader Title="Searching<br />Knowledge Base" |
| 103 | + Subtitle="Analyzing your query..." /> |
79 | 104 | } |
80 | | - else if (SearchResult != null) |
| 105 | + |
| 106 | + @if (!IsLoading && !string.IsNullOrEmpty(Answer)) |
81 | 107 | { |
82 | | - <div class="k-font-size-md k-line-height-md k-mb-4" style="white-space: pre-wrap;"> |
83 | | - @SearchResult.Response |
| 108 | + <div class="ai-search-answer-text markdown-content"> |
| 109 | + <MarkdownContent Text="@Answer" /> |
84 | 110 | </div> |
85 | 111 | } |
86 | | - </div> |
87 | | - </div> |
88 | 112 |
|
89 | | - @* Related Searches *@ |
90 | | - <div class="k-mt-10 k-d-flex k-flex-col k-align-items-center k-w-full k-max-w-screen-lg"> |
91 | | - <div class="k-font-size-sm k-color-subtle k-mb-4">Related searches:</div> |
92 | | - |
93 | | - <div class="k-d-flex k-justify-content-center k-flex-wrap k-gap-2 k-w-full"> |
94 | | - @foreach (var item in PopularSearchItems) |
| 113 | + @* Related searches section *@ |
| 114 | + @if (!IsLoading && !string.IsNullOrEmpty(Answer)) |
95 | 115 | { |
96 | | - <SearchPill Text="@item.Text" OnClick="@(() => OnPillClick(item.Text))" /> |
| 116 | + <div class="k-d-flex k-flex-column k-gap-3"> |
| 117 | + <p class="ai-search-popular-label !k-mb-0"> |
| 118 | + Related searches: |
| 119 | + </p> |
| 120 | + <div class="k-d-flex k-flex-wrap k-justify-content-flex-start k-gap-1"> |
| 121 | + @foreach (var searchText in PopularSearches) |
| 122 | + { |
| 123 | + <SearchPill Text="@searchText" |
| 124 | + OnClick="@(() => HandleExampleSearch(searchText))" |
| 125 | + Disabled="@IsLoading" /> |
| 126 | + } |
| 127 | + </div> |
| 128 | + </div> |
97 | 129 | } |
98 | 130 | </div> |
99 | 131 | </div> |
|
102 | 134 | </div> |
103 | 135 |
|
104 | 136 | @code { |
105 | | - private string SearchQuery { get; set; } = string.Empty; |
106 | | - private bool HasSearched { get; set; } = false; |
107 | | - private bool IsSearching { get; set; } = false; |
108 | | - private StreamingAskResult? SearchResult { get; set; } |
| 137 | + private string Query { get; set; } = string.Empty; |
| 138 | + private bool IsLoading { get; set; } = false; |
| 139 | + private string Answer { get; set; } = string.Empty; |
| 140 | + private string CurrentQuestion { get; set; } = string.Empty; |
109 | 141 |
|
110 | | - public class PopularSearchItem |
111 | | - { |
112 | | - public string Text { get; set; } = string.Empty; |
113 | | - } |
| 142 | + private bool HasResults => !string.IsNullOrEmpty(Answer) || IsLoading || !string.IsNullOrEmpty(CurrentQuestion); |
114 | 143 |
|
115 | | - private List<PopularSearchItem> PopularSearchItems { get; set; } = new List<PopularSearchItem> |
| 144 | + private List<string> PopularSearches = new List<string> |
116 | 145 | { |
117 | | - new PopularSearchItem { Text = "What is PARAG and how does it work?" }, |
118 | | - new PopularSearchItem { Text = "Deployment options and requirements" }, |
119 | | - new PopularSearchItem { Text = "Security features and compliance" }, |
120 | | - new PopularSearchItem { Text = "API integration and capabilities" }, |
121 | | - new PopularSearchItem { Text = "Pricing and licensing options" }, |
122 | | - new PopularSearchItem { Text = "Use cases and customer success stories" } |
| 146 | + "What is PARAG and how does it work?", |
| 147 | + "Deployment options and requirements", |
| 148 | + "Security features and compliance", |
| 149 | + "API integration and capabilities", |
| 150 | + "Pricing and licensing options", |
| 151 | + "Use cases and customer success stories" |
123 | 152 | }; |
124 | 153 |
|
| 154 | + protected override void OnInitialized() |
| 155 | + { |
| 156 | + // Check for query parameter from navigation |
| 157 | + var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri); |
| 158 | + if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("query", out var queryParam)) |
| 159 | + { |
| 160 | + Query = queryParam.ToString(); |
| 161 | + _ = HandleSearch(); |
| 162 | + } |
| 163 | + } |
| 164 | + |
125 | 165 | private async Task HandleSearch() |
126 | 166 | { |
127 | | - if (string.IsNullOrWhiteSpace(SearchQuery)) return; |
| 167 | + if (string.IsNullOrWhiteSpace(Query) || IsLoading) |
| 168 | + { |
| 169 | + return; |
| 170 | + } |
128 | 171 |
|
129 | | - HasSearched = true; |
130 | | - IsSearching = true; |
131 | | - SearchResult = new StreamingAskResult(); |
| 172 | + IsLoading = true; |
| 173 | + Answer = string.Empty; |
| 174 | + CurrentQuestion = Query; |
| 175 | + StateHasChanged(); |
132 | 176 |
|
133 | 177 | try |
134 | 178 | { |
135 | | - SearchResult = await NucliaService.AskVerseAsync(SearchQuery, (text) => |
| 179 | + await NucliaService.AskVerseAsync(Query, (partialResponse) => |
136 | 180 | { |
137 | | - if (SearchResult != null) |
138 | | - { |
139 | | - SearchResult.Response = text; |
140 | | - InvokeAsync(StateHasChanged); |
141 | | - } |
| 181 | + Answer = partialResponse; |
| 182 | + InvokeAsync(StateHasChanged); |
142 | 183 | }); |
143 | 184 | } |
| 185 | + catch (Exception ex) |
| 186 | + { |
| 187 | + Answer = $"Sorry, I encountered an error: {ex.Message}"; |
| 188 | + } |
144 | 189 | finally |
145 | 190 | { |
146 | | - IsSearching = false; |
| 191 | + IsLoading = false; |
147 | 192 | await InvokeAsync(StateHasChanged); |
148 | 193 | } |
149 | 194 | } |
150 | 195 |
|
151 | | - private async Task OnPillClick(string text) |
| 196 | + private async Task HandleExampleSearch(string searchText) |
152 | 197 | { |
153 | | - SearchQuery = text; |
| 198 | + Query = searchText; |
154 | 199 | await HandleSearch(); |
155 | 200 | } |
156 | 201 | } |
0 commit comments