Skip to content

Commit 522da4d

Browse files
committed
Refactor embeddings and add reconnect modal
Enhanced support for SQL Server's `SqlVector<float>` for embeddings, replacing `float[]` to improve vector search compatibility. Added a reconnect modal for better handling of server disconnections, including UI, styles, and JavaScript logic. Upgraded to .NET 10.0 and EF Core 10.0, updated dependencies, and improved token counting logic for embeddings. Updated documentation and configuration files to reflect these changes. Migrated database schema to support `SqlVector<float>`.
2 parents 5ac8175 + d645889 commit 522da4d

18 files changed

+299
-43
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Embeddings and chat completion are powered by [Semantic Kernel](https://github.c
6262

6363
2. Configure the database and OpenAI settings
6464
- Edit `SqlDatabaseVectorSearch/appsettings.json` and set your Azure SQL connection string and OpenAI settings.
65+
- **Important**: The `ModelId` values for both `ChatCompletion` and `Embedding` are used for token counting via `Microsoft.ML.Tokenizers`. These values must be valid model identifiers supported by the tokenizer library (e.g., `gpt-4o`, `gpt-4`, `gpt-3.5-turbo`, `text-embedding-3-small`, `text-embedding-3-large`, `text-embedding-ada-002`). The `ModelId` may differ from the actual deployment name you're using in Azure OpenAI. For example, for gpt-4.1 and gpt-5 models set the `ModelId` to `gpt-4o` for proper token counting.
6566
- If using embedding models with shortening (e.g., `text-embedding-3-small` or `text-embedding-3-large`), set the `Dimensions` property accordingly. For `text-embedding-3-large`, you must specify a value <= 1998.
6667
- If you change the VECTOR size, update both the [ApplicationDbContext](SqlDatabaseVectorSearch/Data/ApplicationDbContext.cs) and the [Initial Migration](SqlDatabaseVectorSearch/Data/Migrations/00000000000000_Initial.cs).
6768

SqlDatabaseVectorSearch/Components/App.razor

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
<meta charset="utf-8" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<base href="/" />
8-
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
8+
<ResourcePreloader />
9+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
910
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet" />
1011
<link href="_content/Blazor.Bootstrap/blazor.bootstrap.css" rel="stylesheet" />
1112
<script src="https://kit.fontawesome.com/f7a7b34f96.js" crossorigin="anonymous"></script>
@@ -18,6 +19,7 @@
1819

1920
<body>
2021
<Routes @rendermode="InteractiveServer" />
22+
<ReconnectModal />
2123
<script src="_framework/blazor.web.js"></script>
2224
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
2325
<!-- Add chart.js reference if chart components are used in your application. -->
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
2+
3+
<dialog id="components-reconnect-modal" data-nosnippet>
4+
<div class="components-reconnect-container">
5+
<div class="components-rejoining-animation" aria-hidden="true">
6+
<div></div>
7+
<div></div>
8+
</div>
9+
<p class="components-reconnect-first-attempt-visible">
10+
Rejoining the server...
11+
</p>
12+
<p class="components-reconnect-repeated-attempt-visible">
13+
Rejoin failed... Trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
14+
</p>
15+
<p class="components-reconnect-failed-visible">
16+
Failed to rejoin.<br />Please retry or reload the page.
17+
</p>
18+
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
19+
Retry
20+
</button>
21+
<p class="components-pause-visible">
22+
The session has been paused by the server.
23+
</p>
24+
<button id="components-resume-button" class="components-pause-visible">
25+
Resume
26+
</button>
27+
<p class="components-resume-failed-visible">
28+
Failed to resume the session.<br />Please reload the page.
29+
</p>
30+
</div>
31+
</dialog>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
.components-reconnect-first-attempt-visible,
2+
.components-reconnect-repeated-attempt-visible,
3+
.components-reconnect-failed-visible,
4+
.components-pause-visible,
5+
.components-resume-failed-visible,
6+
.components-rejoining-animation {
7+
display: none;
8+
}
9+
10+
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
11+
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
12+
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
13+
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
14+
#components-reconnect-modal.components-reconnect-retrying,
15+
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
16+
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
17+
#components-reconnect-modal.components-reconnect-failed,
18+
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
19+
display: block;
20+
}
21+
22+
23+
#components-reconnect-modal {
24+
background-color: white;
25+
width: 20rem;
26+
margin: 20vh auto;
27+
padding: 2rem;
28+
border: 0;
29+
border-radius: 0.5rem;
30+
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
31+
opacity: 0;
32+
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
33+
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
34+
&[open]
35+
36+
{
37+
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
38+
animation-fill-mode: both;
39+
}
40+
41+
}
42+
43+
#components-reconnect-modal::backdrop {
44+
background-color: rgba(0, 0, 0, 0.4);
45+
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
46+
opacity: 1;
47+
}
48+
49+
@keyframes components-reconnect-modal-slideUp {
50+
0% {
51+
transform: translateY(30px) scale(0.95);
52+
}
53+
54+
100% {
55+
transform: translateY(0);
56+
}
57+
}
58+
59+
@keyframes components-reconnect-modal-fadeInOpacity {
60+
0% {
61+
opacity: 0;
62+
}
63+
64+
100% {
65+
opacity: 1;
66+
}
67+
}
68+
69+
@keyframes components-reconnect-modal-fadeOutOpacity {
70+
0% {
71+
opacity: 1;
72+
}
73+
74+
100% {
75+
opacity: 0;
76+
}
77+
}
78+
79+
.components-reconnect-container {
80+
display: flex;
81+
flex-direction: column;
82+
align-items: center;
83+
gap: 1rem;
84+
}
85+
86+
#components-reconnect-modal p {
87+
margin: 0;
88+
text-align: center;
89+
}
90+
91+
#components-reconnect-modal button {
92+
border: 0;
93+
background-color: #6b9ed2;
94+
color: white;
95+
padding: 4px 24px;
96+
border-radius: 4px;
97+
}
98+
99+
#components-reconnect-modal button:hover {
100+
background-color: #3b6ea2;
101+
}
102+
103+
#components-reconnect-modal button:active {
104+
background-color: #6b9ed2;
105+
}
106+
107+
.components-rejoining-animation {
108+
position: relative;
109+
width: 80px;
110+
height: 80px;
111+
}
112+
113+
.components-rejoining-animation div {
114+
position: absolute;
115+
border: 3px solid #0087ff;
116+
opacity: 1;
117+
border-radius: 50%;
118+
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
119+
}
120+
121+
.components-rejoining-animation div:nth-child(2) {
122+
animation-delay: -0.5s;
123+
}
124+
125+
@keyframes components-rejoining-animation {
126+
0% {
127+
top: 40px;
128+
left: 40px;
129+
width: 0;
130+
height: 0;
131+
opacity: 0;
132+
}
133+
134+
4.9% {
135+
top: 40px;
136+
left: 40px;
137+
width: 0;
138+
height: 0;
139+
opacity: 0;
140+
}
141+
142+
5% {
143+
top: 40px;
144+
left: 40px;
145+
width: 0;
146+
height: 0;
147+
opacity: 1;
148+
}
149+
150+
100% {
151+
top: 0px;
152+
left: 0px;
153+
width: 80px;
154+
height: 80px;
155+
opacity: 0;
156+
}
157+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Set up event handlers
2+
const reconnectModal = document.getElementById("components-reconnect-modal");
3+
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
4+
5+
const retryButton = document.getElementById("components-reconnect-button");
6+
retryButton.addEventListener("click", retry);
7+
8+
const resumeButton = document.getElementById("components-resume-button");
9+
resumeButton.addEventListener("click", resume);
10+
11+
function handleReconnectStateChanged(event) {
12+
if (event.detail.state === "show") {
13+
reconnectModal.showModal();
14+
} else if (event.detail.state === "hide") {
15+
reconnectModal.close();
16+
} else if (event.detail.state === "failed") {
17+
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
18+
} else if (event.detail.state === "rejected") {
19+
location.reload();
20+
}
21+
}
22+
23+
async function retry() {
24+
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
25+
26+
try {
27+
// Reconnect will asynchronously return:
28+
// - true to mean success
29+
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
30+
// - exception to mean we didn't reach the server (this can be sync or async)
31+
const successful = await Blazor.reconnect();
32+
if (!successful) {
33+
// We have been able to reach the server, but the circuit is no longer available.
34+
// We'll reload the page so the user can continue using the app as quickly as possible.
35+
const resumeSuccessful = await Blazor.resumeCircuit();
36+
if (!resumeSuccessful) {
37+
location.reload();
38+
} else {
39+
reconnectModal.close();
40+
}
41+
}
42+
} catch (err) {
43+
// We got an exception, server is currently unavailable
44+
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
45+
}
46+
}
47+
48+
async function resume() {
49+
try {
50+
const successful = await Blazor.resumeCircuit();
51+
if (!successful) {
52+
location.reload();
53+
}
54+
} catch {
55+
location.reload();
56+
}
57+
}
58+
59+
async function retryWhenDocumentBecomesVisible() {
60+
if (document.visibilityState === "visible") {
61+
await retry();
62+
}
63+
}

SqlDatabaseVectorSearch/Components/_Imports.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
@using Microsoft.JSInterop
1010
@using SqlDatabaseVectorSearch
1111
@using SqlDatabaseVectorSearch.Components
12+
@using SqlDatabaseVectorSearch.Components.Layout
1213
@using SqlDatabaseVectorSearch.Extensions
1314
@using SqlDatabaseVectorSearch.Models
1415
@using SqlDatabaseVectorSearch.Services

SqlDatabaseVectorSearch/Data/Entities/DocumentChunk.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace SqlDatabaseVectorSearch.Data.Entities;
1+
using Microsoft.Data.SqlTypes;
2+
3+
namespace SqlDatabaseVectorSearch.Data.Entities;
24

35
public class DocumentChunk
46
{
@@ -14,7 +16,7 @@ public class DocumentChunk
1416

1517
public required string Content { get; set; }
1618

17-
public required float[] Embedding { get; set; }
19+
public required SqlVector<float> Embedding { get; set; }
1820

1921
public virtual Document Document { get; set; } = null!;
2022
}

SqlDatabaseVectorSearch/Data/Migrations/00000000000000_Initial.Designer.cs

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SqlDatabaseVectorSearch/Data/Migrations/00000000000000_Initial.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using Microsoft.Data.SqlTypes;
23
using Microsoft.EntityFrameworkCore.Migrations;
34

45
#nullable disable
@@ -34,7 +35,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
3435
PageNumber = table.Column<int>(type: "int", nullable: true),
3536
IndexOnPage = table.Column<int>(type: "int", nullable: false),
3637
Content = table.Column<string>(type: "nvarchar(max)", nullable: false),
37-
Embedding = table.Column<string>(type: "vector(1536)", nullable: false)
38+
Embedding = table.Column<SqlVector<float>>(type: "vector(1536)", nullable: false)
3839
},
3940
constraints: table =>
4041
{

SqlDatabaseVectorSearch/Data/Migrations/ApplicationDbContextModelSnapshot.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// <auto-generated />
22
using System;
3+
using Microsoft.Data.SqlTypes;
34
using Microsoft.EntityFrameworkCore;
45
using Microsoft.EntityFrameworkCore.Infrastructure;
56
using Microsoft.EntityFrameworkCore.Metadata;
@@ -17,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
1718
{
1819
#pragma warning disable 612, 618
1920
modelBuilder
20-
.HasAnnotation("ProductVersion", "9.0.5")
21+
.HasAnnotation("ProductVersion", "10.0.0-rc.1.25451.107")
2122
.HasAnnotation("Relational:MaxIdentifierLength", 128);
2223

2324
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@@ -54,8 +55,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
5455
b.Property<Guid>("DocumentId")
5556
.HasColumnType("uniqueidentifier");
5657

57-
b.PrimitiveCollection<string>("Embedding")
58-
.IsRequired()
58+
b.Property<SqlVector<float>>("Embedding")
5959
.HasColumnType("vector(1536)");
6060

6161
b.Property<int>("Index")

0 commit comments

Comments
 (0)