harryagasi commited on
Commit
0b6365f
·
1 Parent(s): 919f480

feat: polish workspace navigation and PWA branding

Browse files

- add PWA manifest, service worker, and install icons
- improve mobile navigation and report panel behavior
- update login and sidebar branding to use public logo
- remove admin diagnostics from home dashboard
- rename package scope to @btech /dataeyond

.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ public/logo.png filter=lfs diff=lfs merge=lfs -text
index.html CHANGED
@@ -1,16 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
- <!DOCTYPE html>
3
- <html lang="en">
4
- <head>
5
- <meta charset="UTF-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <link rel="icon" type="image/png" href="/logo.png" />
8
- <title>Chatbot application</title>
9
- </head>
10
 
11
- <body>
12
- <div id="root"></div>
13
- <script type="module" src="/src/main.tsx"></script>
14
- </body>
15
- </html>
16
-
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
+ <meta name="description" content="Data Eyond workspace for knowledge setup, AI analysis, and report generation." />
7
+ <meta name="theme-color" content="#0f172a" />
8
+ <meta name="mobile-web-app-capable" content="yes" />
9
+ <meta name="apple-mobile-web-app-capable" content="yes" />
10
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
11
+ <meta name="apple-mobile-web-app-title" content="Data Eyond" />
12
+ <link rel="manifest" href="/manifest.webmanifest" />
13
+ <link rel="icon" href="/logo.png" />
14
+ <link rel="apple-touch-icon" href="/pwa-icon-192.png" />
15
+ <title>Data Eyond</title>
16
+ </head>
17
 
18
+ <body>
19
+ <div id="root"></div>
20
+ <script type="module" src="/src/main.tsx"></script>
21
+ </body>
22
+ </html>
 
 
 
23
 
 
 
 
 
 
 
package-lock.json CHANGED
@@ -1,11 +1,11 @@
1
  {
2
- "name": "@figma/my-make-file",
3
  "version": "0.0.1",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
- "name": "@figma/my-make-file",
9
  "version": "0.0.1",
10
  "dependencies": {
11
  "@emotion/react": "11.14.0",
@@ -7301,4 +7301,4 @@
7301
  }
7302
  }
7303
  }
7304
- }
 
1
  {
2
+ "name": "@btech/dataeyond",
3
  "version": "0.0.1",
4
  "lockfileVersion": 3,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
+ "name": "@btech/dataeyond",
9
  "version": "0.0.1",
10
  "dependencies": {
11
  "@emotion/react": "11.14.0",
 
7301
  }
7302
  }
7303
  }
7304
+ }
package.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "name": "@figma/my-make-file",
3
  "private": true,
4
  "version": "0.0.1",
5
  "type": "module",
@@ -93,4 +93,4 @@
93
  },
94
  "onlyBuiltDependencies": ["@tailwindcss/oxide", "esbuild"]
95
  }
96
- }
 
1
  {
2
+ "name": "@btech/dataeyond",
3
  "private": true,
4
  "version": "0.0.1",
5
  "type": "module",
 
93
  },
94
  "onlyBuiltDependencies": ["@tailwindcss/oxide", "esbuild"]
95
  }
96
+ }
public/logo.png CHANGED

Git LFS Details

  • SHA256: f00b18f668b1a782791ade3230c889dc091269ad7c9a6a203a84c2ac14f49880
  • Pointer size: 131 Bytes
  • Size of remote file: 248 kB
public/manifest.webmanifest ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Data Eyond Analysis Workspace",
3
+ "short_name": "Data Eyond",
4
+ "description": "Data Eyond workspace for knowledge setup, AI analysis, and report generation.",
5
+ "start_url": "/",
6
+ "scope": "/",
7
+ "display": "standalone",
8
+ "orientation": "portrait-primary",
9
+ "background_color": "#f8fafc",
10
+ "theme_color": "#0f172a",
11
+ "categories": ["business", "productivity", "utilities"],
12
+ "icons": [
13
+ {
14
+ "src": "/pwa-icon-192.png",
15
+ "sizes": "192x192",
16
+ "type": "image/png",
17
+ "purpose": "any"
18
+ },
19
+ {
20
+ "src": "/pwa-icon-512.png",
21
+ "sizes": "512x512",
22
+ "type": "image/png",
23
+ "purpose": "any maskable"
24
+ }
25
+ ]
26
+ }
public/pwa-icon-192.png ADDED
public/pwa-icon-512.png ADDED
public/sw.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const CACHE_NAME = "dataeyond-pwa-v1";
2
+ const APP_SHELL = ["/", "/index.html", "/manifest.webmanifest", "/pwa-icon-192.png", "/pwa-icon-512.png"];
3
+
4
+ self.addEventListener("install", (event) => {
5
+ event.waitUntil(
6
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)).then(() => self.skipWaiting())
7
+ );
8
+ });
9
+
10
+ self.addEventListener("activate", (event) => {
11
+ event.waitUntil(
12
+ caches
13
+ .keys()
14
+ .then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))))
15
+ .then(() => self.clients.claim())
16
+ );
17
+ });
18
+
19
+ self.addEventListener("fetch", (event) => {
20
+ const request = event.request;
21
+ if (request.method !== "GET") return;
22
+
23
+ const url = new URL(request.url);
24
+ if (url.origin !== self.location.origin) return;
25
+
26
+ if (request.mode === "navigate") {
27
+ event.respondWith(
28
+ fetch(request)
29
+ .then((response) => {
30
+ const copy = response.clone();
31
+ caches.open(CACHE_NAME).then((cache) => cache.put("/index.html", copy));
32
+ return response;
33
+ })
34
+ .catch(() => caches.match("/index.html"))
35
+ );
36
+ return;
37
+ }
38
+
39
+ event.respondWith(
40
+ caches.match(request).then((cached) => {
41
+ if (cached) return cached;
42
+ return fetch(request).then((response) => {
43
+ if (response.ok) {
44
+ const copy = response.clone();
45
+ caches.open(CACHE_NAME).then((cache) => cache.put(request, copy));
46
+ }
47
+ return response;
48
+ });
49
+ })
50
+ );
51
+ });
specs/001-unified-analysis-chat/quickstart.md CHANGED
@@ -108,8 +108,6 @@ npm run build
108
  - 2026-06-30 service smoke check: Python service at `http://localhost:7860` is reachable, but its OpenAPI exposes legacy `/api/v1/chat/stream` and room endpoints, not the required `/api/v2/chat/stream`, `/api/v1/tools/help`, `/api/v1/tools/report`, or `/api/v1/observability`; set `VITE_AGENTIC_API_BASE_URL` to the agentic service that matches `API_CONTRACT_BE_PYTHON.md` before validating chat/help/report/traceability.- 2026-06-30 credential validation: Login and refresh passed with the provided test account; Golang returned `data.user`, `access_token`, `refresh_token`, `expires_in`, and `refresh_expires_in` as expected.
109
  - 2026-06-30 knowledge validation: Golang document type listing, database type listing, protected document listing, document upload, document process, and user catalog rebuild were exercised successfully. A smoke `.txt` document reached `completed` status.
110
  - 2026-06-30 analysis validation blocker: `POST /api/v1/analyses` failed with `400` and message `pq: unsupported jsonb version number 91 (XX000)` for a contract-shaped raw JSON payload containing `analysis_title`, `objective`, `business_questions`, and `data_bind`. New Analysis, message persistence, analysis history, chat, Help, report, and traceability validation remain blocked until the Golang analysis create endpoint is fixed.
111
- - 2026-06-30 frontend hardening: Home now includes backend diagnostics for Golang auth/protected endpoints and Python agentic OpenAPI contract paths; New Analysis now translates the JSONB backend failure into an actionable UI error.- Manual backend flow validation remains pending until the Golang and Python services are running with real credentials and data sources.
112
-
113
-
114
-
115
-
 
108
  - 2026-06-30 service smoke check: Python service at `http://localhost:7860` is reachable, but its OpenAPI exposes legacy `/api/v1/chat/stream` and room endpoints, not the required `/api/v2/chat/stream`, `/api/v1/tools/help`, `/api/v1/tools/report`, or `/api/v1/observability`; set `VITE_AGENTIC_API_BASE_URL` to the agentic service that matches `API_CONTRACT_BE_PYTHON.md` before validating chat/help/report/traceability.- 2026-06-30 credential validation: Login and refresh passed with the provided test account; Golang returned `data.user`, `access_token`, `refresh_token`, `expires_in`, and `refresh_expires_in` as expected.
109
  - 2026-06-30 knowledge validation: Golang document type listing, database type listing, protected document listing, document upload, document process, and user catalog rebuild were exercised successfully. A smoke `.txt` document reached `completed` status.
110
  - 2026-06-30 analysis validation blocker: `POST /api/v1/analyses` failed with `400` and message `pq: unsupported jsonb version number 91 (XX000)` for a contract-shaped raw JSON payload containing `analysis_title`, `objective`, `business_questions`, and `data_bind`. New Analysis, message persistence, analysis history, chat, Help, report, and traceability validation remain blocked until the Golang analysis create endpoint is fixed.
111
+ - 2026-06-30 frontend hardening: New Analysis now translates the JSONB backend failure into an actionable UI error.
112
+ - 2026-06-30 UX update: backend diagnostics were removed from Home because they are admin-facing and should not appear in the user workspace.
113
+ - Manual backend flow validation remains pending until the Golang and Python services are running with real credentials and data sources.
 
 
src/app/components/KnowledgeManagement.tsx CHANGED
@@ -350,37 +350,48 @@ export default function KnowledgeManagement({
350
  const renderStatus = (doc: ApiDocument) => {
351
  if (doc.status === "processed" || doc.status === "completed") {
352
  return (
353
- <div className="flex items-center gap-1.5 text-green-600">
354
- <Check className="w-3.5 h-3.5" />
355
- <span className="text-xs font-medium">Processed</span>
 
 
 
 
356
  </div>
357
  );
358
  }
359
 
360
  if (doc.status === "processing" || processing === doc.id) {
361
  return (
362
- <div className="flex items-center gap-1.5 text-blue-600">
363
- <Loader2 className="w-3.5 h-3.5 animate-spin" />
364
- <span className="text-xs">Processing...</span>
 
 
 
 
365
  </div>
366
  );
367
  }
368
 
 
369
  return (
370
  <button
 
 
 
371
  onClick={() => {
372
  const userId = getUserId();
373
  if (userId) processDocumentById(userId, doc.id);
374
  }}
375
  disabled={processing === doc.id}
376
- className="flex items-center gap-1.5 bg-gradient-to-r from-[#00C853] to-[#00A843] text-white px-3 py-1.5 rounded-lg hover:from-[#00A843] hover:to-[#00962B] transition disabled:opacity-50 disabled:cursor-not-allowed text-xs"
377
  >
378
- <Database className="w-3.5 h-3.5" />
379
- {doc.status === "failed" ? "Retry Process" : "Process to Knowledge"}
380
  </button>
381
  );
382
  };
383
-
384
  const loadCatalog = async (uid: string, rebuild = false) => {
385
  setLoadingCatalog(true);
386
  try {
@@ -603,7 +614,7 @@ export default function KnowledgeManagement({
603
  <button
604
  onClick={() => handleDeleteClient(client.id)}
605
  disabled={deletingClient === client.id}
606
- className="opacity-0 group-hover:opacity-100 text-slate-300 hover:text-red-500 transition disabled:opacity-30"
607
  title="Delete connection"
608
  >
609
  {deletingClient === client.id ? (
@@ -668,12 +679,12 @@ export default function KnowledgeManagement({
668
  {formatFileSize(doc.file_size)} · {formatDate(doc.created_at)}
669
  </p>
670
  </div>
671
- <div className="flex items-center gap-2 flex-shrink-0">
672
  {renderStatus(doc)}
673
  <button
674
  onClick={() => handleDeleteDocument(doc.id)}
675
  disabled={deleting === doc.id}
676
- className="opacity-0 group-hover:opacity-100 text-slate-300 hover:text-red-500 transition disabled:opacity-30"
677
  title="Delete"
678
  >
679
  {deleting === doc.id ? (
 
350
  const renderStatus = (doc: ApiDocument) => {
351
  if (doc.status === "processed" || doc.status === "completed") {
352
  return (
353
+ <div
354
+ title="Document is processed and ready for analysis"
355
+ aria-label="Document processed"
356
+ className="flex h-8 w-8 items-center justify-center rounded-lg bg-green-50 text-green-600 sm:w-auto sm:gap-1.5 sm:px-2"
357
+ >
358
+ <Check className="h-3.5 w-3.5" />
359
+ <span className="hidden text-xs font-medium sm:inline">Processed</span>
360
  </div>
361
  );
362
  }
363
 
364
  if (doc.status === "processing" || processing === doc.id) {
365
  return (
366
+ <div
367
+ title="Document is being processed"
368
+ aria-label="Document processing"
369
+ className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600 sm:w-auto sm:gap-1.5 sm:px-2"
370
+ >
371
+ <Loader2 className="h-3.5 w-3.5 animate-spin" />
372
+ <span className="hidden text-xs sm:inline">Processing</span>
373
  </div>
374
  );
375
  }
376
 
377
+ const isRetry = doc.status === "failed";
378
  return (
379
  <button
380
+ type="button"
381
+ title={isRetry ? "Retry document processing" : "Process document to knowledge"}
382
+ aria-label={isRetry ? "Retry document processing" : "Process document to knowledge"}
383
  onClick={() => {
384
  const userId = getUserId();
385
  if (userId) processDocumentById(userId, doc.id);
386
  }}
387
  disabled={processing === doc.id}
388
+ className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-600 text-white transition hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto sm:gap-1.5 sm:px-2.5"
389
  >
390
+ <Database className="h-3.5 w-3.5" />
391
+ <span className="hidden text-xs font-medium sm:inline">{isRetry ? "Retry" : "Process"}</span>
392
  </button>
393
  );
394
  };
 
395
  const loadCatalog = async (uid: string, rebuild = false) => {
396
  setLoadingCatalog(true);
397
  try {
 
614
  <button
615
  onClick={() => handleDeleteClient(client.id)}
616
  disabled={deletingClient === client.id}
617
+ className="flex h-8 w-8 items-center justify-center rounded-lg text-slate-300 opacity-100 transition hover:bg-red-50 hover:text-red-500 disabled:opacity-30 sm:h-auto sm:w-auto sm:opacity-0 sm:group-hover:opacity-100"
618
  title="Delete connection"
619
  >
620
  {deletingClient === client.id ? (
 
679
  {formatFileSize(doc.file_size)} · {formatDate(doc.created_at)}
680
  </p>
681
  </div>
682
+ <div className="flex flex-shrink-0 items-center gap-1.5 sm:gap-2">
683
  {renderStatus(doc)}
684
  <button
685
  onClick={() => handleDeleteDocument(doc.id)}
686
  disabled={deleting === doc.id}
687
+ className="flex h-8 w-8 items-center justify-center rounded-lg text-slate-300 opacity-100 transition hover:bg-red-50 hover:text-red-500 disabled:opacity-30 sm:h-auto sm:w-auto sm:opacity-0 sm:group-hover:opacity-100"
688
  title="Delete"
689
  >
690
  {deleting === doc.id ? (
src/app/components/Login.tsx CHANGED
@@ -1,8 +1,9 @@
1
- import { useState } from "react";
2
  import { useNavigate } from "react-router";
3
  import { Loader2, LogIn } from "lucide-react";
4
  import { login, persistAuthData } from "@/services/orchestrationApi";
5
- import logoUrl from "../../assets/logo.png";
 
6
 
7
  export default function Login() {
8
  const [email, setEmail] = useState("");
@@ -42,7 +43,9 @@ export default function Login() {
42
  <section className="grid w-full max-w-5xl overflow-hidden rounded-lg border border-slate-200 bg-white shadow-xl md:grid-cols-[1fr_24rem]">
43
  <div className="hidden bg-slate-950 p-10 text-white md:flex md:flex-col md:justify-between">
44
  <div className="flex items-center gap-3">
45
- <img src={logoUrl} alt="Data Eyond" className="h-9 w-9 object-contain" />
 
 
46
  <div>
47
  <p className="text-sm font-semibold">Data Eyond</p>
48
  <p className="text-xs text-slate-400">Analysis workspace</p>
@@ -56,7 +59,9 @@ export default function Login() {
56
 
57
  <div className="p-6 sm:p-8">
58
  <div className="mb-8 flex items-center gap-3 md:hidden">
59
- <img src={logoUrl} alt="Data Eyond" className="h-9 w-9 object-contain" />
 
 
60
  <div>
61
  <p className="text-sm font-semibold text-slate-900">Data Eyond</p>
62
  <p className="text-xs text-slate-500">Analysis workspace</p>
 
1
+ import { useState } from "react";
2
  import { useNavigate } from "react-router";
3
  import { Loader2, LogIn } from "lucide-react";
4
  import { login, persistAuthData } from "@/services/orchestrationApi";
5
+
6
+ const logoUrl = "/logo.png";
7
 
8
  export default function Login() {
9
  const [email, setEmail] = useState("");
 
43
  <section className="grid w-full max-w-5xl overflow-hidden rounded-lg border border-slate-200 bg-white shadow-xl md:grid-cols-[1fr_24rem]">
44
  <div className="hidden bg-slate-950 p-10 text-white md:flex md:flex-col md:justify-between">
45
  <div className="flex items-center gap-3">
46
+ <span className="flex h-12 w-12 shrink-0 items-center justify-center rounded-md border border-slate-200 bg-white p-2 shadow-sm">
47
+ <img src={logoUrl} alt="Data Eyond" className="h-full w-full object-contain" />
48
+ </span>
49
  <div>
50
  <p className="text-sm font-semibold">Data Eyond</p>
51
  <p className="text-xs text-slate-400">Analysis workspace</p>
 
59
 
60
  <div className="p-6 sm:p-8">
61
  <div className="mb-8 flex items-center gap-3 md:hidden">
62
+ <span className="flex h-12 w-12 shrink-0 items-center justify-center rounded-md border border-slate-200 bg-white p-2 shadow-sm">
63
+ <img src={logoUrl} alt="Data Eyond" className="h-full w-full object-contain" />
64
+ </span>
65
  <div>
66
  <p className="text-sm font-semibold text-slate-900">Data Eyond</p>
67
  <p className="text-xs text-slate-500">Analysis workspace</p>
src/app/components/analysis/AnalysisShell.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import { useEffect, useMemo, useState } from "react";
2
  import { useNavigate } from "react-router";
3
- import { PanelRightOpen } from "lucide-react";
4
  import { toast } from "sonner";
5
  import KnowledgeManagement from "../KnowledgeManagement";
6
  import {
@@ -45,6 +45,8 @@ export function AnalysisShell() {
45
  const navigate = useNavigate();
46
  const session = useMemo(() => getCurrentSession(), []);
47
  const [activeMenu, setActiveMenu] = useState<AppMenuKey>("home");
 
 
48
  const [analyses, setAnalyses] = useState<Analysis[]>([]);
49
  const [activeAnalysis, setActiveAnalysis] = useState<Analysis | null>(null);
50
  const [messages, setMessages] = useState<UiMessage[]>([]);
@@ -278,6 +280,15 @@ export function AnalysisShell() {
278
  navigate("/login", { replace: true });
279
  };
280
 
 
 
 
 
 
 
 
 
 
281
  const renderAnalysisAgent = () => (
282
  <div className={cx("grid min-h-screen grid-cols-1", reportCollapsed ? "lg:grid-cols-[18rem_minmax(0,1fr)_3.5rem]" : "lg:grid-cols-[18rem_minmax(0,1fr)_23rem]")}>
283
  <AnalysisSidebar
@@ -314,7 +325,7 @@ export function AnalysisShell() {
314
  </button>
315
  </aside>
316
  ) : (
317
- <aside className="min-h-0 overflow-y-auto border-l border-slate-200 bg-white p-4">
318
  <ReportSidebar analysis={activeAnalysis} userId={session?.user_id} onCollapse={() => setReportCollapsed(true)} />
319
  </aside>
320
  )}
@@ -322,16 +333,85 @@ export function AnalysisShell() {
322
  );
323
 
324
  return (
325
- <div className="grid min-h-screen grid-cols-1 bg-slate-100 text-slate-900 lg:grid-cols-[16rem_minmax(0,1fr)]">
326
- <AppNavigation active={activeMenu} userName={session?.name} onChange={setActiveMenu} onLogout={logout} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
- <div className="min-w-0">
329
- {activeMenu === "home" && <HomeDashboard hasAnalyses={analyses.length > 0} onNavigate={setActiveMenu} />}
330
- {activeMenu === "knowledge" && <KnowledgeManagement open onClose={() => setActiveMenu("home")} variant="page" />}
331
- {activeMenu === "analysis-agent" && renderAnalysisAgent()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  </div>
333
 
334
  <NewAnalysisDialog open={newAnalysisOpen} onClose={() => setNewAnalysisOpen(false)} onCreated={handleCreated} />
335
  </div>
336
  );
337
- }
 
1
  import { useEffect, useMemo, useState } from "react";
2
  import { useNavigate } from "react-router";
3
+ import { FileText, Menu, PanelRightOpen, X } from "lucide-react";
4
  import { toast } from "sonner";
5
  import KnowledgeManagement from "../KnowledgeManagement";
6
  import {
 
45
  const navigate = useNavigate();
46
  const session = useMemo(() => getCurrentSession(), []);
47
  const [activeMenu, setActiveMenu] = useState<AppMenuKey>("home");
48
+ const [mobileNavOpen, setMobileNavOpen] = useState(false);
49
+ const [mobileReportOpen, setMobileReportOpen] = useState(false);
50
  const [analyses, setAnalyses] = useState<Analysis[]>([]);
51
  const [activeAnalysis, setActiveAnalysis] = useState<Analysis | null>(null);
52
  const [messages, setMessages] = useState<UiMessage[]>([]);
 
280
  navigate("/login", { replace: true });
281
  };
282
 
283
+ const changeMenu = (menu: AppMenuKey) => {
284
+ setActiveMenu(menu);
285
+ setMobileNavOpen(false);
286
+ setMobileReportOpen(false);
287
+ };
288
+
289
+ const activeMenuLabel =
290
+ activeMenu === "analysis-agent" ? "Analysis Agent" : activeMenu === "knowledge" ? "Knowledge" : "Home";
291
+
292
  const renderAnalysisAgent = () => (
293
  <div className={cx("grid min-h-screen grid-cols-1", reportCollapsed ? "lg:grid-cols-[18rem_minmax(0,1fr)_3.5rem]" : "lg:grid-cols-[18rem_minmax(0,1fr)_23rem]")}>
294
  <AnalysisSidebar
 
325
  </button>
326
  </aside>
327
  ) : (
328
+ <aside className="hidden min-h-0 overflow-y-auto border-l border-slate-200 bg-white p-4 lg:block">
329
  <ReportSidebar analysis={activeAnalysis} userId={session?.user_id} onCollapse={() => setReportCollapsed(true)} />
330
  </aside>
331
  )}
 
333
  );
334
 
335
  return (
336
+ <div className="min-h-screen bg-slate-100 text-slate-900">
337
+ <header className="sticky top-0 z-40 flex h-14 items-center justify-between border-b border-slate-200 bg-white px-3 lg:hidden">
338
+ <button
339
+ type="button"
340
+ title="Open workspace menu"
341
+ aria-label="Open workspace menu"
342
+ onClick={() => setMobileNavOpen(true)}
343
+ className="flex h-10 w-10 items-center justify-center rounded-md text-slate-600 hover:bg-slate-100 hover:text-slate-950"
344
+ >
345
+ <Menu className="h-5 w-5" />
346
+ </button>
347
+ <div className="min-w-0 text-center">
348
+ <p className="truncate text-sm font-semibold text-slate-950">{activeMenuLabel}</p>
349
+ <p className="text-xs text-slate-500">Data Eyond</p>
350
+ </div>
351
+ {activeMenu === "analysis-agent" ? (
352
+ <button
353
+ type="button"
354
+ title="Open report panel"
355
+ aria-label="Open report panel"
356
+ onClick={() => setMobileReportOpen(true)}
357
+ className="flex h-10 w-10 items-center justify-center rounded-md text-slate-600 hover:bg-slate-100 hover:text-slate-950"
358
+ >
359
+ <FileText className="h-5 w-5" />
360
+ </button>
361
+ ) : (
362
+ <div className="h-10 w-10" />
363
+ )}
364
+ </header>
365
 
366
+ {mobileNavOpen && (
367
+ <div className="fixed inset-0 z-50 lg:hidden" role="dialog" aria-modal="true" aria-label="Workspace navigation">
368
+ <button
369
+ type="button"
370
+ aria-label="Close workspace menu overlay"
371
+ onClick={() => setMobileNavOpen(false)}
372
+ className="absolute inset-0 bg-slate-950/40"
373
+ />
374
+ <div className="relative h-full w-72 max-w-[86vw] bg-white shadow-2xl">
375
+ <AppNavigation active={activeMenu} userName={session?.name} onChange={changeMenu} onLogout={logout} />
376
+ <button
377
+ type="button"
378
+ title="Close workspace menu"
379
+ aria-label="Close workspace menu"
380
+ onClick={() => setMobileNavOpen(false)}
381
+ className="absolute right-3 top-3 flex h-9 w-9 items-center justify-center rounded-md text-slate-500 hover:bg-slate-100 hover:text-slate-950"
382
+ >
383
+ <X className="h-4 w-4" />
384
+ </button>
385
+ </div>
386
+ </div>
387
+ )}
388
+ {mobileReportOpen && activeMenu === "analysis-agent" && (
389
+ <div className="fixed inset-0 z-50 lg:hidden" role="dialog" aria-modal="true" aria-label="Report panel">
390
+ <button
391
+ type="button"
392
+ aria-label="Close report panel overlay"
393
+ onClick={() => setMobileReportOpen(false)}
394
+ className="absolute inset-0 bg-slate-950/40"
395
+ />
396
+ <div className="absolute inset-x-0 bottom-0 max-h-[86vh] overflow-y-auto rounded-t-xl bg-white p-4 shadow-2xl">
397
+ <ReportSidebar analysis={activeAnalysis} userId={session?.user_id} onCollapse={() => setMobileReportOpen(false)} />
398
+ </div>
399
+ </div>
400
+ )}
401
+
402
+ <div className="lg:grid lg:min-h-screen lg:grid-cols-[16rem_minmax(0,1fr)]">
403
+ <div className="hidden lg:block">
404
+ <AppNavigation active={activeMenu} userName={session?.name} onChange={setActiveMenu} onLogout={logout} />
405
+ </div>
406
+
407
+ <div className="min-w-0">
408
+ {activeMenu === "home" && <HomeDashboard hasAnalyses={analyses.length > 0} onNavigate={changeMenu} />}
409
+ {activeMenu === "knowledge" && <KnowledgeManagement open onClose={() => changeMenu("home")} variant="page" />}
410
+ {activeMenu === "analysis-agent" && renderAnalysisAgent()}
411
+ </div>
412
  </div>
413
 
414
  <NewAnalysisDialog open={newAnalysisOpen} onClose={() => setNewAnalysisOpen(false)} onCreated={handleCreated} />
415
  </div>
416
  );
417
+ }
src/app/components/analysis/AppNavigation.tsx CHANGED
@@ -42,8 +42,8 @@ export function AppNavigation({ active, userName, onChange, onLogout }: AppNavig
42
  return (
43
  <aside className="flex min-h-screen w-full flex-col border-r border-slate-200 bg-white px-3 py-4 text-slate-900">
44
  <div className="flex items-center gap-3 px-2 pb-5">
45
- <div className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-slate-950 text-sm font-semibold text-white">
46
- DE
47
  </div>
48
  <div className="min-w-0">
49
  <p className="truncate text-sm font-semibold leading-none">Data Eyond</p>
 
42
  return (
43
  <aside className="flex min-h-screen w-full flex-col border-r border-slate-200 bg-white px-3 py-4 text-slate-900">
44
  <div className="flex items-center gap-3 px-2 pb-5">
45
+ <div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md border border-slate-200 bg-white p-1.5 shadow-sm">
46
+ <img src="/logo.png" alt="Data Eyond" className="h-full w-full object-contain" />
47
  </div>
48
  <div className="min-w-0">
49
  <p className="truncate text-sm font-semibold leading-none">Data Eyond</p>
src/app/components/analysis/BackendDiagnostics.tsx DELETED
@@ -1,127 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { AlertTriangle, CheckCircle2, Loader2, Server, XCircle } from "lucide-react";
3
- import { getDocumentTypes, listAnalyses } from "@/services/orchestrationApi";
4
- import { inspectAgenticContract, type AgenticContractStatus } from "@/services/agenticApi";
5
- import { getCurrentSession } from "./session";
6
- import { cx } from "./utils";
7
-
8
- type CheckState = "loading" | "ok" | "warning" | "error";
9
-
10
- interface CheckItem {
11
- label: string;
12
- state: CheckState;
13
- detail: string;
14
- }
15
-
16
- function StateIcon({ state }: { state: CheckState }) {
17
- if (state === "loading") return <Loader2 className="h-4 w-4 animate-spin text-slate-400" />;
18
- if (state === "ok") return <CheckCircle2 className="h-4 w-4 text-emerald-600" />;
19
- if (state === "warning") return <AlertTriangle className="h-4 w-4 text-amber-600" />;
20
- return <XCircle className="h-4 w-4 text-red-600" />;
21
- }
22
-
23
- function summarizeAgentic(status: AgenticContractStatus): CheckItem {
24
- if (!status.reachable) {
25
- return {
26
- label: "Python agentic backend",
27
- state: "error",
28
- detail: status.error ? `Not reachable: ${status.error}` : "Not reachable from the browser.",
29
- };
30
- }
31
-
32
- if (!status.matchesContract) {
33
- const legacy = status.legacyPaths.length > 0 ? ` Legacy paths detected: ${status.legacyPaths.join(", ")}.` : "";
34
- return {
35
- label: "Python agentic backend",
36
- state: "warning",
37
- detail: `Contract mismatch. Missing: ${status.missingPaths.join(", ")}.${legacy}`,
38
- };
39
- }
40
-
41
- return {
42
- label: "Python agentic backend",
43
- state: "ok",
44
- detail: "Required chat, Help, report, and observability endpoints are exposed.",
45
- };
46
- }
47
-
48
- export function BackendDiagnostics() {
49
- const [checks, setChecks] = useState<CheckItem[]>([
50
- { label: "Golang orchestration", state: "loading", detail: "Checking protected orchestration endpoints." },
51
- { label: "Python agentic backend", state: "loading", detail: "Checking OpenAPI contract paths." },
52
- ]);
53
-
54
- useEffect(() => {
55
- let cancelled = false;
56
-
57
- const run = async () => {
58
- const session = getCurrentSession();
59
- const next: CheckItem[] = [];
60
-
61
- if (!session?.access_token) {
62
- next.push({
63
- label: "Golang orchestration",
64
- state: "warning",
65
- detail: "Login is required before protected orchestration endpoints can be checked.",
66
- });
67
- } else {
68
- try {
69
- await Promise.all([getDocumentTypes(), listAnalyses({ status: "active", page: 1, limit: 1 })]);
70
- next.push({
71
- label: "Golang orchestration",
72
- state: "ok",
73
- detail: "Auth, knowledge metadata, and analysis listing endpoints are reachable.",
74
- });
75
- } catch (err) {
76
- next.push({
77
- label: "Golang orchestration",
78
- state: "error",
79
- detail: err instanceof Error ? err.message : "Protected orchestration check failed.",
80
- });
81
- }
82
- }
83
-
84
- next.push(summarizeAgentic(await inspectAgenticContract()));
85
- if (!cancelled) setChecks(next);
86
- };
87
-
88
- run();
89
- return () => {
90
- cancelled = true;
91
- };
92
- }, []);
93
-
94
- return (
95
- <section className="rounded-lg border border-slate-200 bg-white p-5 shadow-sm">
96
- <div className="mb-4 flex items-center gap-2">
97
- <Server className="h-4 w-4 text-slate-500" />
98
- <div>
99
- <h2 className="text-sm font-semibold text-slate-950">Backend diagnostics</h2>
100
- <p className="mt-1 text-xs text-slate-500">Quick read on whether the current `.env` matches the frontend flow.</p>
101
- </div>
102
- </div>
103
- <div className="space-y-2">
104
- {checks.map((check) => (
105
- <div
106
- key={check.label}
107
- className={cx(
108
- "rounded-md border px-3 py-3",
109
- check.state === "ok" && "border-emerald-200 bg-emerald-50",
110
- check.state === "warning" && "border-amber-200 bg-amber-50",
111
- check.state === "error" && "border-red-200 bg-red-50",
112
- check.state === "loading" && "border-slate-200 bg-slate-50"
113
- )}
114
- >
115
- <div className="flex items-start gap-2">
116
- <StateIcon state={check.state} />
117
- <div className="min-w-0">
118
- <p className="text-sm font-medium text-slate-900">{check.label}</p>
119
- <p className="mt-1 break-words text-xs leading-5 text-slate-600">{check.detail}</p>
120
- </div>
121
- </div>
122
- </div>
123
- ))}
124
- </div>
125
- </section>
126
- );
127
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/components/analysis/HomeDashboard.tsx CHANGED
@@ -1,6 +1,5 @@
1
  import { ArrowRight, BarChart3, BookOpen, FileText, PlayCircle } from "lucide-react";
2
  import type { AppMenuKey } from "./AppNavigation";
3
- import { BackendDiagnostics } from "./BackendDiagnostics";
4
 
5
  interface HomeDashboardProps {
6
  hasAnalyses: boolean;
@@ -67,8 +66,6 @@ export function HomeDashboard({ hasAnalyses, onNavigate }: HomeDashboardProps) {
67
  </div>
68
  </section>
69
 
70
- <BackendDiagnostics />
71
-
72
  <section className="rounded-lg border border-slate-200 bg-white p-5 shadow-sm">
73
  <div className="mb-4 flex items-center justify-between gap-3">
74
  <div>
@@ -102,5 +99,4 @@ export function HomeDashboard({ hasAnalyses, onNavigate }: HomeDashboardProps) {
102
  </div>
103
  </main>
104
  );
105
- }
106
-
 
1
  import { ArrowRight, BarChart3, BookOpen, FileText, PlayCircle } from "lucide-react";
2
  import type { AppMenuKey } from "./AppNavigation";
 
3
 
4
  interface HomeDashboardProps {
5
  hasAnalyses: boolean;
 
66
  </div>
67
  </section>
68
 
 
 
69
  <section className="rounded-lg border border-slate-200 bg-white p-5 shadow-sm">
70
  <div className="mb-4 flex items-center justify-between gap-3">
71
  <div>
 
99
  </div>
100
  </main>
101
  );
102
+ }
 
src/assets/logo.png DELETED
Binary file (10.2 kB)
 
src/main.tsx CHANGED
@@ -1,7 +1,14 @@
 
 
 
 
1
 
2
- import { createRoot } from "react-dom/client";
3
- import App from "./app/App.tsx";
4
- import "./styles/index.css";
5
- import "katex/dist/katex.min.css";
6
 
7
- createRoot(document.getElementById("root")!).render(<App />);
 
 
 
 
 
 
 
1
+ import { createRoot } from "react-dom/client";
2
+ import App from "./app/App.tsx";
3
+ import "./styles/index.css";
4
+ import "katex/dist/katex.min.css";
5
 
6
+ createRoot(document.getElementById("root")!).render(<App />);
 
 
 
7
 
8
+ if ("serviceWorker" in navigator && import.meta.env.PROD) {
9
+ window.addEventListener("load", () => {
10
+ navigator.serviceWorker.register("/sw.js").catch((error) => {
11
+ console.error("Service worker registration failed", error);
12
+ });
13
+ });
14
+ }
src/services/agenticApi.ts CHANGED
@@ -217,57 +217,4 @@ export const listReportVersions = (analysisId: string): Promise<ReportSummary[]>
217
 
218
  export const getReportVersion = (analysisId: string, version: number): Promise<ReportDetail> =>
219
  agenticJson(`/api/v1/tools/report/${encodeURIComponent(analysisId)}/${encodeURIComponent(String(version))}`);
220
- export interface AgenticContractStatus {
221
- reachable: boolean;
222
- matchesContract: boolean;
223
- missingPaths: string[];
224
- legacyPaths: string[];
225
- error?: string;
226
- }
227
-
228
- const requiredAgenticPaths = [
229
- "/api/v2/chat/stream",
230
- "/api/v1/tools/help",
231
- "/api/v1/tools/report",
232
- "/api/v1/tools/report/{analysis_id}",
233
- "/api/v1/tools/report/{analysis_id}/{version}",
234
- "/api/v1/observability",
235
- ];
236
-
237
- const legacyAgenticPaths = ["/api/v1/chat/stream", "/api/v1/room/create", "/api/v1/rooms/{user_id}"];
238
-
239
- export async function inspectAgenticContract(): Promise<AgenticContractStatus> {
240
- try {
241
- const res = await fetch(`${AGENTIC_BASE_URL}/openapi.json`);
242
- if (!res.ok) {
243
- return {
244
- reachable: false,
245
- matchesContract: false,
246
- missingPaths: requiredAgenticPaths,
247
- legacyPaths: [],
248
- error: `HTTP ${res.status}`,
249
- };
250
- }
251
-
252
- const openapi = (await res.json()) as { paths?: Record<string, unknown> };
253
- const paths = new Set(Object.keys(openapi.paths ?? {}));
254
- const missingPaths = requiredAgenticPaths.filter((path) => !paths.has(path));
255
- const detectedLegacyPaths = legacyAgenticPaths.filter((path) => paths.has(path));
256
-
257
- return {
258
- reachable: true,
259
- matchesContract: missingPaths.length === 0,
260
- missingPaths,
261
- legacyPaths: detectedLegacyPaths,
262
- };
263
- } catch (err) {
264
- return {
265
- reachable: false,
266
- matchesContract: false,
267
- missingPaths: requiredAgenticPaths,
268
- legacyPaths: [],
269
- error: err instanceof Error ? err.message : "Unable to inspect agentic backend",
270
- };
271
- }
272
- }
273
 
 
217
 
218
  export const getReportVersion = (analysisId: string, version: number): Promise<ReportDetail> =>
219
  agenticJson(`/api/v1/tools/report/${encodeURIComponent(analysisId)}/${encodeURIComponent(String(version))}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220