diff --git a/.gitignore b/.gitignore index 8a7f4b8..e5c9c28 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,9 @@ web_modules/ .env.production.local .env.local +# Allow the C# SDK demo backend env file to be checked in for convenience +!rest-api/csharp-sdk/backend/.env + # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache diff --git a/rest-api/csharp-sdk/.devcontainer/devcontainer.json b/rest-api/csharp-sdk/.devcontainer/devcontainer.json new file mode 100644 index 0000000..aa9a460 --- /dev/null +++ b/rest-api/csharp-sdk/.devcontainer/devcontainer.json @@ -0,0 +1,30 @@ +{ + "name": "ThoughtSpot C# SDK demo", + "image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0", + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "20" + } + }, + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}/rest-api/csharp-sdk", + "postCreateCommand": "cd frontend && npm install", + "forwardPorts": [5000, 5173], + "portsAttributes": { + "5000": { + "label": "Backend (ASP.NET Core API)", + "onAutoForward": "notify" + }, + "5173": { + "label": "Frontend (Vite)", + "onAutoForward": "openPreview" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-dotnettools.csharp", + "dbaeumer.vscode-eslint" + ] + } + } +} diff --git a/rest-api/csharp-sdk/README.md b/rest-api/csharp-sdk/README.md new file mode 100644 index 0000000..3519d61 --- /dev/null +++ b/rest-api/csharp-sdk/README.md @@ -0,0 +1,104 @@ + + +# ThoughtSpot C# SDK — full-stack demo + +[Open this repo in GitHub Codespaces](https://github.com/thoughtspot/developer-examples/codespaces/new?ref=SCAL-322334-add-csharp-developer-examples) + +A minimal two-process demo covering search users, search liveboards, +exporting a liveboard's TML, and asking Spotter a natural-language question +with a live streaming (SSE) answer. + +``` +backend/ ASP.NET Core minimal API (C#) wrapping the thoughtspot_rest_api_sdk package +frontend/ Vite + React app that calls the backend +``` + +## Configure + +Both processes talk to the same cluster. Set these before running the backend +(defaults point at the same demo cluster used elsewhere in this repo): + +``` +export TS_HOST=https:// +export TS_USER= +export TS_PASS= +export TS_SPOTTER_WORKSHEET_ID= +``` + +Or set the `VITE_TS_HOST` / `VITE_TS_USERNAME` / `VITE_TS_PASSWORD` / +`VITE_LIVEBOARD_ID` / `VITE_SPOTTER_WORKSHEET_ID` equivalents in +`backend/.env` (loaded automatically at startup). + +### Why create-user / style-customization / PDF-export aren't here + +They all require admin privileges. On a restricted sandbox account (e.g. a +training/trial cluster), those calls return a 403 "Operation is not allowed" +— a cluster permission limit, not something this demo can route around — so +they were dropped rather than shipped as broken buttons. Search users, +search liveboards, TML export, and Spotter all work with a regular +(non-admin) account, as long as `CAN_USE_SPOTTER` is granted for Spotter. + +The worksheet used for Spotter must have AI Answer Generation enabled +(check the worksheet's metadata header — some sample worksheets ship with +it disabled and Spotter returns "No answer found for your query" for them). + +## Run + +```bash +# terminal 1 +cd backend +dotnet run # listens on http://localhost:5000 + +# terminal 2 +cd frontend +npm install +npm run dev # listens on http://localhost:5173 +``` + +Open http://localhost:5173. + +## Notes + +- The backend authenticates via `ThoughtSpotRestApi.CreateAsync(new ApiClientConfiguration { ... })` + once at startup, rather than the legacy `HttpClient`/`HttpClientHandler` + constructors. `CreateAsync` builds its own `SocketsHttpHandler`/`HttpClient` + internally and is what actually gives you `ConnectTimeout`/`ReadTimeout`/ + `WriteTimeout`, connection pooling, SSL handling, and automatic bearer-token + fetch + refresh. None of that is wired up if you construct the client from + your own `HttpClient`/`HttpClientHandler`. +- CORS is locked to `http://localhost:5173` in `backend/Program.cs` — update + if you serve the frontend elsewhere. +- TML export (`GET /api/liveboards/{id}/tml`) deserializes through + Newtonsoft.Json internally (that's what the SDK uses), so the backend + re-serializes the response with Newtonsoft before returning it — handing + the raw `List` straight to ASP.NET Core's default System.Text.Json + serializer silently produces near-empty `edoc`/`info` fields instead of an + error, since it doesn't know how to write out Newtonsoft's JToken types. +- Spotter (`GET /api/spotter/stream`) creates a fresh agent conversation via + `CreateAgentConversation`, then relays + `SendAgentConversationMessageStreamingStreamAsync`'s Server-Sent Events + straight through to the browser as they arrive — the frontend consumes it + with a native `EventSource` (see `frontend/src/api.js`'s `streamSpotter`). + A custom `done` SSE event signals a clean finish and a custom `ts-error` + event carries backend/API errors; both close the connection to stop the + browser's default auto-reconnect behavior. + - **SDK workaround**: `thoughtspot_rest_api_sdk`'s `DataSourceContextInput` + model always serializes its unused sibling fields + (`data_source_identifiers`, `guid`) as explicit JSON `null`s — confirmed + still present as of `0.1.0-beta.7`. This cluster's request validation + rejects that shape (it falls back to expecting a `worksheet_context` + payload instead of `data_source_context`, and complains that its + `worksheet_ids` field is missing). The backend works around this by + leaving `ContextPayloadV2Input.DataSourceContext` unset and writing the + minimal `data_source_context` object via `AdditionalProperties` instead, + which avoids emitting the nulls. Worth retrying without the workaround on + newer SDK versions. diff --git a/rest-api/csharp-sdk/backend/.env b/rest-api/csharp-sdk/backend/.env new file mode 100644 index 0000000..ac18b32 --- /dev/null +++ b/rest-api/csharp-sdk/backend/.env @@ -0,0 +1,6 @@ +VITE_TS_HOST=https://training.thoughtspot.cloud +VITE_TS_USERNAME=code-sandbox +VITE_TS_PASSWORD=3mbed+#3xplz +VITE_LIVEBOARD_ID=b504e160-3025-4508-a76a-1beb1f4b5eed +# Worksheet with AI Answer Generation enabled, used by GET /api/spotter/stream +VITE_SPOTTER_WORKSHEET_ID=cd252e5c-b552-49a8-821d-3eadaa049cca \ No newline at end of file diff --git a/rest-api/csharp-sdk/backend/.gitignore b/rest-api/csharp-sdk/backend/.gitignore new file mode 100644 index 0000000..cd42ee3 --- /dev/null +++ b/rest-api/csharp-sdk/backend/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ diff --git a/rest-api/csharp-sdk/backend/Backend.csproj b/rest-api/csharp-sdk/backend/Backend.csproj new file mode 100644 index 0000000..bdb5d54 --- /dev/null +++ b/rest-api/csharp-sdk/backend/Backend.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + ThoughtSpotBackend + + LatestMajor + + + + + + + + diff --git a/rest-api/csharp-sdk/backend/Program.cs b/rest-api/csharp-sdk/backend/Program.cs new file mode 100644 index 0000000..84a9e3f --- /dev/null +++ b/rest-api/csharp-sdk/backend/Program.cs @@ -0,0 +1,300 @@ +using System.IO; +using thoughtspot_rest_api_sdk; +using thoughtspot_rest_api_sdk.Api; +using thoughtspot_rest_api_sdk.Client; +using thoughtspot_rest_api_sdk.Model; + + +// Loads key=value lines from a .env file into process environment variables +static void LoadDotEnv(string path = ".env") +{ + if (!File.Exists(path)) + { + return; + } + + foreach (var raw in File.ReadAllLines(path)) + { + var line = raw.Trim(); + if (string.IsNullOrEmpty(line) || line.StartsWith("#")) + continue; + + var idx = line.IndexOf('='); + if (idx <= 0) + continue; + + var key = line.Substring(0, idx).Trim(); + var val = line.Substring(idx + 1).Trim(); + + if ((val.StartsWith("\"") && val.EndsWith("\"")) || (val.StartsWith("'") && val.EndsWith("'"))) + { + val = val.Substring(1, val.Length - 2); + } + + // Do not overwrite existing environment vars + if (Environment.GetEnvironmentVariable(key) == null) + { + Environment.SetEnvironmentVariable(key, val); + } + } +} + +// Load a local .env file (if present) so developers don't have to export vars. +LoadDotEnv(); + +// Prefer the VITE_* env vars (used by the frontend/dev) but fall back +// to the legacy TS_* names for compatibility. +string hostEnv = Environment.GetEnvironmentVariable("VITE_TS_HOST") ?? Environment.GetEnvironmentVariable("TS_HOST"); +string host = ThoughtSpotConfiguration.NormalizeHost(hostEnv); +string user = Environment.GetEnvironmentVariable("VITE_TS_USERNAME") ?? Environment.GetEnvironmentVariable("TS_USER") ?? ""; +string pass = Environment.GetEnvironmentVariable("VITE_TS_PASSWORD") ?? Environment.GetEnvironmentVariable("TS_PASS") ?? ""; +string liveboardId = Environment.GetEnvironmentVariable("VITE_LIVEBOARD_ID") ?? Environment.GetEnvironmentVariable("TS_LIVEBOARD_ID") ?? ""; +string spotterWorksheetId = Environment.GetEnvironmentVariable("VITE_SPOTTER_WORKSHEET_ID") ?? Environment.GetEnvironmentVariable("TS_SPOTTER_WORKSHEET_ID") ?? ""; + +// Try to create the ThoughtSpot client but don't crash the server if it fails. +ThoughtSpotRestApi? api = null; +try +{ + if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(pass)) + { + throw new InvalidOperationException("Missing ThoughtSpot credentials (TS_USER/TS_PASS or VITE_TS_USERNAME/VITE_TS_PASSWORD)."); + } + + api = await ThoughtSpotRestApi.CreateAsync(new ApiClientConfiguration + { + Host = host, + Username = user, + Password = pass, + TokenValiditySeconds = 3600, + // Self-signed dev/demo clusters. Remove for production. + IgnoreSslErrors = true, + }); + + Console.WriteLine("ThoughtSpot client created successfully."); +} +catch (Exception ex) +{ + Console.Error.WriteLine($"Warning: ThoughtSpot client not configured: {ex.Message}"); + Console.Error.WriteLine("Server will continue running; API endpoints will return 503 until configured."); +} + +// Determine the URL to listen on and configure the host. +var builder = WebApplication.CreateBuilder(args); +string? urls = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); +if (string.IsNullOrWhiteSpace(urls)) +{ + string port = Environment.GetEnvironmentVariable("PORT") ?? "5000"; + urls = $"http://127.0.0.1:{port}"; + Console.WriteLine($"No ASPNETCORE_URLS set; binding to {urls}"); + builder.WebHost.UseUrls(urls); +} +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + policy.WithOrigins( + "http://localhost:5173", + "http://127.0.0.1:5173", + "http://0.0.0.0:5173") + .AllowAnyHeader() + .AllowAnyMethod()); +}); + +var app = builder.Build(); +app.UseCors(); + +app.MapGet("/api/health", () => Results.Ok(new { host, liveboardId, spotterWorksheetId })); + +Func clientNotConfigured = () => Results.Json(new { error = "ThoughtSpot client not configured. See .env.example or set TS_USER/TS_PASS." }, statusCode: 503); + +// Some clusters (e.g. restricted sandbox/trial accounts) return a generic +// "Operation is not allowed" 403 for calls that need privileges the +// configured account doesn't have. Surface that as a clear message instead +// of the raw upstream error blob. +string FriendlyError(ApiException ex) +{ + if (ex.ErrorCode == 403 && ex.Message.Contains("Operation is not allowed")) + { + return "This operation requires privileges (e.g. admin) that the configured ThoughtSpot account does not have on this cluster."; + } + + return ex.Message; +} + +// 1. Search users --------------------------------------------------------------- +app.MapGet("/api/users", (string? query, int size) => +{ + if (api == null) return clientNotConfigured(); + string? namePattern = string.IsNullOrWhiteSpace(query) ? null : $"%{query}%"; + var request = new SearchUsersRequest( + namePattern: namePattern ?? null!, + recordSize: size <= 0 ? 10 : size); + + List users = api.SearchUsers(request); + return Results.Ok(users.Select(u => new { id = u.Id, name = u.Name, displayName = u.DisplayName })); +}); + +// 2. Search liveboards ------------------------------------------------------------ +app.MapGet("/api/liveboards", (string? query, int size) => +{ + if (api == null) return clientNotConfigured(); + string? namePattern = string.IsNullOrWhiteSpace(query) ? null : $"%{query}%"; + var metadataItem = new MetadataListItemInput( + type: MetadataListItemInput.TypeEnum.LIVEBOARD, + namePattern: namePattern ?? null!); + + var request = new SearchMetadataRequest( + metadata: new List { metadataItem }, + recordSize: size <= 0 ? 10 : size); + + List results = api.SearchMetadata(request); + return Results.Ok(results.Select(r => new { id = r.MetadataId, name = r.MetadataName })); +}); + +// 3. Export TML --------------------------------------------------------------------- +app.MapGet("/api/liveboards/{id}/tml", (string id) => +{ + if (api == null) return clientNotConfigured(); + var request = new ExportMetadataTMLRequest( + metadata: new List + { + new ExportMetadataTypeInput(type: ExportMetadataTypeInput.TypeEnum.LIVEBOARD, identifier: id) + }, + exportFqn: true, + edocFormat: ExportMetadataTMLRequest.EdocFormatEnum.JSON); + + try + { + List tml = api.ExportMetadataTML(request); + // The SDK deserializes this response body with Newtonsoft.Json, so each + // entry in `tml` is really a Newtonsoft JObject/JArray/JValue. ASP.NET + // Core's default System.Text.Json serializer doesn't understand those + // types and silently writes them out as near-empty objects/arrays. + // Round-trip through Newtonsoft to get back well-formed JSON text. + string json = Newtonsoft.Json.JsonConvert.SerializeObject(tml); + return Results.Content(json, "application/json"); + } + catch (ApiException ex) + { + return Results.Json(new { error = FriendlyError(ex) }, statusCode: ex.ErrorCode); + } +}); + +// 4. Spotter — ask a natural-language question, streamed live via SSE ----------- +// +// Creates a fresh agent conversation per question, then relays the +// Server-Sent Events from SendAgentConversationMessageStreamingStreamAsync +// straight through to the browser as they arrive (no buffering the full +// answer server-side first). Each upstream `data: [...]` line is +// forwarded verbatim as the default SSE "message" event; a final custom +// "done" event (or "ts-error" on failure) tells the frontend when to stop +// listening. +app.MapGet("/api/spotter/stream", async (HttpContext ctx, string query, string? worksheetId) => +{ + if (api == null) + { + ctx.Response.StatusCode = 503; + await ctx.Response.WriteAsJsonAsync(new { error = "ThoughtSpot client not configured. See .env.example or set TS_USER/TS_PASS." }); + return; + } + + string metadataId = string.IsNullOrWhiteSpace(worksheetId) ? spotterWorksheetId : worksheetId; + if (string.IsNullOrWhiteSpace(query) || string.IsNullOrWhiteSpace(metadataId)) + { + ctx.Response.StatusCode = 400; + await ctx.Response.WriteAsJsonAsync(new { error = "A 'query' and a worksheet id (VITE_SPOTTER_WORKSHEET_ID or ?worksheetId=) are required." }); + return; + } + + ctx.Response.ContentType = "text/event-stream"; + ctx.Response.Headers.CacheControl = "no-cache"; + ctx.Response.Headers["X-Accel-Buffering"] = "no"; + + async Task WriteEventAsync(string? eventName, string data) + { + if (!string.IsNullOrEmpty(eventName)) + { + await ctx.Response.WriteAsync($"event: {eventName}\n", ctx.RequestAborted); + } + await ctx.Response.WriteAsync($"data: {data}\n\n", ctx.RequestAborted); + await ctx.Response.Body.FlushAsync(ctx.RequestAborted); + } + + try + { + // Workaround: thoughtspot_rest_api_sdk's DataSourceContextInput always + // serializes its unused sibling fields (data_source_identifiers, guid) + // as explicit JSON nulls (still true as of 0.1.0-beta.7). This + // cluster's request validation rejects that shape (it falls back to + // expecting a `worksheet_context` payload instead). Bypassing the + // typed DataSourceContext property and writing the minimal object via + // AdditionalProperties avoids emitting those nulls. + var metadataContext = new ContextPayloadV2Input(type: ContextPayloadV2Input.TypeEnum.DATASOURCE); + metadataContext.AdditionalProperties["data_source_context"] = new Dictionary + { + ["data_source_identifier"] = metadataId, + }; + + AgentConversation conversation = api.CreateAgentConversation(new CreateAgentConversationRequest( + metadataContext: metadataContext, + conversationSettings: new ConversationSettingsInput())); + + var streamRequest = new SendAgentConversationMessageStreamingRequest(messages: new List { query }); + + await foreach (var payload in api.SendAgentConversationMessageStreamingStreamAsync( + conversation.ConversationIdentifier, streamRequest, ctx.RequestAborted)) + { + await WriteEventAsync(null, payload); + } + + await WriteEventAsync("done", "{}"); + } + catch (ApiException ex) + { + await WriteEventAsync("ts-error", System.Text.Json.JsonSerializer.Serialize(new { error = FriendlyError(ex) })); + } + catch (OperationCanceledException) + { + // Client navigated away / closed the EventSource — nothing to write. + } +}); + +app.Run(); + +public static class ThoughtSpotConfiguration +{ + public static string NormalizeHost(string? host) + { + if (string.IsNullOrWhiteSpace(host)) + { + return "https://try-everywhere.thoughtspot.cloud"; + } + + var trimmed = host.Trim(); + if (!trimmed.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && + !trimmed.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + trimmed = $"https://{trimmed}"; + } + + if (!Uri.TryCreate(trimmed, UriKind.Absolute, out var uri)) + { + throw new ArgumentException($"The TS_HOST value '{host}' is not a valid URL.", nameof(host)); + } + + var path = uri.AbsolutePath.Trim('/'); + if (path.Equals("v2", StringComparison.OrdinalIgnoreCase)) + { + path = string.Empty; + } + + var builder = new UriBuilder(uri) + { + Path = string.IsNullOrEmpty(path) ? string.Empty : $"/{path}", + Query = string.Empty, + Fragment = string.Empty + }; + + return builder.Uri.GetLeftPart(UriPartial.Authority) + (builder.Path.Length > 1 ? builder.Path : string.Empty); + } +} + diff --git a/rest-api/csharp-sdk/frontend/index.html b/rest-api/csharp-sdk/frontend/index.html new file mode 100644 index 0000000..c937fc6 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + ThoughtSpot C# SDK Demo + + +
+ + + diff --git a/rest-api/csharp-sdk/frontend/package-lock.json b/rest-api/csharp-sdk/frontend/package-lock.json new file mode 100644 index 0000000..511a750 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/package-lock.json @@ -0,0 +1,1680 @@ +{ + "name": "thoughtspot-sdk-demo-frontend", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "thoughtspot-sdk-demo-frontend", + "version": "0.0.1", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.1", + "vite": "^5.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.42", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", + "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.387", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.387.tgz", + "integrity": "sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/rest-api/csharp-sdk/frontend/package.json b/rest-api/csharp-sdk/frontend/package.json new file mode 100644 index 0000000..4190854 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "thoughtspot-sdk-demo-frontend", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.1", + "vite": "^5.4.0" + } +} diff --git a/rest-api/csharp-sdk/frontend/public/ts-logo.svg b/rest-api/csharp-sdk/frontend/public/ts-logo.svg new file mode 100644 index 0000000..c134264 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/public/ts-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/rest-api/csharp-sdk/frontend/src/App.css b/rest-api/csharp-sdk/frontend/src/App.css new file mode 100644 index 0000000..11e3cd1 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/App.css @@ -0,0 +1,698 @@ +:root { + color-scheme: light dark; + --bg: #f2f3fa; + --surface: #ffffff; + --surface-solid: #ffffff; + --surface-hover: #f8f8fd; + --border: #e6e7f0; + --text: #14141c; + --text-muted: #6b6f80; + --accent: #5b5bf6; + --accent-2: #ec4899; + --accent-hover: #4747e0; + --accent-soft: #eeeeff; + --danger: #dc2626; + --danger-soft: rgba(220, 38, 38, 0.08); + --radius-lg: 20px; + --radius-md: 12px; + --radius-sm: 8px; + --shadow-sm: 0 1px 2px rgba(20, 20, 40, 0.05); + --shadow-md: 0 10px 30px rgba(30, 30, 60, 0.10); + --shadow-lg: 0 18px 45px rgba(30, 30, 60, 0.16); + --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #0d0d12; + --surface: #1a1a20; + --surface-solid: #1a1a20; + --surface-hover: #202028; + --border: #2c2c36; + --text: #f2f2f5; + --text-muted: #9a9aa8; + --accent: #8484fb; + --accent-2: #f472b6; + --accent-hover: #a0a0ff; + --accent-soft: rgba(132, 132, 251, 0.14); + --danger: #f87171; + --danger-soft: rgba(248, 113, 113, 0.12); + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow-md: 0 10px 30px rgba(0, 0, 0, 0.35); + --shadow-lg: 0 18px 45px rgba(0, 0, 0, 0.5); + } +} + +* { + box-sizing: border-box; +} + +html { + background: var(--bg); +} + +body { + margin: 0; + background: var(--bg); + color: var(--text); + font-family: var(--font); + -webkit-font-smoothing: antialiased; + min-height: 100vh; + position: relative; + overflow-x: hidden; +} + +/* Soft aurora blobs behind the content — fixed, blurred, low-opacity so text + always stays readable on top. Pure CSS, no images/deps. */ +body::before, +body::after { + content: ""; + position: fixed; + width: 42vw; + height: 42vw; + max-width: 620px; + max-height: 620px; + border-radius: 50%; + filter: blur(90px); + z-index: 0; + pointer-events: none; + opacity: 0.5; +} + +body::before { + top: -14vw; + right: -10vw; + background: radial-gradient(circle, var(--accent), transparent 70%); +} + +body::after { + bottom: -18vw; + left: -12vw; + background: radial-gradient(circle, var(--accent-2), transparent 70%); + opacity: 0.35; +} + +#root { + position: relative; + z-index: 1; +} + +.app { + max-width: 880px; + margin: 0 auto; + padding: 56px 24px 72px; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(14px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.app-header { + display: flex; + align-items: center; + gap: 18px; + margin-bottom: 40px; + animation: fadeInUp 0.5s ease both; +} + +.app-badge { + width: 56px; + height: 56px; + border-radius: 15px; + background: #ffffff; + border: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: var(--shadow-lg); + padding: 10px; +} + +.app-logo { + width: 100%; + height: 100%; + object-fit: contain; +} + +.app-title { + font-size: 23px; + font-weight: 750; + margin: 0; + letter-spacing: -0.01em; +} + +.app-subtitle { + color: var(--text-muted); + font-size: 14px; + margin: 3px 0 0; +} + +.app-pills { + display: flex; + gap: 6px; + margin-top: 10px; +} + +.pill { + font-size: 11px; + font-weight: 600; + padding: 3px 9px; + border-radius: 999px; + background: var(--accent-soft); + color: var(--accent); + letter-spacing: 0.02em; +} + +.card { + position: relative; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 26px; + margin-bottom: 22px; + box-shadow: var(--shadow-sm); + transition: box-shadow 0.25s ease, border-color 0.25s ease, transform 0.25s ease; + animation: fadeInUp 0.5s ease both; + overflow: hidden; +} + +.card::before { + content: ""; + position: absolute; + inset: 0 0 auto 0; + height: 3px; + background: linear-gradient(90deg, var(--accent), var(--accent-2)); + opacity: 0; + transition: opacity 0.25s ease; +} + +.card:hover { + box-shadow: var(--shadow-lg); + border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); + transform: translateY(-2px); +} + +.card:hover::before { + opacity: 1; +} + +.card:nth-of-type(1) { + animation-delay: 0.02s; +} +.card:nth-of-type(2) { + animation-delay: 0.09s; +} +.card:nth-of-type(3) { + animation-delay: 0.16s; +} + +.card-header { + display: flex; + align-items: flex-start; + gap: 14px; +} + +.card-icon { + width: 40px; + height: 40px; + border-radius: 12px; + background: var(--accent-soft); + color: var(--accent); + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + flex-shrink: 0; + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent); +} + +.card-title { + font-size: 16px; + font-weight: 650; + margin: 0; +} + +.card-desc { + color: var(--text-muted); + font-size: 13px; + margin: 5px 0 0; + line-height: 1.55; +} + +.card-body { + margin-top: 18px; +} + +.field-row { + display: flex; + gap: 10px; +} + +.input-wrap { + position: relative; + flex: 1; + min-width: 0; + display: flex; + align-items: center; +} + +.input-icon { + position: absolute; + left: 12px; + color: var(--text-muted); + pointer-events: none; + display: flex; +} + +.input { + width: 100%; + min-width: 0; + padding: 11px 14px 11px 36px; + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--surface-solid); + color: var(--text); + font-size: 14px; + font-family: inherit; + transition: border-color 0.15s, box-shadow 0.15s; +} + +.input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 4px var(--accent-soft); +} + +.btn { + padding: 11px 20px; + border-radius: var(--radius-md); + border: none; + background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 70%, var(--accent-2))); + color: #fff; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: transform 0.12s ease, box-shadow 0.2s ease, filter 0.15s ease; + white-space: nowrap; + font-family: inherit; + box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 35%, transparent); +} + +.btn:hover:not(:disabled) { + transform: translateY(-1px); + filter: brightness(1.06); + box-shadow: 0 8px 20px color-mix(in srgb, var(--accent) 45%, transparent); +} + +.btn:active:not(:disabled) { + transform: translateY(0) scale(0.97); +} + +.btn:disabled { + opacity: 0.55; + cursor: not-allowed; + box-shadow: none; +} + +.btn-secondary { + background: var(--accent-soft); + color: var(--accent); + box-shadow: none; +} + +.btn-secondary:hover:not(:disabled) { + background: var(--border); + filter: none; + box-shadow: none; +} + +.btn-sm { + padding: 6px 12px; + font-size: 12.5px; + border-radius: var(--radius-sm); +} + +.error-banner { + background: var(--danger-soft); + color: var(--danger); + border: 1px solid rgba(220, 38, 38, 0.25); + padding: 10px 14px; + border-radius: var(--radius-md); + font-size: 13.5px; + margin-top: 14px; +} + +.section-label { + font-size: 11.5px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-muted); + margin: 20px 0 8px; +} + +.result-list { + list-style: none; + margin: 16px 0 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 8px; +} + +.result-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 14px; + border-radius: var(--radius-md); + background: var(--surface-solid); + border: 1px solid var(--border); + transition: background 0.15s, border-color 0.15s, transform 0.15s, box-shadow 0.15s; + animation: fadeInUp 0.35s ease both; +} + +.result-row:hover { + background: var(--surface-hover); + border-color: var(--accent); + transform: translateX(2px); + box-shadow: var(--shadow-sm); +} + +.result-main { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; +} + +.avatar { + width: 30px; + height: 30px; + border-radius: 50%; + background: linear-gradient(135deg, var(--accent), var(--accent-2)); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; + flex-shrink: 0; +} + +.result-name { + font-weight: 600; + font-size: 13.5px; +} + +.result-sub { + color: var(--text-muted); + font-size: 12px; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.empty-state { + color: var(--text-muted); + font-size: 13px; + padding: 18px 0 2px; + text-align: center; +} + +.code-block-wrap { + position: relative; + margin-top: 14px; +} + +.code-block-copy { + position: absolute; + top: 8px; + right: 8px; +} + +.code-block { + background: var(--surface-solid); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 14px 16px; + font-family: var(--mono); + font-size: 12px; + max-height: 260px; + overflow: auto; + white-space: pre; +} + +/* Custom scrollbars for the panels that actually scroll (webkit + Firefox) */ +.code-block, +.chat { + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; +} + +.code-block::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.code-block::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 8px; +} + +/* Spotter chat */ + +.chat { + margin-top: 18px; + display: flex; + flex-direction: column; + gap: 18px; +} + +.chat-empty { + text-align: center; + color: var(--text-muted); + font-size: 13px; + padding: 20px 0 4px; +} + +.exchange { + animation: fadeInUp 0.35s ease both; +} + +.msg-user-row { + display: flex; + justify-content: flex-end; + margin-bottom: 10px; +} + +.msg-user { + background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 70%, var(--accent-2))); + color: #fff; + padding: 10px 16px; + border-radius: 14px 14px 4px 14px; + max-width: 80%; + font-size: 14px; + line-height: 1.5; + box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 25%, transparent); +} + +.msg-assistant-row { + display: flex; + align-items: flex-start; + gap: 10px; +} + +.assistant-avatar { + width: 26px; + height: 26px; + border-radius: 8px; + background: var(--accent-soft); + color: var(--accent); + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + flex-shrink: 0; + margin-top: 2px; +} + +.msg-assistant { + flex: 1; + min-width: 0; +} + +.trace { + display: flex; + flex-direction: column; + gap: 0; + margin-bottom: 10px; + position: relative; + padding-left: 14px; + border-left: 2px solid var(--border); +} + +.trace-item { + display: flex; + align-items: flex-start; + gap: 8px; + color: var(--text-muted); + font-size: 12px; + line-height: 1.5; + padding: 3px 0; + position: relative; +} + +.trace-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent); + flex-shrink: 0; + margin-top: 5px; + margin-left: -19px; + box-shadow: 0 0 0 3px var(--surface-solid); +} + +.answer-bubble { + background: var(--surface-solid); + border: 1px solid var(--border); + border-radius: 14px 14px 14px 4px; + padding: 14px 18px; + font-size: 14px; + line-height: 1.6; + box-shadow: var(--shadow-sm); +} + +.answer-bubble p { + margin: 6px 0; +} + +.answer-bubble ul { + margin: 6px 0; + padding-left: 20px; +} + +.answer-bubble code { + background: var(--accent-soft); + padding: 1px 5px; + border-radius: 4px; + font-size: 0.9em; +} + +.answer-bubble h4, +.answer-bubble h5, +.answer-bubble h6 { + margin: 12px 0 4px; +} + +.table-wrap { + overflow-x: auto; + margin: 10px 0; +} + +.answer-bubble table { + border-collapse: collapse; + width: 100%; + font-size: 13px; +} + +.answer-bubble th, +.answer-bubble td { + border: 1px solid var(--border); + padding: 6px 10px; + text-align: left; +} + +.answer-bubble th { + background: var(--accent-soft); + font-weight: 650; +} + +.answer-bubble tr:hover td { + background: var(--surface-hover); +} + +.cursor { + display: inline-block; + width: 2px; + height: 1em; + background: var(--accent); + margin-left: 2px; + vertical-align: -2px; + animation: blink 1s step-start infinite; +} + +@keyframes blink { + 50% { + opacity: 0; + } +} + +.typing-dots { + display: inline-flex; + gap: 3px; + align-items: center; + padding: 2px 0; +} + +.typing-dots span { + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--text-muted); + animation: bounce 1.2s infinite ease-in-out; +} + +.typing-dots span:nth-child(2) { + animation-delay: 0.15s; +} + +.typing-dots span:nth-child(3) { + animation-delay: 0.3s; +} + +@keyframes bounce { + 0%, + 80%, + 100% { + transform: scale(0.6); + opacity: 0.4; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + +.app-footer { + text-align: center; + color: var(--text-muted); + font-size: 12.5px; + margin-top: 40px; + animation: fadeInUp 0.5s ease both; + animation-delay: 0.22s; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + } +} diff --git a/rest-api/csharp-sdk/frontend/src/App.jsx b/rest-api/csharp-sdk/frontend/src/App.jsx new file mode 100644 index 0000000..c23e13c --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/App.jsx @@ -0,0 +1,33 @@ +import "./App.css"; +import tsLogo from "/ts-logo.svg"; +import SearchUsersCard from "./components/SearchUsersCard.jsx"; +import LiveboardsCard from "./components/LiveboardsCard.jsx"; +import SpotterCard from "./components/SpotterCard.jsx"; + +export default function App() { + return ( +
+
+
+ ThoughtSpot logo +
+
+

ThoughtSpot C# SDK — Full-Stack Demo

+

ASP.NET Core backend + React frontend, wrapping thoughtspot_rest_api_sdk

+
+ C# + ASP.NET Core + React + Server-Sent Events +
+
+
+ + + + + +
Built on thoughtspot_rest_api_sdk — see backend/Program.cs for the REST calls behind each card.
+
+ ); +} diff --git a/rest-api/csharp-sdk/frontend/src/api.js b/rest-api/csharp-sdk/frontend/src/api.js new file mode 100644 index 0000000..fcedc49 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/api.js @@ -0,0 +1,61 @@ +// Thin fetch wrapper around the ASP.NET Core backend in ../backend. +// In Vite dev mode, an empty base uses the same origin and is proxied by vite.config.js. +const BASE = import.meta.env.VITE_API_BASE_URL ?? ""; + +async function handle(res) { + const body = await res.json().catch(() => null); + if (!res.ok) throw new Error(body?.error ?? res.statusText); + return body; +} + +export const api = { + searchUsers: (query, size = 10) => + fetch(`${BASE}/api/users?query=${encodeURIComponent(query ?? "")}&size=${size}`).then(handle), + + searchLiveboards: (query, size = 10) => + fetch(`${BASE}/api/liveboards?query=${encodeURIComponent(query ?? "")}&size=${size}`).then(handle), + + exportTml: (id) => fetch(`${BASE}/api/liveboards/${id}/tml`).then(handle), + + // Opens a live SSE connection to /api/spotter/stream and relays each + // ThoughtSpot agent event to the caller as it arrives — no waiting for + // the full answer before showing anything. + // onEvent(event) — one raw Spotter event object (type: ack | notification | text-chunk | text | answer | ...) + // onError(message) — a fatal error occurred; the stream is already closed + // onDone() — the agent finished responding; the stream is already closed + // Returns a `stop()` function that closes the connection early. + streamSpotter: (query, { onEvent, onError, onDone }) => { + const url = `${BASE}/api/spotter/stream?query=${encodeURIComponent(query)}`; + const source = new EventSource(url); + + source.onmessage = (e) => { + try { + const batch = JSON.parse(e.data); + batch.forEach((evt) => onEvent(evt)); + } catch { + // Ignore malformed/keepalive lines. + } + }; + + source.addEventListener("done", () => { + source.close(); + onDone(); + }); + + source.addEventListener("ts-error", (e) => { + const { error } = JSON.parse(e.data); + source.close(); + onError(error); + }); + + // Fires if the connection drops before a "done"/"ts-error" frame arrives + // (backend crashed, network blip, etc). EventSource retries by default, + // so close it explicitly to stop that and surface the failure instead. + source.onerror = () => { + source.close(); + onError("Lost connection to the backend while streaming."); + }; + + return () => source.close(); + }, +}; diff --git a/rest-api/csharp-sdk/frontend/src/components/LiveboardsCard.jsx b/rest-api/csharp-sdk/frontend/src/components/LiveboardsCard.jsx new file mode 100644 index 0000000..2b20e3b --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/components/LiveboardsCard.jsx @@ -0,0 +1,128 @@ +import { useState } from "react"; +import { api } from "../api.js"; +import { SearchIcon, CopyIcon } from "../icons.jsx"; + +export default function LiveboardsCard() { + const [query, setQuery] = useState(""); + const [liveboards, setLiveboards] = useState([]); + const [tml, setTml] = useState(null); + const [tmlForId, setTmlForId] = useState(null); + const [error, setError] = useState(null); + const [searching, setSearching] = useState(false); + const [exportingId, setExportingId] = useState(null); + const [searched, setSearched] = useState(false); + const [copied, setCopied] = useState(false); + + const search = async (e) => { + e.preventDefault(); + setError(null); + setSearching(true); + // A previous export shouldn't linger under a fresh set of results — + // it read as if the first result had already been opened for you. + setTml(null); + setTmlForId(null); + try { + setLiveboards(await api.searchLiveboards(query)); + } catch (err) { + setError(err.message); + } finally { + setSearching(false); + setSearched(true); + } + }; + + const loadTml = async (lb) => { + setError(null); + setTml(null); + setCopied(false); + setTmlForId(lb.id); + setExportingId(lb.id); + try { + setTml(await api.exportTml(lb.id)); + } catch (err) { + setError(err.message); + } finally { + setExportingId(null); + } + }; + + const copyTml = async () => { + await navigator.clipboard.writeText(JSON.stringify(tml, null, 2)); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }; + + return ( +
+
+
📊
+
+

Search & export liveboards

+

+ Search liveboards, then export any result's TML via ExportMetadataTML. +

+
+
+ +
+
+
+ + setQuery(e.target.value)} + /> +
+ +
+ + {error &&

{error}

} + + {liveboards.length > 0 && ( + <> +

Liveboards ({liveboards.length})

+
    + {liveboards.map((lb, i) => ( +
  • + {lb.name} + +
  • + ))} +
+ + )} + + {searched && !searching && liveboards.length === 0 && !error && ( +

No liveboards matched that filter.

+ )} + + {tml && ( +
+ +
+              {liveboards.find((lb) => lb.id === tmlForId)?.name ? (
+                
+                  // {liveboards.find((lb) => lb.id === tmlForId).name}
+                  {"\n"}
+                
+              ) : null}
+              {JSON.stringify(tml, null, 2)}
+            
+
+ )} +
+
+ ); +} diff --git a/rest-api/csharp-sdk/frontend/src/components/SearchUsersCard.jsx b/rest-api/csharp-sdk/frontend/src/components/SearchUsersCard.jsx new file mode 100644 index 0000000..7efd82a --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/components/SearchUsersCard.jsx @@ -0,0 +1,89 @@ +import { useState } from "react"; +import { api } from "../api.js"; +import { SearchIcon } from "../icons.jsx"; + +const initials = (name) => + (name ?? "?") + .trim() + .split(/\s+/) + .slice(0, 2) + .map((w) => w[0]?.toUpperCase()) + .join("") || "?"; + +export default function SearchUsersCard() { + const [query, setQuery] = useState(""); + const [users, setUsers] = useState([]); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + const [searched, setSearched] = useState(false); + + const search = async (e) => { + e.preventDefault(); + setError(null); + setLoading(true); + try { + setUsers(await api.searchUsers(query)); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + setSearched(true); + } + }; + + return ( +
+
+
👤
+
+

Search users

+

+ Calls SearchUsers against the configured cluster and lists matching accounts. +

+
+
+ +
+
+
+ + setQuery(e.target.value)} + /> +
+ +
+ + {error &&

{error}

} + + {users.length > 0 && ( + <> +

Users ({users.length})

+
    + {users.map((u, i) => ( +
  • +
    +
    {initials(u.displayName ?? u.name)}
    +
    +
    {u.displayName ?? u.name}
    +
    {u.name}
    +
    +
    +
  • + ))} +
+ + )} + + {searched && !loading && users.length === 0 && !error && ( +

No users matched that filter.

+ )} +
+
+ ); +} diff --git a/rest-api/csharp-sdk/frontend/src/components/SpotterCard.jsx b/rest-api/csharp-sdk/frontend/src/components/SpotterCard.jsx new file mode 100644 index 0000000..5f0b3af --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/components/SpotterCard.jsx @@ -0,0 +1,163 @@ +import { useEffect, useRef, useState } from "react"; +import { api } from "../api.js"; +import { renderMarkdown } from "../markdown.jsx"; +import { SearchIcon } from "../icons.jsx"; + +// Some "thinking" chunks echo raw tool output (dataset schemas, JSON blobs) +// that can run to several KB — fine for a backend log, unreadable as a +// single trace line. Clip it so the trace stays scannable. +const TRACE_CHAR_LIMIT = 160; +const clipTrace = (text) => + text.length > TRACE_CHAR_LIMIT ? `${text.slice(0, TRACE_CHAR_LIMIT).trim()}…` : text; + +// One row of the muted trace above the answer — tool calls and the model's +// "thinking" chunks, so the user can see what Spotter is doing while it works. +function TraceItem({ event }) { + if (event.type === "notification") { + return ( +
+ + {event.metadata?.tool_title ?? event.code} +
+ ); + } + if (event.type === "text-chunk" || event.type === "text") { + return ( +
+ + {clipTrace(event.content)} +
+ ); + } + return null; +} + +function Exchange({ entry }) { + const isAnswering = entry.streaming && entry.answer.length > 0; + const isThinking = entry.streaming && entry.answer.length === 0; + + return ( +
+
+
{entry.query}
+
+
+
+
+ {entry.events.length > 0 && ( +
+ {entry.events.map((evt, i) => )} +
+ )} + + {isThinking && ( +
+ + + +
+ )} + + {entry.answer && ( +
+ {renderMarkdown(entry.answer)} + {isAnswering && } +
+ )} + + {entry.error &&

{entry.error}

} +
+
+
+ ); +} + +export default function SpotterCard() { + const [query, setQuery] = useState("total sales by product type"); + const [history, setHistory] = useState([]); + const [streaming, setStreaming] = useState(false); + const stopRef = useRef(null); + const bottomRef = useRef(null); + + // Close any open stream if the user navigates away mid-answer. + useEffect(() => () => stopRef.current?.(), []); + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: "smooth", block: "end" }); + }, [history]); + + const ask = (e) => { + e.preventDefault(); + stopRef.current?.(); + + const id = `${Date.now()}-${history.length}`; + const askedQuery = query; + setHistory((h) => [...h, { id, query: askedQuery, events: [], answer: "", streaming: true, error: null }]); + setStreaming(true); + + const update = (patch) => + setHistory((h) => h.map((entry) => (entry.id === id ? { ...entry, ...patch(entry) } : entry))); + + stopRef.current = api.streamSpotter(askedQuery, { + onEvent: (evt) => { + const isThinking = evt.metadata?.type === "thinking"; + const isAnswerChunk = (evt.type === "text-chunk" || evt.type === "text") && !isThinking; + update((entry) => + isAnswerChunk + ? { answer: entry.answer + evt.content } + : { events: [...entry.events, evt] } + ); + }, + onError: (message) => { + update(() => ({ error: message, streaming: false })); + setStreaming(false); + }, + onDone: () => { + update(() => ({ streaming: false })); + setStreaming(false); + }, + }); + }; + + return ( +
+
+
+
+

Ask Spotter

+

+ Streams a Spotter agent conversation live via Server-Sent Events, against the + worksheet configured via VITE_SPOTTER_WORKSHEET_ID. +

+
+
+ +
+ {history.length === 0 &&

Ask a question about your data to get started.

} + + {history.length > 0 && ( +
+ {history.map((entry) => )} +
+
+ )} + +
+
+ + setQuery(e.target.value)} + required + /> +
+ +
+
+
+ ); +} diff --git a/rest-api/csharp-sdk/frontend/src/icons.jsx b/rest-api/csharp-sdk/frontend/src/icons.jsx new file mode 100644 index 0000000..387bd85 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/icons.jsx @@ -0,0 +1,17 @@ +export function SearchIcon() { + return ( + + + + + ); +} + +export function CopyIcon() { + return ( + + + + + ); +} diff --git a/rest-api/csharp-sdk/frontend/src/main.jsx b/rest-api/csharp-sdk/frontend/src/main.jsx new file mode 100644 index 0000000..569fdf2 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/main.jsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.jsx"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + +); diff --git a/rest-api/csharp-sdk/frontend/src/markdown.jsx b/rest-api/csharp-sdk/frontend/src/markdown.jsx new file mode 100644 index 0000000..02165f0 --- /dev/null +++ b/rest-api/csharp-sdk/frontend/src/markdown.jsx @@ -0,0 +1,97 @@ +// Minimal Markdown -> React renderer covering what Spotter answers actually +// use (tables, headings, bullet lists, bold/code) — avoids pulling in a full +// markdown library for a demo app. + +function renderInline(text, keyPrefix) { + const parts = []; + const re = /\*\*([^*]+)\*\*|`([^`]+)`/g; + let last = 0, match, i = 0; + while ((match = re.exec(text))) { + if (match.index > last) parts.push(text.slice(last, match.index)); + parts.push( + match[1] !== undefined + ? {match[1]} + : {match[2]} + ); + last = match.index + match[0].length; + } + parts.push(text.slice(last)); + return parts; +} + +const splitTableRow = (line) => + line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim()); + +const isTableSeparator = (line) => /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(line ?? ""); + +export function renderMarkdown(text) { + const lines = text.split("\n"); + const blocks = []; + let i = 0, key = 0; + + while (i < lines.length) { + const line = lines[i]; + + if (!line.trim()) { + i++; + continue; + } + + if (line.includes("|") && isTableSeparator(lines[i + 1])) { + const headers = splitTableRow(line); + i += 2; + const rows = []; + while (i < lines.length && lines[i].trim() && lines[i].includes("|")) { + rows.push(splitTableRow(lines[i])); + i++; + } + blocks.push( +
+ + + {headers.map((h, c) => )} + + + {rows.map((row, r) => ( + {row.map((cell, c) => )} + ))} + +
{renderInline(h, `h${c}`)}
{renderInline(cell, `${r}-${c}`)}
+
+ ); + continue; + } + + const heading = /^(#{1,3})\s+(.*)/.exec(line); + if (heading) { + const Tag = `h${Math.min(heading[1].length + 3, 6)}`; + blocks.push({renderInline(heading[2], `hd${key}`)}); + i++; + continue; + } + + if (/^\s*[-*]\s+/.test(line)) { + const items = []; + while (i < lines.length && /^\s*[-*]\s+/.test(lines[i])) { + items.push(lines[i].replace(/^\s*[-*]\s+/, "")); + i++; + } + blocks.push( +
    + {items.map((item, idx) =>
  • {renderInline(item, `li${key}-${idx}`)}
  • )} +
+ ); + continue; + } + + const paraLines = [line]; + i++; + while (i < lines.length && lines[i].trim() && !lines[i].includes("|") && !/^\s*[-*#]/.test(lines[i])) { + paraLines.push(lines[i]); + i++; + } + blocks.push(

{renderInline(paraLines.join(" "), `p${key}`)}

); + } + + return blocks; +} diff --git a/rest-api/csharp-sdk/frontend/vite.config.js b/rest-api/csharp-sdk/frontend/vite.config.js new file mode 100644 index 0000000..780a7da --- /dev/null +++ b/rest-api/csharp-sdk/frontend/vite.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +const backendTarget = process.env.VITE_BACKEND_URL || "http://127.0.0.1:5000"; + +export default defineConfig({ + plugins: [react()], + server: { + host: "0.0.0.0", + port: 5173, + proxy: { + "/api": { + target: backendTarget, + changeOrigin: true, + secure: false, + }, + }, + }, +});