@optifye/dashboard-core 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ var dateFns = require('date-fns');
7
7
  var dateFnsTz = require('date-fns-tz');
8
8
  var supabaseJs = require('@supabase/supabase-js');
9
9
  var mixpanel = require('mixpanel-browser');
10
+ var useSWR = require('swr');
10
11
  var Hls2 = require('hls.js');
11
12
  var motionUtils = require('motion-utils');
12
13
  var motionDom = require('motion-dom');
@@ -19,7 +20,6 @@ var jsPDF = require('jspdf');
19
20
  var clientS3 = require('@aws-sdk/client-s3');
20
21
  var outline = require('@heroicons/react/24/outline');
21
22
  var SelectPrimitive = require('@radix-ui/react-select');
22
- var useSWR = require('swr');
23
23
  var sonner = require('sonner');
24
24
  var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
25
25
  var stream = require('stream');
@@ -46,11 +46,11 @@ function _interopNamespace(e) {
46
46
 
47
47
  var React46__namespace = /*#__PURE__*/_interopNamespace(React46);
48
48
  var mixpanel__default = /*#__PURE__*/_interopDefault(mixpanel);
49
+ var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
49
50
  var Hls2__default = /*#__PURE__*/_interopDefault(Hls2);
50
51
  var html2canvas__default = /*#__PURE__*/_interopDefault(html2canvas);
51
52
  var jsPDF__default = /*#__PURE__*/_interopDefault(jsPDF);
52
53
  var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
53
- var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
54
54
 
55
55
  var __create = Object.create;
56
56
  var __defProp = Object.defineProperty;
@@ -5641,7 +5641,9 @@ var DEFAULT_DATE_TIME_CONFIG = {
5641
5641
  }
5642
5642
  };
5643
5643
  var DEFAULT_ENDPOINTS_CONFIG = {
5644
- whatsapp: "/api/send-whatsapp-direct"
5644
+ whatsapp: "/api/send-whatsapp-direct",
5645
+ agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://optifye-agent-production.up.railway.app"
5646
+ // Default AGNO API URL
5645
5647
  };
5646
5648
  var DEFAULT_THEME_CONFIG = {
5647
5649
  // Sensible defaults for theme can be added here
@@ -8235,6 +8237,210 @@ var resetCoreMixpanel = () => {
8235
8237
  mixpanel__default.default.reset();
8236
8238
  };
8237
8239
 
8240
+ // src/lib/services/sseClient.ts
8241
+ var SSEChatClient = class {
8242
+ constructor(baseUrl) {
8243
+ this.controllers = /* @__PURE__ */ new Map();
8244
+ this.baseUrl = baseUrl || "";
8245
+ }
8246
+ async sendMessage(message, userId, threadId, context, callbacks) {
8247
+ const connectionId = `${threadId || "new"}-${Date.now()}`;
8248
+ if (threadId) {
8249
+ for (const [id3, controller2] of this.controllers.entries()) {
8250
+ if (id3.startsWith(threadId)) {
8251
+ controller2.abort();
8252
+ this.controllers.delete(id3);
8253
+ }
8254
+ }
8255
+ }
8256
+ const controller = new AbortController();
8257
+ this.controllers.set(connectionId, controller);
8258
+ console.log("[SSEClient] Sending message:", {
8259
+ message,
8260
+ thread_id: threadId,
8261
+ user_id: userId,
8262
+ context
8263
+ });
8264
+ const agnoApiUrl = this.baseUrl || "https://optifye-agent-production.up.railway.app";
8265
+ const endpoint = `${agnoApiUrl}/api/chat`;
8266
+ console.log("[SSEClient] Posting directly to AGNO:", endpoint);
8267
+ const response = await fetch(endpoint, {
8268
+ method: "POST",
8269
+ headers: {
8270
+ "Content-Type": "application/json",
8271
+ "Accept": "text/event-stream"
8272
+ },
8273
+ body: JSON.stringify({
8274
+ message,
8275
+ thread_id: threadId,
8276
+ user_id: userId,
8277
+ company_id: context.companyId,
8278
+ line_id: context.lineId,
8279
+ shift_id: context.shiftId
8280
+ }),
8281
+ signal: controller.signal,
8282
+ // Don't include credentials since the API returns Access-Control-Allow-Origin: *
8283
+ // credentials: 'include', // Include cookies for CORS
8284
+ mode: "cors"
8285
+ // Explicitly set CORS mode
8286
+ });
8287
+ console.log("[SSEClient] Response status:", response.status);
8288
+ console.log("[SSEClient] Response headers:", Object.fromEntries(response.headers.entries()));
8289
+ if (!response.ok) {
8290
+ let errorMessage = `Chat request failed with status ${response.status}`;
8291
+ try {
8292
+ const error = await response.json();
8293
+ errorMessage = error.detail || error.message || errorMessage;
8294
+ } catch (e) {
8295
+ try {
8296
+ const text = await response.text();
8297
+ if (text) errorMessage = text;
8298
+ } catch (textError) {
8299
+ }
8300
+ }
8301
+ console.error("[SSEClient] Error response:", errorMessage);
8302
+ throw new Error(errorMessage);
8303
+ }
8304
+ const contentType = response.headers.get("content-type");
8305
+ if (!contentType?.includes("text/event-stream")) {
8306
+ console.warn("[SSEClient] Unexpected content-type:", contentType);
8307
+ }
8308
+ try {
8309
+ await this.handleSSEStream(response, callbacks);
8310
+ } finally {
8311
+ this.controllers.delete(connectionId);
8312
+ }
8313
+ }
8314
+ async handleSSEStream(response, callbacks) {
8315
+ if (!response.body) {
8316
+ console.error("[SSEClient] Response body is null");
8317
+ throw new Error("No response body available for streaming");
8318
+ }
8319
+ const reader = response.body.getReader();
8320
+ const decoder = new TextDecoder();
8321
+ let buffer = "";
8322
+ try {
8323
+ console.log("[SSEClient] Starting to read stream...");
8324
+ while (true) {
8325
+ const { done, value } = await reader.read();
8326
+ if (done) {
8327
+ console.log("[SSEClient] Stream ended");
8328
+ break;
8329
+ }
8330
+ buffer += decoder.decode(value, { stream: true });
8331
+ const lines = buffer.split("\n");
8332
+ buffer = lines.pop() || "";
8333
+ for (let i = 0; i < lines.length; i++) {
8334
+ const line = lines[i].trim();
8335
+ if (!line) continue;
8336
+ console.log("[SSEClient] Processing line:", line);
8337
+ if (line.startsWith("event:")) {
8338
+ const event = line.slice(6).trim();
8339
+ console.log("[SSEClient] Event type:", event);
8340
+ const nextLine = lines[i + 1];
8341
+ if (nextLine?.startsWith("data:")) {
8342
+ const dataStr = nextLine.slice(5).trim();
8343
+ console.log("[SSEClient] Event data:", dataStr);
8344
+ try {
8345
+ const data = JSON.parse(dataStr);
8346
+ switch (event) {
8347
+ case "thread":
8348
+ callbacks.onThreadCreated?.(data.thread_id);
8349
+ break;
8350
+ case "message":
8351
+ callbacks.onMessage?.(data.text);
8352
+ break;
8353
+ case "reasoning":
8354
+ callbacks.onReasoning?.(data.text);
8355
+ break;
8356
+ case "complete":
8357
+ callbacks.onComplete?.(data.message_id);
8358
+ break;
8359
+ case "error":
8360
+ callbacks.onError?.(data.error);
8361
+ break;
8362
+ }
8363
+ } catch (e) {
8364
+ console.error("[SSEClient] Failed to parse data:", dataStr, e);
8365
+ }
8366
+ i++;
8367
+ }
8368
+ }
8369
+ }
8370
+ }
8371
+ } finally {
8372
+ reader.releaseLock();
8373
+ }
8374
+ }
8375
+ abort(threadId) {
8376
+ if (threadId) {
8377
+ for (const [id3, controller] of this.controllers.entries()) {
8378
+ if (id3.startsWith(threadId)) {
8379
+ controller.abort();
8380
+ this.controllers.delete(id3);
8381
+ }
8382
+ }
8383
+ } else {
8384
+ for (const [id3, controller] of this.controllers.entries()) {
8385
+ controller.abort();
8386
+ }
8387
+ this.controllers.clear();
8388
+ }
8389
+ }
8390
+ };
8391
+
8392
+ // src/lib/services/chatService.ts
8393
+ async function getUserThreads(userId, limit = 20) {
8394
+ const supabase = _getSupabaseInstance();
8395
+ const { data, error } = await supabase.schema("ai").from("chat_threads").select("*").eq("user_id", userId).order("updated_at", { ascending: false }).limit(limit);
8396
+ if (error) throw error;
8397
+ return data;
8398
+ }
8399
+ async function getUserThreadsPaginated(userId, page = 1, pageSize = 20) {
8400
+ const supabase = _getSupabaseInstance();
8401
+ const from = (page - 1) * pageSize;
8402
+ const to = from + pageSize - 1;
8403
+ const { data, error, count } = await supabase.schema("ai").from("chat_threads").select("*", { count: "exact" }).eq("user_id", userId).order("updated_at", { ascending: false }).range(from, to);
8404
+ if (error) throw error;
8405
+ return {
8406
+ threads: data,
8407
+ totalCount: count || 0,
8408
+ totalPages: Math.ceil((count || 0) / pageSize),
8409
+ currentPage: page
8410
+ };
8411
+ }
8412
+ async function getThreadMessages(threadId, limit = 50, beforePosition) {
8413
+ const supabase = _getSupabaseInstance();
8414
+ let query = supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
8415
+ if (beforePosition !== void 0) {
8416
+ query = query.lt("position", beforePosition);
8417
+ }
8418
+ const { data, error } = await query.limit(limit);
8419
+ if (error) throw error;
8420
+ return data;
8421
+ }
8422
+ async function getAllThreadMessages(threadId) {
8423
+ const supabase = _getSupabaseInstance();
8424
+ const { data, error } = await supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
8425
+ if (error) throw error;
8426
+ return data;
8427
+ }
8428
+ async function updateThreadTitle(threadId, newTitle) {
8429
+ const supabase = _getSupabaseInstance();
8430
+ const { data, error } = await supabase.schema("ai").from("chat_threads").update({
8431
+ title: newTitle,
8432
+ auto_title: false,
8433
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
8434
+ }).eq("id", threadId).select().single();
8435
+ if (error) throw error;
8436
+ return data;
8437
+ }
8438
+ async function deleteThread(threadId) {
8439
+ const supabase = _getSupabaseInstance();
8440
+ const { error } = await supabase.schema("ai").from("chat_threads").delete().eq("id", threadId);
8441
+ if (error) throw error;
8442
+ }
8443
+
8238
8444
  // src/lib/hooks/useLineDetailedMetrics.ts
8239
8445
  var useLineDetailedMetrics = (lineIdFromProp) => {
8240
8446
  const entityConfig = useEntityConfig();
@@ -9355,6 +9561,122 @@ var useWorkspaceOperators = (workspaceId, options) => {
9355
9561
  refetch: fetchData
9356
9562
  };
9357
9563
  };
9564
+ function useThreads() {
9565
+ const supabase = _getSupabaseInstance();
9566
+ const fetcher = async (key) => {
9567
+ const { data: { session } } = await supabase.auth.getSession();
9568
+ if (!session) {
9569
+ console.log("[useThreads] No session found");
9570
+ return [];
9571
+ }
9572
+ const user = session.user;
9573
+ if (!user) {
9574
+ console.log("[useThreads] No user in session");
9575
+ return [];
9576
+ }
9577
+ console.log("[useThreads] Fetching threads for user:", user.id);
9578
+ try {
9579
+ const { data, error: error2 } = await supabase.schema("ai").from("chat_threads").select("*").eq("user_id", user.id).order("updated_at", { ascending: false });
9580
+ if (error2) {
9581
+ console.error("[useThreads] Error fetching threads:", error2);
9582
+ throw error2;
9583
+ }
9584
+ console.log("[useThreads] Fetched threads:", data?.length || 0);
9585
+ return data || [];
9586
+ } catch (err) {
9587
+ console.error("[useThreads] Unexpected error:", err);
9588
+ throw err;
9589
+ }
9590
+ };
9591
+ const { data: threads = [], error, isLoading, mutate } = useSWR__default.default("chat_threads", fetcher, {
9592
+ revalidateOnFocus: false,
9593
+ revalidateOnReconnect: true
9594
+ });
9595
+ const createThread = React46.useCallback(async (title) => {
9596
+ const { data: { session } } = await supabase.auth.getSession();
9597
+ if (!session?.user) throw new Error("Not authenticated");
9598
+ const user = session.user;
9599
+ console.log("[useThreads] Creating thread for user:", user.id);
9600
+ const { data, error: error2 } = await supabase.schema("ai").from("chat_threads").insert([{
9601
+ user_id: user.id,
9602
+ title: title || `Chat ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`,
9603
+ auto_title: !title,
9604
+ model_id: "gpt-4o-mini",
9605
+ has_reasoning: false,
9606
+ last_message: null,
9607
+ message_count: 0
9608
+ }]).select().single();
9609
+ if (error2) {
9610
+ console.error("[useThreads] Error creating thread:", error2);
9611
+ throw error2;
9612
+ }
9613
+ console.log("[useThreads] Created thread:", data?.id);
9614
+ mutate([data, ...threads], false);
9615
+ return data;
9616
+ }, [supabase, threads, mutate]);
9617
+ const deleteThread2 = React46.useCallback(async (threadId) => {
9618
+ const { error: error2 } = await supabase.schema("ai").from("chat_threads").delete().eq("id", threadId);
9619
+ if (error2) throw error2;
9620
+ mutate(threads.filter((t) => t.id !== threadId), false);
9621
+ }, [supabase, threads, mutate]);
9622
+ return {
9623
+ threads,
9624
+ isLoading,
9625
+ error,
9626
+ mutate,
9627
+ createThread,
9628
+ deleteThread: deleteThread2
9629
+ };
9630
+ }
9631
+ function useMessages(threadId) {
9632
+ const supabase = _getSupabaseInstance();
9633
+ const [messages, setMessages] = React46.useState([]);
9634
+ const [isLoading, setIsLoading] = React46.useState(true);
9635
+ const [error, setError] = React46.useState(null);
9636
+ React46.useEffect(() => {
9637
+ if (!threadId) {
9638
+ setMessages([]);
9639
+ setIsLoading(false);
9640
+ return;
9641
+ }
9642
+ const loadMessages = async () => {
9643
+ setIsLoading(true);
9644
+ setError(null);
9645
+ try {
9646
+ const { data, error: fetchError } = await supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
9647
+ if (fetchError) throw fetchError;
9648
+ setMessages(data);
9649
+ } catch (err) {
9650
+ setError(err);
9651
+ console.error("Error loading messages:", err);
9652
+ } finally {
9653
+ setIsLoading(false);
9654
+ }
9655
+ };
9656
+ loadMessages();
9657
+ }, [threadId, supabase]);
9658
+ const addMessage = React46.useCallback(async (message) => {
9659
+ const maxPosition = messages.reduce((max, msg) => Math.max(max, msg.position), -1);
9660
+ const { data, error: insertError } = await supabase.schema("ai").from("chat_messages").insert([{
9661
+ ...message,
9662
+ position: maxPosition + 1
9663
+ }]).select().single();
9664
+ if (insertError) throw insertError;
9665
+ return data;
9666
+ }, [supabase, messages]);
9667
+ const updateMessage = React46.useCallback(async (id3, content) => {
9668
+ const { error: updateError } = await supabase.schema("ai").from("chat_messages").update({ content }).eq("id", id3);
9669
+ if (updateError) throw updateError;
9670
+ }, [supabase]);
9671
+ return {
9672
+ messages,
9673
+ setMessages,
9674
+ isLoading,
9675
+ error,
9676
+ addMessage,
9677
+ updateMessage
9678
+ };
9679
+ }
9358
9680
  var DEFAULT_FACTORY_OVERVIEW_TABLE_NAME = "factory_daily_summary";
9359
9681
  var useFactoryOverviewMetrics = (options) => {
9360
9682
  const { companyId, factoryId: entityFactoryId } = useEntityConfig();
@@ -13066,6 +13388,15 @@ var createSupabaseClient = (url, key) => supabaseJs.createClient(url, key, {
13066
13388
  persistSession: true,
13067
13389
  detectSessionInUrl: true,
13068
13390
  flowType: "pkce"
13391
+ },
13392
+ db: {
13393
+ schema: "public"
13394
+ // Default schema, we'll use .schema('ai') in queries
13395
+ },
13396
+ global: {
13397
+ headers: {
13398
+ "x-application-name": "optifye-dashboard"
13399
+ }
13069
13400
  }
13070
13401
  });
13071
13402
  var getAnonClient = () => {
@@ -21682,6 +22013,22 @@ var HourlyOutputChart = ({
21682
22013
  // Keep original data for labels
21683
22014
  color: (animatedData[i] || 0) >= Math.round(pphThreshold) ? "#00AB45" : "#E34329"
21684
22015
  }));
22016
+ const maxYValue = Math.ceil(pphThreshold * 1.5);
22017
+ const generateYAxisTicks = () => {
22018
+ const targetValue = Math.round(pphThreshold);
22019
+ const ticks = [0];
22020
+ if (targetValue > 0) {
22021
+ ticks.push(targetValue);
22022
+ }
22023
+ const step = Math.ceil((maxYValue - targetValue) / 2);
22024
+ if (step > 5) {
22025
+ for (let i = targetValue + step; i < maxYValue; i += step) {
22026
+ ticks.push(i);
22027
+ }
22028
+ }
22029
+ ticks.push(maxYValue);
22030
+ return [...new Set(ticks)].sort((a, b) => a - b);
22031
+ };
21685
22032
  const renderLegend = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 absolute -bottom-1 left-0 right-0 bg-white py-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
21686
22033
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
21687
22034
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
@@ -21721,7 +22068,8 @@ var HourlyOutputChart = ({
21721
22068
  {
21722
22069
  tickMargin: 8,
21723
22070
  width: 35,
21724
- ticks: [0, Math.round(pphThreshold), ...Array.from({ length: 4 }, (_, i) => (i + 1) * 50)].sort((a, b) => a - b),
22071
+ domain: [0, maxYValue],
22072
+ ticks: generateYAxisTicks(),
21725
22073
  tickFormatter: (value) => value,
21726
22074
  tick: (props) => {
21727
22075
  const { x, y, payload } = props;
@@ -24996,9 +25344,6 @@ var S3ClipsService = class {
24996
25344
  s3UriToCloudfront(s3Uri) {
24997
25345
  const url = new URL(s3Uri);
24998
25346
  const key = url.pathname.startsWith("/") ? url.pathname.substring(1) : url.pathname;
24999
- if (typeof window !== "undefined" && window.location.hostname === "localhost") {
25000
- return `/api/stream/${key}`;
25001
- }
25002
25347
  return `${this.config.s3Config.cloudFrontDomain}/${key}`;
25003
25348
  }
25004
25349
  /**
@@ -25812,8 +26157,7 @@ var BottlenecksContent = ({
25812
26157
  loop: false,
25813
26158
  preload: "metadata",
25814
26159
  onClick: togglePlayback,
25815
- onDoubleClick: toggleFullscreen,
25816
- crossOrigin: "anonymous"
26160
+ onDoubleClick: toggleFullscreen
25817
26161
  },
25818
26162
  currentVideo.id
25819
26163
  ),
@@ -28319,146 +28663,124 @@ var SingleVideoStream = ({
28319
28663
  );
28320
28664
  };
28321
28665
  var SingleVideoStream_default = SingleVideoStream;
28322
-
28323
- // src/lib/api/optifye-agent.ts
28324
- var OPTIFYE_API_URL = "https://optifye-agent-production.up.railway.app";
28325
- var OptifyeAgentClient = class {
28326
- constructor(apiUrl = OPTIFYE_API_URL) {
28327
- this.apiUrl = apiUrl;
28328
- }
28329
- /**
28330
- * Call Optifye Agent for manufacturing analysis
28331
- */
28332
- async getManufacturingInsights(userQuestion, lineId, shiftId, companyId, context) {
28333
- try {
28334
- const requestData = {
28335
- prompt: userQuestion,
28336
- line_id: lineId,
28337
- shift_id: shiftId,
28338
- company_id: companyId
28339
- };
28340
- if (context) {
28341
- requestData.context = context;
28342
- }
28343
- console.log("[OptifyeAgent] Sending request:", requestData);
28344
- const response = await fetch(`${this.apiUrl}/api/custom-analysis`, {
28345
- method: "POST",
28346
- headers: {
28347
- "Content-Type": "application/json"
28348
- },
28349
- body: JSON.stringify(requestData),
28350
- signal: AbortSignal.timeout(6e4)
28351
- // 60 second timeout
28352
- });
28353
- const responseText = await response.text();
28354
- console.log("[OptifyeAgent] Response status:", response.status);
28355
- console.log("[OptifyeAgent] Response text:", responseText);
28356
- let result;
28666
+ var ThreadSidebar = ({
28667
+ activeThreadId,
28668
+ onSelectThread,
28669
+ onNewThread,
28670
+ className = ""
28671
+ }) => {
28672
+ const { threads, isLoading, error, deleteThread: deleteThread2 } = useThreads();
28673
+ const [deletingId, setDeletingId] = React46.useState(null);
28674
+ const handleDelete = async (e, threadId) => {
28675
+ e.stopPropagation();
28676
+ if (confirm("Are you sure you want to delete this conversation?")) {
28677
+ setDeletingId(threadId);
28357
28678
  try {
28358
- result = JSON.parse(responseText);
28359
- } catch (parseError) {
28360
- console.error("[OptifyeAgent] Failed to parse response as JSON:", parseError);
28361
- if (responseText.includes("'Agent' object has no attribute")) {
28362
- return {
28363
- success: false,
28364
- error: "Server error: The AI agent service is experiencing issues. Please try again later or contact support."
28365
- };
28366
- }
28367
- return {
28368
- success: false,
28369
- error: `Invalid response format from server: ${responseText.substring(0, 200)}`
28370
- };
28371
- }
28372
- if (response.ok) {
28373
- return {
28374
- success: result.success ?? true,
28375
- analysis: result.analysis || result.response || result.message || result.data,
28376
- timestamp: result.timestamp,
28377
- error: result.error
28378
- };
28379
- } else {
28380
- const errorMsg = result.error || result.message || `API request failed: ${response.status}`;
28381
- console.error("[OptifyeAgent] API error:", errorMsg);
28382
- if (errorMsg.includes("'Agent' object has no attribute")) {
28383
- return {
28384
- success: false,
28385
- error: "The AI service is currently being updated. Please try again in a few moments."
28386
- };
28679
+ await deleteThread2(threadId);
28680
+ if (activeThreadId === threadId) {
28681
+ onNewThread();
28387
28682
  }
28388
- return {
28389
- success: false,
28390
- error: errorMsg
28391
- };
28392
- }
28393
- } catch (error) {
28394
- const errorMsg = error instanceof Error ? `Failed to connect to Optifye Agent: ${error.message}` : "Failed to connect to Optifye Agent";
28395
- console.error("[OptifyeAgent] Request failed:", error);
28396
- if (error instanceof Error && error.name === "AbortError") {
28397
- return {
28398
- success: false,
28399
- error: "Request timed out. The AI agent is taking too long to respond. Please try again."
28400
- };
28683
+ } catch (error2) {
28684
+ console.error("Error deleting thread:", error2);
28685
+ alert("Failed to delete conversation. Please try again.");
28686
+ } finally {
28687
+ setDeletingId(null);
28401
28688
  }
28402
- return {
28403
- success: false,
28404
- error: errorMsg
28405
- };
28406
28689
  }
28407
- }
28408
- /**
28409
- * Check if Optifye Agent API is healthy
28410
- */
28411
- async checkHealth() {
28412
- try {
28413
- const response = await fetch(`${this.apiUrl}/health`, {
28414
- signal: AbortSignal.timeout(1e4)
28415
- // 10 second timeout
28416
- });
28417
- if (response.ok) {
28418
- const healthData = await response.json();
28419
- return healthData.status === "healthy";
28420
- }
28421
- return false;
28422
- } catch (error) {
28423
- console.error("[OptifyeAgent] Health check failed:", error);
28424
- return false;
28690
+ };
28691
+ const formatDate = (dateString) => {
28692
+ const date = new Date(dateString);
28693
+ const now2 = /* @__PURE__ */ new Date();
28694
+ const diffMs = now2.getTime() - date.getTime();
28695
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
28696
+ if (diffDays === 0) {
28697
+ return "Today";
28698
+ } else if (diffDays === 1) {
28699
+ return "Yesterday";
28700
+ } else if (diffDays < 7) {
28701
+ return date.toLocaleDateString("en-US", { weekday: "short" });
28702
+ } else {
28703
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
28425
28704
  }
28705
+ };
28706
+ if (error) {
28707
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `p-4 text-red-600 text-sm ${className}`, children: "Failed to load conversations" });
28426
28708
  }
28709
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col h-full bg-gray-50 border-r border-gray-200 ${className}`, children: [
28710
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 p-4 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsxs(
28711
+ "button",
28712
+ {
28713
+ onClick: onNewThread,
28714
+ className: "w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium",
28715
+ children: [
28716
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "w-4 h-4" }),
28717
+ "New Chat"
28718
+ ]
28719
+ }
28720
+ ) }),
28721
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner_default, { size: "sm" }) }) : threads.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center text-gray-500 text-sm", children: "No conversations yet" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2", children: threads.map((thread) => /* @__PURE__ */ jsxRuntime.jsxs(
28722
+ "div",
28723
+ {
28724
+ onClick: () => onSelectThread(thread.id),
28725
+ className: `group relative flex items-start gap-3 px-4 py-3 hover:bg-gray-100 cursor-pointer transition-colors ${activeThreadId === thread.id ? "bg-gray-100" : ""}`,
28726
+ children: [
28727
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "w-4 h-4 text-gray-400 mt-0.5 flex-shrink-0" }),
28728
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
28729
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-gray-900 truncate", children: thread.title || "Untitled Chat" }),
28730
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: formatDate(thread.created_at) })
28731
+ ] }),
28732
+ deletingId === thread.id ? /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner_default, { size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(
28733
+ "button",
28734
+ {
28735
+ onClick: (e) => handleDelete(e, thread.id),
28736
+ className: "flex-shrink-0 opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-200 rounded transition-all",
28737
+ title: "Delete conversation",
28738
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "w-3.5 h-3.5 text-gray-500" })
28739
+ }
28740
+ )
28741
+ ]
28742
+ },
28743
+ thread.id
28744
+ )) }) }),
28745
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 p-4 border-t border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 text-center", children: [
28746
+ threads.length,
28747
+ " conversation",
28748
+ threads.length !== 1 ? "s" : ""
28749
+ ] }) })
28750
+ ] });
28427
28751
  };
28428
- var optifyeAgentClient = new OptifyeAgentClient();
28429
- async function getManufacturingInsights(userQuestion, lineId, shiftId, companyId, context) {
28430
- if (!userQuestion || !userQuestion.trim()) {
28431
- return {
28432
- success: false,
28433
- error: "Please provide a question"
28434
- };
28435
- }
28436
- if (!lineId || !companyId) {
28437
- console.warn("[OptifyeAgent] Missing required IDs:", { lineId, companyId });
28438
- }
28439
- return optifyeAgentClient.getManufacturingInsights(
28440
- userQuestion,
28441
- lineId,
28442
- shiftId,
28443
- companyId,
28444
- context
28445
- );
28446
- }
28447
28752
  var axelProfilePng = "/axel-profile.png";
28448
28753
  var AIAgentView = () => {
28449
28754
  const { navigate, pathname } = useNavigation();
28755
+ const config = useDashboardConfig();
28450
28756
  const entityConfig = useEntityConfig();
28451
28757
  const dateTimeConfig = useDateTimeConfig();
28452
28758
  const shiftConfig = useShiftConfig();
28453
28759
  const [inputValue, setInputValue] = React46.useState("");
28454
- const [messages, setMessages] = React46.useState([]);
28455
- const [isLoading, setIsLoading] = React46.useState(false);
28760
+ const [loadingThreads, setLoadingThreads] = React46.useState(/* @__PURE__ */ new Set());
28456
28761
  const [lastError, setLastError] = React46.useState(null);
28457
28762
  const [copiedMessageId, setCopiedMessageId] = React46.useState(null);
28458
- const [isTyping, setIsTyping] = React46.useState(false);
28763
+ const [activeThreadId, setActiveThreadId] = React46.useState(void 0);
28764
+ const [isSidebarOpen, setIsSidebarOpen] = React46.useState(true);
28765
+ const [streamingStates, setStreamingStates] = React46.useState(/* @__PURE__ */ new Map());
28766
+ const [userId, setUserId] = React46.useState(null);
28767
+ const [pendingThreadId, setPendingThreadId] = React46.useState(null);
28768
+ const isThreadLoading = (threadId) => {
28769
+ return threadId ? loadingThreads.has(threadId) : false;
28770
+ };
28771
+ const getStreamingState = (threadId) => {
28772
+ return threadId ? streamingStates.get(threadId) || { message: "", reasoning: "" } : { message: "", reasoning: "" };
28773
+ };
28459
28774
  const textareaRef = React46.useRef(null);
28460
28775
  const messagesEndRef = React46.useRef(null);
28461
28776
  const containerRef = React46.useRef(null);
28777
+ const { createThread, mutate: mutateThreads } = useThreads();
28778
+ const { messages, addMessage, setMessages } = useMessages(activeThreadId);
28779
+ const agnoApiUrl = config.endpoints?.agnoApiUrl || "https://optifye-agent-production.up.railway.app";
28780
+ const sseClient = React46.useMemo(() => {
28781
+ console.log("[AIAgentView] Using AGNO API URL:", agnoApiUrl);
28782
+ return new SSEChatClient(agnoApiUrl);
28783
+ }, [agnoApiUrl]);
28462
28784
  const getLineIdFromPath = () => {
28463
28785
  const pathParts = pathname.split("/");
28464
28786
  const lineIdIndex = pathParts.findIndex((part) => part === "ai-agent") + 1;
@@ -28476,11 +28798,14 @@ var AIAgentView = () => {
28476
28798
  textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 120)}px`;
28477
28799
  }
28478
28800
  }, [inputValue]);
28801
+ const scrollToBottom = () => {
28802
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
28803
+ };
28479
28804
  React46.useEffect(() => {
28480
- if (containerRef.current) {
28481
- containerRef.current.scrollTop = 0;
28805
+ if (activeThreadId && messages.length > 0) {
28806
+ setTimeout(scrollToBottom, 100);
28482
28807
  }
28483
- }, []);
28808
+ }, [activeThreadId]);
28484
28809
  const copyToClipboard = async (text, messageId) => {
28485
28810
  try {
28486
28811
  await navigator.clipboard.writeText(text);
@@ -28490,101 +28815,179 @@ var AIAgentView = () => {
28490
28815
  console.error("Failed to copy text: ", err);
28491
28816
  }
28492
28817
  };
28818
+ const handleNewThread = () => {
28819
+ setActiveThreadId(void 0);
28820
+ setMessages([]);
28821
+ setInputValue("");
28822
+ setPendingThreadId(null);
28823
+ textareaRef.current?.focus();
28824
+ };
28825
+ React46.useEffect(() => {
28826
+ const checkAuth = async () => {
28827
+ const supabase2 = _getSupabaseInstance();
28828
+ const { data: { session } } = await supabase2.auth.getSession();
28829
+ if (session?.user) {
28830
+ setUserId(session.user.id);
28831
+ console.log("[AIAgentView] User authenticated:", session.user.id);
28832
+ } else {
28833
+ console.log("[AIAgentView] No authenticated session found");
28834
+ }
28835
+ };
28836
+ checkAuth();
28837
+ const supabase = _getSupabaseInstance();
28838
+ const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
28839
+ if (session?.user) {
28840
+ setUserId(session.user.id);
28841
+ } else {
28842
+ setUserId(null);
28843
+ }
28844
+ });
28845
+ return () => {
28846
+ subscription.unsubscribe();
28847
+ };
28848
+ }, []);
28493
28849
  const handleSubmit = async (e) => {
28494
28850
  e.preventDefault();
28495
- if (!inputValue.trim() || isLoading) return;
28851
+ if (!inputValue.trim() || !userId) return;
28852
+ let currentThreadId = activeThreadId || `temp-${Date.now()}`;
28853
+ if (isThreadLoading(currentThreadId)) return;
28496
28854
  const userMessage = inputValue.trim();
28497
- const messageId = Date.now().toString();
28498
- const newUserMessage = {
28499
- id: messageId,
28855
+ setInputValue("");
28856
+ setLoadingThreads((prev) => new Set(prev).add(currentThreadId));
28857
+ setLastError(null);
28858
+ if (!activeThreadId) {
28859
+ setPendingThreadId(currentThreadId);
28860
+ }
28861
+ setStreamingStates((prev) => {
28862
+ const newMap = new Map(prev);
28863
+ newMap.set(currentThreadId, { message: "", reasoning: "" });
28864
+ return newMap;
28865
+ });
28866
+ const tempUserMessage = {
28867
+ id: Date.now(),
28868
+ // Temporary ID
28869
+ thread_id: activeThreadId || "",
28500
28870
  role: "user",
28501
28871
  content: userMessage,
28502
- timestamp: /* @__PURE__ */ new Date()
28872
+ reasoning: null,
28873
+ model_id: null,
28874
+ token_usage: null,
28875
+ metadata: null,
28876
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
28877
+ position: messages.length
28503
28878
  };
28504
- setMessages((prev) => [...prev, newUserMessage]);
28505
- setInputValue("");
28506
- setIsLoading(true);
28507
- setIsTyping(true);
28508
- setLastError(null);
28879
+ setMessages((prev) => [...prev, tempUserMessage]);
28880
+ setTimeout(scrollToBottom, 100);
28509
28881
  try {
28510
- const conversationHistory = messages.slice(-10).map((msg) => ({
28511
- role: msg.role,
28512
- content: msg.content
28513
- }));
28514
- const context = {
28515
- timeframe: "flexible",
28516
- focus: "conversational",
28517
- conversation_style: "friendly_expert",
28518
- system_prompt: `You are Axel, a friendly and knowledgeable manufacturing expert.
28519
-
28520
- Your personality:
28521
- - Warm, approachable, and professional
28522
- - You respond naturally to greetings and casual conversation
28523
- - You're passionate about manufacturing and love sharing your expertise
28524
- - You can discuss any manufacturing topic across any timeframe
28525
-
28526
- Guidelines:
28527
- - Respond to greetings warmly (e.g., "Hi" \u2192 "Hi there! How can I help you with your manufacturing needs today?")
28528
- - For manufacturing questions, provide detailed, actionable insights
28529
- - You can analyze past data, current performance, or future projections
28530
- - Always maintain a conversational tone while being informative
28531
- - If asked about non-manufacturing topics, politely redirect to your expertise
28532
-
28533
- Remember: You're not just an AI, you're Axel - a trusted manufacturing advisor who builds relationships with clients.`,
28534
- conversation_history: conversationHistory,
28535
- allow_any_timeframe: true,
28536
- personality_traits: ["friendly", "expert", "conversational", "helpful"]
28537
- };
28538
- console.log("[AIAgentView] Submitting message:", {
28539
- message: userMessage,
28540
- lineId,
28541
- shiftId,
28542
- companyId,
28543
- context
28544
- });
28545
- const result = await getManufacturingInsights(
28882
+ await sseClient.sendMessage(
28546
28883
  userMessage,
28547
- lineId,
28548
- shiftId,
28549
- companyId,
28550
- context
28884
+ userId,
28885
+ activeThreadId || null,
28886
+ {
28887
+ companyId,
28888
+ lineId,
28889
+ shiftId
28890
+ },
28891
+ {
28892
+ onThreadCreated: (threadId) => {
28893
+ if (!activeThreadId) {
28894
+ const oldThreadId = currentThreadId;
28895
+ currentThreadId = threadId;
28896
+ setActiveThreadId(threadId);
28897
+ setPendingThreadId(null);
28898
+ setLoadingThreads((prev) => {
28899
+ const newSet = new Set(prev);
28900
+ if (newSet.has(oldThreadId)) {
28901
+ newSet.delete(oldThreadId);
28902
+ newSet.add(threadId);
28903
+ }
28904
+ return newSet;
28905
+ });
28906
+ setStreamingStates((prev) => {
28907
+ const newMap = new Map(prev);
28908
+ const streamingState = newMap.get(oldThreadId);
28909
+ if (streamingState) {
28910
+ newMap.delete(oldThreadId);
28911
+ newMap.set(threadId, streamingState);
28912
+ }
28913
+ return newMap;
28914
+ });
28915
+ mutateThreads();
28916
+ }
28917
+ },
28918
+ onMessage: (text) => {
28919
+ setStreamingStates((prev) => {
28920
+ const newMap = new Map(prev);
28921
+ const current = newMap.get(currentThreadId) || { message: "", reasoning: "" };
28922
+ newMap.set(currentThreadId, { ...current, message: current.message + text });
28923
+ return newMap;
28924
+ });
28925
+ },
28926
+ onReasoning: (text) => {
28927
+ setStreamingStates((prev) => {
28928
+ const newMap = new Map(prev);
28929
+ const current = newMap.get(currentThreadId) || { message: "", reasoning: "" };
28930
+ newMap.set(currentThreadId, { ...current, reasoning: current.reasoning + text });
28931
+ return newMap;
28932
+ });
28933
+ },
28934
+ onComplete: async (messageId) => {
28935
+ if (currentThreadId && !currentThreadId.startsWith("temp-")) {
28936
+ const updatedMessages = await getAllThreadMessages(currentThreadId);
28937
+ setMessages(updatedMessages);
28938
+ }
28939
+ setStreamingStates((prev) => {
28940
+ const newMap = new Map(prev);
28941
+ newMap.delete(currentThreadId);
28942
+ return newMap;
28943
+ });
28944
+ setLoadingThreads((prev) => {
28945
+ const newSet = new Set(prev);
28946
+ newSet.delete(currentThreadId);
28947
+ return newSet;
28948
+ });
28949
+ if (!activeThreadId) {
28950
+ setPendingThreadId(null);
28951
+ }
28952
+ },
28953
+ onError: (error) => {
28954
+ console.error("Chat error:", error);
28955
+ setLastError(error);
28956
+ setLoadingThreads((prev) => {
28957
+ const newSet = new Set(prev);
28958
+ newSet.delete(currentThreadId);
28959
+ return newSet;
28960
+ });
28961
+ setStreamingStates((prev) => {
28962
+ const newMap = new Map(prev);
28963
+ newMap.delete(currentThreadId);
28964
+ return newMap;
28965
+ });
28966
+ if (!activeThreadId) {
28967
+ setPendingThreadId(null);
28968
+ }
28969
+ setMessages((prev) => prev.slice(0, -1));
28970
+ }
28971
+ }
28551
28972
  );
28552
- if (result.success && result.analysis) {
28553
- const assistantMessage = {
28554
- id: Date.now().toString(),
28555
- role: "assistant",
28556
- content: result.analysis,
28557
- timestamp: /* @__PURE__ */ new Date()
28558
- };
28559
- setMessages((prev) => [...prev, assistantMessage]);
28560
- } else {
28561
- const errorMessage = result.error || "Sorry, I encountered an error processing your request. Please try again.";
28562
- const errorAssistantMessage = {
28563
- id: Date.now().toString(),
28564
- role: "assistant",
28565
- content: errorMessage,
28566
- timestamp: /* @__PURE__ */ new Date(),
28567
- error: true
28568
- };
28569
- setMessages((prev) => [...prev, errorAssistantMessage]);
28570
- setLastError(errorMessage);
28571
- console.error("[AIAgentView] API returned error:", errorMessage);
28572
- }
28573
28973
  } catch (error) {
28574
- console.error("[AIAgentView] Error calling Optifye Agent:", error);
28575
- const errorMessage = "I apologize, but I encountered an unexpected error. Please try again or contact support if the issue persists.";
28576
- const errorAssistantMessage = {
28577
- id: Date.now().toString(),
28578
- role: "assistant",
28579
- content: errorMessage,
28580
- timestamp: /* @__PURE__ */ new Date(),
28581
- error: true
28582
- };
28583
- setMessages((prev) => [...prev, errorAssistantMessage]);
28974
+ console.error("[AIAgentView] Error in chat:", error);
28975
+ const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred";
28584
28976
  setLastError(errorMessage);
28585
- } finally {
28586
- setIsLoading(false);
28587
- setIsTyping(false);
28977
+ setLoadingThreads((prev) => {
28978
+ const newSet = new Set(prev);
28979
+ newSet.delete(currentThreadId);
28980
+ return newSet;
28981
+ });
28982
+ setStreamingStates((prev) => {
28983
+ const newMap = new Map(prev);
28984
+ newMap.delete(currentThreadId);
28985
+ return newMap;
28986
+ });
28987
+ if (!activeThreadId) {
28988
+ setPendingThreadId(null);
28989
+ }
28990
+ setMessages((prev) => prev.slice(0, -1));
28588
28991
  }
28589
28992
  };
28590
28993
  const handleKeyDown = (e) => {
@@ -28594,207 +28997,324 @@ var AIAgentView = () => {
28594
28997
  }
28595
28998
  };
28596
28999
  const formatMessage = (content) => {
28597
- return content.split("\n").map((line, i) => {
28598
- line = line.replace(/\*\*(.*?)\*\*/g, '<strong class="font-semibold text-gray-900">$1</strong>');
28599
- if (line.startsWith("\u2022")) {
28600
- line = `<li class="ml-4 mb-1 text-gray-700">${line.substring(1).trim()}</li>`;
29000
+ const processInlineFormatting = (text) => {
29001
+ text = text.replace(/\*\*(.*?)\*\*/g, '<strong class="font-semibold text-gray-900">$1</strong>');
29002
+ text = text.replace(/`([^`]+)`/g, '<code class="bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-gray-800">$1</code>');
29003
+ return text;
29004
+ };
29005
+ const lines = content.split("\n");
29006
+ const formattedLines = [];
29007
+ let inList = false;
29008
+ for (let i = 0; i < lines.length; i++) {
29009
+ let line = lines[i];
29010
+ const trimmedLine = line.trim();
29011
+ if (!trimmedLine) {
29012
+ if (inList) {
29013
+ formattedLines.push("</ul>");
29014
+ inList = false;
29015
+ }
29016
+ formattedLines.push("<br/>");
29017
+ continue;
28601
29018
  }
28602
- if (/^\d+\./.test(line)) {
28603
- line = `<li class="ml-4 mb-1 text-gray-700">${line}</li>`;
29019
+ if (trimmedLine.startsWith("###")) {
29020
+ if (inList) {
29021
+ formattedLines.push("</ul>");
29022
+ inList = false;
29023
+ }
29024
+ const headerText = processInlineFormatting(trimmedLine.replace(/^###\s*/, ""));
29025
+ formattedLines.push(`<h3 class="text-lg font-semibold text-gray-900 mt-4 mb-2">${headerText}</h3>`);
29026
+ continue;
29027
+ } else if (trimmedLine.startsWith("##")) {
29028
+ if (inList) {
29029
+ formattedLines.push("</ul>");
29030
+ inList = false;
29031
+ }
29032
+ const headerText = processInlineFormatting(trimmedLine.replace(/^##\s*/, ""));
29033
+ formattedLines.push(`<h2 class="text-xl font-semibold text-gray-900 mt-4 mb-2">${headerText}</h2>`);
29034
+ continue;
29035
+ } else if (trimmedLine.startsWith("#")) {
29036
+ if (inList) {
29037
+ formattedLines.push("</ul>");
29038
+ inList = false;
29039
+ }
29040
+ const headerText = processInlineFormatting(trimmedLine.replace(/^#\s*/, ""));
29041
+ formattedLines.push(`<h1 class="text-2xl font-bold text-gray-900 mt-4 mb-3">${headerText}</h1>`);
29042
+ continue;
28604
29043
  }
28605
- line = line.replace(/`([^`]+)`/g, '<code class="bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-gray-800">$1</code>');
28606
- return line;
28607
- }).join("<br/>");
28608
- };
28609
- const clearConversation = () => {
28610
- setMessages([]);
28611
- setLastError(null);
28612
- textareaRef.current?.focus();
29044
+ const listMatch = trimmedLine.match(/^([-*•]\s+|\d+\.\s+|^\s*[-*•]\s+)/);
29045
+ if (listMatch) {
29046
+ if (!inList) {
29047
+ formattedLines.push('<ul class="space-y-1 mt-2 mb-2">');
29048
+ inList = true;
29049
+ }
29050
+ const listContent = processInlineFormatting(trimmedLine.replace(/^([-*•]\s+|\d+\.\s+|\s*[-*•]\s+)/, ""));
29051
+ formattedLines.push(`<li class="ml-4 text-gray-700 flex items-start"><span class="mr-2 text-gray-500">\u2022</span><span>${listContent}</span></li>`);
29052
+ continue;
29053
+ } else if (inList) {
29054
+ formattedLines.push("</ul>");
29055
+ inList = false;
29056
+ }
29057
+ if (trimmedLine.match(/^---+$/)) {
29058
+ formattedLines.push('<hr class="my-4 border-gray-200"/>');
29059
+ continue;
29060
+ }
29061
+ if (trimmedLine) {
29062
+ const processedLine = processInlineFormatting(line);
29063
+ if (trimmedLine.endsWith(":") && trimmedLine.length < 50 && !trimmedLine.includes("**")) {
29064
+ formattedLines.push(`<h3 class="text-lg font-semibold text-gray-900 mt-4 mb-2">${processedLine}</h3>`);
29065
+ } else {
29066
+ formattedLines.push(`<p class="text-gray-800 leading-relaxed mb-2">${processedLine}</p>`);
29067
+ }
29068
+ }
29069
+ }
29070
+ if (inList) {
29071
+ formattedLines.push("</ul>");
29072
+ }
29073
+ return formattedLines.join("");
28613
29074
  };
28614
29075
  const formatTime2 = (timestamp) => {
28615
- return timestamp.toLocaleTimeString([], {
29076
+ const date = new Date(timestamp);
29077
+ return date.toLocaleTimeString([], {
28616
29078
  hour: "2-digit",
28617
29079
  minute: "2-digit",
28618
29080
  hour12: false
28619
29081
  });
28620
29082
  };
28621
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-screen bg-white", children: [
28622
- /* @__PURE__ */ jsxRuntime.jsx("header", { className: "flex-shrink-0 bg-white border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center relative", children: [
28623
- /* @__PURE__ */ jsxRuntime.jsxs(
28624
- "button",
28625
- {
28626
- onClick: () => navigate("/"),
28627
- className: "absolute left-0 flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors",
28628
- "aria-label": "Go back",
28629
- children: [
28630
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "w-5 h-5" }),
28631
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Back" })
28632
- ]
28633
- }
28634
- ),
28635
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
28636
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
28637
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl sm:text-2xl md:text-3xl font-bold text-gray-800 tracking-tight leading-none", children: "Axel - AI Manufacturing Expert" }),
28638
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-blue-500 animate-pulse ring-2 ring-blue-500/30" })
28639
- ] }),
28640
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-500", children: /* @__PURE__ */ jsxRuntime.jsx(ISTTimer_default, {}) }) })
28641
- ] }),
28642
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0 w-24" })
28643
- ] }) }) }),
28644
- /* @__PURE__ */ jsxRuntime.jsx(
28645
- "main",
29083
+ const displayMessages = [...messages];
29084
+ const effectiveThreadId = activeThreadId || pendingThreadId || void 0;
29085
+ const currentStreaming = getStreamingState(effectiveThreadId);
29086
+ const isCurrentThreadLoading = isThreadLoading(effectiveThreadId);
29087
+ if (isCurrentThreadLoading && currentStreaming.message) {
29088
+ displayMessages.push({
29089
+ id: -1,
29090
+ // Use -1 for streaming message to identify it
29091
+ thread_id: activeThreadId || "",
29092
+ role: "assistant",
29093
+ content: currentStreaming.message,
29094
+ reasoning: currentStreaming.reasoning || null,
29095
+ model_id: "gpt-4o-mini",
29096
+ token_usage: null,
29097
+ metadata: null,
29098
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
29099
+ position: messages.length
29100
+ });
29101
+ }
29102
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-screen bg-white", children: [
29103
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${isSidebarOpen ? "w-80" : "w-0"} transition-all duration-300 overflow-hidden flex-shrink-0`, children: /* @__PURE__ */ jsxRuntime.jsx(
29104
+ ThreadSidebar,
28646
29105
  {
28647
- ref: containerRef,
28648
- className: "flex-1 overflow-y-auto bg-gray-50/50 min-h-0",
28649
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-5xl mx-auto p-4 sm:p-6 md:p-8", children: [
28650
- messages.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-8 bg-white border border-gray-200/80 shadow-sm rounded-2xl p-6 sm:p-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-4", children: [
28651
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-16 h-16 rounded-xl overflow-hidden shadow-sm flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
28652
- "img",
28653
- {
28654
- src: axelProfilePng,
28655
- alt: "Axel - AI Manufacturing Expert",
28656
- className: "w-full h-full object-cover"
28657
- }
28658
- ) }),
28659
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 pt-1", children: [
28660
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-semibold text-gray-900 mb-2", children: "Hi, I'm Axel - Your AI Supervisor" }),
28661
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 leading-relaxed", children: "Ask me anything about your shop-floor." })
28662
- ] })
28663
- ] }) }),
28664
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
28665
- messages.map((message) => /* @__PURE__ */ jsxRuntime.jsxs(
28666
- "div",
28667
- {
28668
- className: `flex gap-4 ${message.role === "user" ? "justify-end" : "justify-start"}`,
28669
- children: [
28670
- message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(
28671
- "img",
28672
- {
28673
- src: axelProfilePng,
28674
- alt: "Axel",
28675
- className: "w-full h-full object-cover"
28676
- }
28677
- ) }) }),
28678
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `max-w-[75%] group ${message.role === "user" ? "order-1" : ""}`, children: [
28679
- /* @__PURE__ */ jsxRuntime.jsxs(
28680
- "div",
28681
- {
28682
- className: `relative px-5 py-4 rounded-2xl shadow-sm ${message.role === "user" ? "bg-blue-600 text-white" : message.error ? "bg-red-50 border border-red-200" : "bg-white border border-gray-200/80"}`,
28683
- children: [
28684
- message.error && message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-3 pb-3 border-b border-red-200", children: [
28685
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-4 h-4 text-red-500 flex-shrink-0" }),
28686
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-red-700", children: "Error Processing Request" })
28687
- ] }),
28688
- /* @__PURE__ */ jsxRuntime.jsx(
28689
- "div",
28690
- {
28691
- className: `prose prose-sm max-w-none ${message.role === "user" ? "text-white prose-headings:text-white prose-strong:text-white" : "text-gray-800 prose-headings:text-gray-900 prose-strong:text-gray-900"}`,
28692
- dangerouslySetInnerHTML: {
28693
- __html: message.role === "assistant" ? formatMessage(message.content) : message.content
28694
- }
28695
- }
28696
- ),
28697
- message.role === "assistant" && !message.error && /* @__PURE__ */ jsxRuntime.jsx(
28698
- "button",
28699
- {
28700
- onClick: () => copyToClipboard(message.content, message.id),
28701
- className: "absolute top-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 p-1.5 hover:bg-gray-100 rounded-lg",
28702
- title: "Copy message",
28703
- children: copiedMessageId === message.id ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-green-600" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "w-4 h-4 text-gray-500" })
28704
- }
28705
- )
28706
- ]
28707
- }
28708
- ),
28709
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `mt-2 flex items-center gap-2 text-xs text-gray-400 ${message.role === "user" ? "justify-end" : "justify-start"}`, children: [
28710
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime2(message.timestamp) }),
28711
- message.role === "assistant" && !message.error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
28712
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
28713
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Axel" })
28714
- ] })
28715
- ] })
28716
- ] }),
28717
- message.role === "user" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-10 h-10 bg-gray-700 text-white rounded-xl flex items-center justify-center text-sm font-semibold", children: entityConfig.companyId?.charAt(0).toUpperCase() || "U" }) })
28718
- ]
28719
- },
28720
- message.id
28721
- )),
28722
- isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 justify-start", children: [
28723
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(
29106
+ activeThreadId,
29107
+ onSelectThread: setActiveThreadId,
29108
+ onNewThread: handleNewThread,
29109
+ className: "h-full"
29110
+ }
29111
+ ) }),
29112
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col", children: [
29113
+ /* @__PURE__ */ jsxRuntime.jsx("header", { className: "flex-shrink-0 bg-white border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
29114
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
29115
+ /* @__PURE__ */ jsxRuntime.jsx(
29116
+ "button",
29117
+ {
29118
+ onClick: () => setIsSidebarOpen(!isSidebarOpen),
29119
+ className: "p-2 hover:bg-gray-100 rounded-lg transition-colors",
29120
+ "aria-label": "Toggle sidebar",
29121
+ children: isSidebarOpen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-5 h-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Menu, { className: "w-5 h-5" })
29122
+ }
29123
+ ),
29124
+ /* @__PURE__ */ jsxRuntime.jsxs(
29125
+ "button",
29126
+ {
29127
+ onClick: () => navigate("/"),
29128
+ className: "flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors",
29129
+ "aria-label": "Go back",
29130
+ children: [
29131
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "w-5 h-5" }),
29132
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Back" })
29133
+ ]
29134
+ }
29135
+ )
29136
+ ] }),
29137
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center", children: [
29138
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
29139
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl sm:text-2xl md:text-3xl font-bold text-gray-800 tracking-tight leading-none", children: "Axel - AI Manufacturing Expert" }),
29140
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-blue-500 animate-pulse ring-2 ring-blue-500/30" })
29141
+ ] }),
29142
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-500", children: /* @__PURE__ */ jsxRuntime.jsx(ISTTimer_default, {}) }) })
29143
+ ] }),
29144
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-24" })
29145
+ ] }) }) }),
29146
+ /* @__PURE__ */ jsxRuntime.jsx(
29147
+ "main",
29148
+ {
29149
+ ref: containerRef,
29150
+ className: "flex-1 overflow-y-auto bg-gray-50/50 min-h-0",
29151
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-5xl mx-auto p-4 sm:p-6 md:p-8", children: [
29152
+ displayMessages.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-8 bg-white border border-gray-200/80 shadow-sm rounded-2xl p-6 sm:p-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-4", children: [
29153
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-16 h-16 rounded-xl overflow-hidden shadow-sm flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
28724
29154
  "img",
28725
29155
  {
28726
29156
  src: axelProfilePng,
28727
- alt: "Axel",
29157
+ alt: "Axel - AI Manufacturing Expert",
28728
29158
  className: "w-full h-full object-cover"
28729
29159
  }
28730
- ) }) }),
28731
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white border border-gray-200/80 px-5 py-4 rounded-2xl shadow-sm max-w-[75%]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
28732
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex space-x-1", children: [
28733
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce" }),
28734
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce", style: { animationDelay: "0.1s" } }),
28735
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce", style: { animationDelay: "0.2s" } })
28736
- ] }),
28737
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-600 text-sm", children: "Axel is analyzing your request..." })
28738
- ] }) })
28739
- ] }),
28740
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
29160
+ ) }),
29161
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 pt-1", children: [
29162
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-semibold text-gray-900 mb-2", children: "Hi, I'm Axel - Your AI Supervisor" }),
29163
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 leading-relaxed", children: "Ask me anything about your shop-floor. I'll remember our conversation and help you optimize your manufacturing processes." })
29164
+ ] })
29165
+ ] }) }),
29166
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
29167
+ displayMessages.map((message, index) => /* @__PURE__ */ jsxRuntime.jsxs(
29168
+ "div",
29169
+ {
29170
+ className: `flex gap-4 ${message.role === "user" ? "justify-end" : "justify-start"}`,
29171
+ children: [
29172
+ message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(
29173
+ "img",
29174
+ {
29175
+ src: axelProfilePng,
29176
+ alt: "Axel",
29177
+ className: "w-full h-full object-cover"
29178
+ }
29179
+ ) }) }),
29180
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `max-w-[75%] group ${message.role === "user" ? "order-1" : ""}`, children: [
29181
+ /* @__PURE__ */ jsxRuntime.jsxs(
29182
+ "div",
29183
+ {
29184
+ className: `relative px-5 py-4 rounded-2xl shadow-sm ${message.role === "user" ? "bg-blue-600 text-white" : "bg-white border border-gray-200/80"}`,
29185
+ children: [
29186
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${message.role === "user" ? "text-white" : "text-gray-800"}`, children: [
29187
+ message.role === "assistant" ? /* @__PURE__ */ jsxRuntime.jsx(
29188
+ "div",
29189
+ {
29190
+ className: "formatted-content",
29191
+ dangerouslySetInnerHTML: {
29192
+ __html: formatMessage(message.content)
29193
+ }
29194
+ }
29195
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-pre-wrap leading-relaxed", children: message.content }),
29196
+ message.id === -1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-0.5 h-4 bg-gray-400 animate-pulse ml-0.5" })
29197
+ ] }),
29198
+ message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxRuntime.jsx(
29199
+ "button",
29200
+ {
29201
+ onClick: () => copyToClipboard(message.content, message.id.toString()),
29202
+ className: "absolute top-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 p-1.5 hover:bg-gray-100 rounded-lg",
29203
+ title: "Copy message",
29204
+ children: copiedMessageId === message.id.toString() ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-green-600" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "w-4 h-4 text-gray-500" })
29205
+ }
29206
+ ),
29207
+ message.reasoning && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "mt-3 pt-3 border-t border-gray-200", children: [
29208
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { className: "cursor-pointer text-sm text-gray-600 hover:text-gray-800", children: "View reasoning" }),
29209
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 text-sm text-gray-600", children: message.reasoning })
29210
+ ] })
29211
+ ]
29212
+ }
29213
+ ),
29214
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `mt-2 flex items-center gap-2 text-xs text-gray-400 ${message.role === "user" ? "justify-end" : "justify-start"}`, children: [
29215
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime2(message.created_at) }),
29216
+ message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
29217
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
29218
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Axel" })
29219
+ ] })
29220
+ ] })
29221
+ ] }),
29222
+ message.role === "user" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-10 h-10 bg-gray-700 text-white rounded-xl flex items-center justify-center text-sm font-semibold", children: entityConfig.companyId?.charAt(0).toUpperCase() || "U" }) })
29223
+ ]
29224
+ },
29225
+ message.id === -1 ? `streaming-${currentStreaming.message.length}` : `${message.id}-${index}`
29226
+ )),
29227
+ lastError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 justify-start", children: [
29228
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl bg-red-100 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-6 h-6 text-red-600" }) }) }),
29229
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-red-50 border border-red-200 px-5 py-4 rounded-2xl shadow-sm max-w-[75%]", children: [
29230
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-800 text-sm", children: lastError }),
29231
+ /* @__PURE__ */ jsxRuntime.jsx(
29232
+ "button",
29233
+ {
29234
+ onClick: () => setLastError(null),
29235
+ className: "mt-2 text-xs text-red-600 hover:text-red-800 underline",
29236
+ children: "Dismiss"
29237
+ }
29238
+ )
29239
+ ] })
29240
+ ] }),
29241
+ isCurrentThreadLoading && !currentStreaming.message && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 justify-start", children: [
29242
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(
29243
+ "img",
29244
+ {
29245
+ src: axelProfilePng,
29246
+ alt: "Axel",
29247
+ className: "w-full h-full object-cover"
29248
+ }
29249
+ ) }) }),
29250
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white border border-gray-200/80 px-5 py-4 rounded-2xl shadow-sm max-w-[75%]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
29251
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex space-x-1", children: [
29252
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce" }),
29253
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce", style: { animationDelay: "0.1s" } }),
29254
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce", style: { animationDelay: "0.2s" } })
29255
+ ] }),
29256
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-600 text-sm", children: "Axel is analyzing your request..." })
29257
+ ] }) })
29258
+ ] }),
29259
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
29260
+ ] })
28741
29261
  ] })
28742
- ] })
28743
- }
28744
- ),
28745
- /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "flex-shrink-0 border-t border-gray-200 bg-white sticky bottom-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-5xl mx-auto p-4 sm:p-6", children: [
28746
- messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsxRuntime.jsxs(
28747
- "button",
28748
- {
28749
- onClick: clearConversation,
28750
- className: "inline-flex items-center gap-2 px-3 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors",
28751
- children: [
28752
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "w-3.5 h-3.5" }),
28753
- "Clear conversation"
28754
- ]
28755
29262
  }
28756
- ) }),
28757
- /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-3", children: [
28758
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 items-end", children: [
28759
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 relative", children: [
28760
- /* @__PURE__ */ jsxRuntime.jsx(
28761
- "textarea",
29263
+ ),
29264
+ /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "flex-shrink-0 border-t border-gray-200 bg-white sticky bottom-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-5xl mx-auto p-4 sm:p-6", children: [
29265
+ activeThreadId && messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsxRuntime.jsxs(
29266
+ "button",
29267
+ {
29268
+ onClick: handleNewThread,
29269
+ className: "inline-flex items-center gap-2 px-3 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors",
29270
+ children: [
29271
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "w-3.5 h-3.5" }),
29272
+ "New conversation"
29273
+ ]
29274
+ }
29275
+ ) }),
29276
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-3", children: [
29277
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 items-end", children: [
29278
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 relative", children: [
29279
+ /* @__PURE__ */ jsxRuntime.jsx(
29280
+ "textarea",
29281
+ {
29282
+ ref: textareaRef,
29283
+ value: inputValue,
29284
+ onChange: (e) => setInputValue(e.target.value),
29285
+ onKeyDown: handleKeyDown,
29286
+ placeholder: "Ask me about production optimization, quality metrics, or any manufacturing challenge...",
29287
+ className: "w-full resize-none rounded-xl border border-gray-300 px-4 py-3.5 pr-16 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-400 bg-white placeholder-gray-500 text-gray-900 text-sm leading-relaxed shadow-sm transition-all duration-200",
29288
+ rows: 1,
29289
+ disabled: isCurrentThreadLoading,
29290
+ style: { minHeight: "48px", maxHeight: "120px" }
29291
+ }
29292
+ ),
29293
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-3 right-3 flex items-center gap-2", children: inputValue.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 font-mono", children: inputValue.length }) })
29294
+ ] }),
29295
+ /* @__PURE__ */ jsxRuntime.jsxs(
29296
+ "button",
28762
29297
  {
28763
- ref: textareaRef,
28764
- value: inputValue,
28765
- onChange: (e) => setInputValue(e.target.value),
28766
- onKeyDown: handleKeyDown,
28767
- placeholder: "Ask me about production optimization, quality metrics, or any manufacturing challenge...",
28768
- className: "w-full resize-none rounded-xl border border-gray-300 px-4 py-3.5 pr-16 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-400 bg-white placeholder-gray-500 text-gray-900 text-sm leading-relaxed shadow-sm transition-all duration-200",
28769
- rows: 1,
28770
- disabled: isLoading,
28771
- style: { minHeight: "48px", maxHeight: "120px" }
29298
+ type: "submit",
29299
+ disabled: !inputValue.trim() || isCurrentThreadLoading,
29300
+ className: "inline-flex items-center justify-center gap-2 h-12 px-5 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:ring-offset-2 shadow-sm hover:shadow-md disabled:hover:shadow-sm",
29301
+ children: [
29302
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-4 h-4" }),
29303
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Send" })
29304
+ ]
28772
29305
  }
28773
- ),
28774
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-3 right-3 flex items-center gap-2", children: inputValue.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 font-mono", children: inputValue.length }) })
29306
+ )
28775
29307
  ] }),
28776
- /* @__PURE__ */ jsxRuntime.jsxs(
28777
- "button",
28778
- {
28779
- type: "submit",
28780
- disabled: !inputValue.trim() || isLoading,
28781
- className: "inline-flex items-center justify-center gap-2 h-12 px-5 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:ring-offset-2 shadow-sm hover:shadow-md disabled:hover:shadow-sm",
28782
- children: [
28783
- isLoading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "w-4 h-4" }),
28784
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: isLoading ? "Sending..." : "Send" })
28785
- ]
28786
- }
28787
- )
28788
- ] }),
28789
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs text-gray-400", children: [
28790
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Press Enter to send \u2022 Shift+Enter for new line" }) }),
28791
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
28792
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 bg-green-500 rounded-full" }),
28793
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Connected" })
28794
- ] }) })
29308
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs text-gray-400", children: [
29309
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Press Enter to send \u2022 Shift+Enter for new line" }) }),
29310
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
29311
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 bg-green-500 rounded-full" }),
29312
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Connected" })
29313
+ ] }) })
29314
+ ] })
28795
29315
  ] })
28796
- ] })
28797
- ] }) })
29316
+ ] }) })
29317
+ ] })
28798
29318
  ] });
28799
29319
  };
28800
29320
  var AIAgentView_default = AIAgentView;
@@ -31243,10 +31763,7 @@ var ShiftsView = ({
31243
31763
  }
31244
31764
  ),
31245
31765
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
31246
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
31247
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl sm:text-2xl md:text-3xl font-bold text-gray-800 tracking-tight leading-none", children: "Shift Management" }),
31248
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-blue-500 animate-pulse ring-2 ring-blue-500/30" })
31249
- ] }),
31766
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl sm:text-2xl md:text-3xl font-bold text-gray-800 tracking-tight leading-none", children: "Shift Management" }) }),
31250
31767
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-500", children: /* @__PURE__ */ jsxRuntime.jsx(ISTTimer_default, {}) }) })
31251
31768
  ] }),
31252
31769
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0 w-24" })
@@ -33547,8 +34064,133 @@ var S3Service = class {
33547
34064
  }
33548
34065
  }
33549
34066
  };
34067
+
34068
+ // src/lib/api/optifye-agent.ts
34069
+ var OPTIFYE_API_URL = "https://optifye-agent-production.up.railway.app";
34070
+ var OptifyeAgentClient = class {
34071
+ constructor(apiUrl = OPTIFYE_API_URL) {
34072
+ this.apiUrl = apiUrl;
34073
+ }
34074
+ /**
34075
+ * Call Optifye Agent for manufacturing analysis
34076
+ */
34077
+ async getManufacturingInsights(userQuestion, lineId, shiftId, companyId, context) {
34078
+ try {
34079
+ const requestData = {
34080
+ prompt: userQuestion,
34081
+ line_id: lineId,
34082
+ shift_id: shiftId,
34083
+ company_id: companyId
34084
+ };
34085
+ if (context) {
34086
+ requestData.context = context;
34087
+ }
34088
+ console.log("[OptifyeAgent] Sending request:", requestData);
34089
+ const response = await fetch(`${this.apiUrl}/api/custom-analysis`, {
34090
+ method: "POST",
34091
+ headers: {
34092
+ "Content-Type": "application/json"
34093
+ },
34094
+ body: JSON.stringify(requestData),
34095
+ signal: AbortSignal.timeout(6e4)
34096
+ // 60 second timeout
34097
+ });
34098
+ const responseText = await response.text();
34099
+ console.log("[OptifyeAgent] Response status:", response.status);
34100
+ console.log("[OptifyeAgent] Response text:", responseText);
34101
+ let result;
34102
+ try {
34103
+ result = JSON.parse(responseText);
34104
+ } catch (parseError) {
34105
+ console.error("[OptifyeAgent] Failed to parse response as JSON:", parseError);
34106
+ if (responseText.includes("'Agent' object has no attribute")) {
34107
+ return {
34108
+ success: false,
34109
+ error: "Server error: The AI agent service is experiencing issues. Please try again later or contact support."
34110
+ };
34111
+ }
34112
+ return {
34113
+ success: false,
34114
+ error: `Invalid response format from server: ${responseText.substring(0, 200)}`
34115
+ };
34116
+ }
34117
+ if (response.ok) {
34118
+ return {
34119
+ success: result.success ?? true,
34120
+ analysis: result.analysis || result.response || result.message || result.data,
34121
+ timestamp: result.timestamp,
34122
+ error: result.error
34123
+ };
34124
+ } else {
34125
+ const errorMsg = result.error || result.message || `API request failed: ${response.status}`;
34126
+ console.error("[OptifyeAgent] API error:", errorMsg);
34127
+ if (errorMsg.includes("'Agent' object has no attribute")) {
34128
+ return {
34129
+ success: false,
34130
+ error: "The AI service is currently being updated. Please try again in a few moments."
34131
+ };
34132
+ }
34133
+ return {
34134
+ success: false,
34135
+ error: errorMsg
34136
+ };
34137
+ }
34138
+ } catch (error) {
34139
+ const errorMsg = error instanceof Error ? `Failed to connect to Optifye Agent: ${error.message}` : "Failed to connect to Optifye Agent";
34140
+ console.error("[OptifyeAgent] Request failed:", error);
34141
+ if (error instanceof Error && error.name === "AbortError") {
34142
+ return {
34143
+ success: false,
34144
+ error: "Request timed out. The AI agent is taking too long to respond. Please try again."
34145
+ };
34146
+ }
34147
+ return {
34148
+ success: false,
34149
+ error: errorMsg
34150
+ };
34151
+ }
34152
+ }
34153
+ /**
34154
+ * Check if Optifye Agent API is healthy
34155
+ */
34156
+ async checkHealth() {
34157
+ try {
34158
+ const response = await fetch(`${this.apiUrl}/health`, {
34159
+ signal: AbortSignal.timeout(1e4)
34160
+ // 10 second timeout
34161
+ });
34162
+ if (response.ok) {
34163
+ const healthData = await response.json();
34164
+ return healthData.status === "healthy";
34165
+ }
34166
+ return false;
34167
+ } catch (error) {
34168
+ console.error("[OptifyeAgent] Health check failed:", error);
34169
+ return false;
34170
+ }
34171
+ }
34172
+ };
34173
+ var optifyeAgentClient = new OptifyeAgentClient();
34174
+ async function getManufacturingInsights(userQuestion, lineId, shiftId, companyId, context) {
34175
+ if (!userQuestion || !userQuestion.trim()) {
34176
+ return {
34177
+ success: false,
34178
+ error: "Please provide a question"
34179
+ };
34180
+ }
34181
+ if (!lineId || !companyId) {
34182
+ console.warn("[OptifyeAgent] Missing required IDs:", { lineId, companyId });
34183
+ }
34184
+ return optifyeAgentClient.getManufacturingInsights(
34185
+ userQuestion,
34186
+ lineId,
34187
+ shiftId,
34188
+ companyId,
34189
+ context
34190
+ );
34191
+ }
33550
34192
  function createStreamProxyHandler(config) {
33551
- const cloudFrontDomain = config?.cloudFrontDomain || "https://d1eiob0chi5jw6.cloudfront.net";
34193
+ const cloudFrontDomain = config?.cloudFrontDomain || "https://d1eiob0chi5jw.cloudfront.net";
33552
34194
  return async function handler(req, res) {
33553
34195
  if (req.method !== "GET") {
33554
34196
  return res.status(405).json({ error: "Method not allowed" });
@@ -33730,6 +34372,7 @@ exports.ProfileView = ProfileView_default;
33730
34372
  exports.RegistryProvider = RegistryProvider;
33731
34373
  exports.S3Service = S3Service;
33732
34374
  exports.SOPComplianceChart = SOPComplianceChart;
34375
+ exports.SSEChatClient = SSEChatClient;
33733
34376
  exports.Select = Select;
33734
34377
  exports.SelectContent = SelectContent;
33735
34378
  exports.SelectGroup = SelectGroup;
@@ -33748,6 +34391,7 @@ exports.Skeleton = Skeleton;
33748
34391
  exports.SupabaseProvider = SupabaseProvider;
33749
34392
  exports.TargetWorkspaceGrid = TargetWorkspaceGrid;
33750
34393
  exports.TargetsView = TargetsView_default;
34394
+ exports.ThreadSidebar = ThreadSidebar;
33751
34395
  exports.TimeDisplay = TimeDisplay_default;
33752
34396
  exports.VideoCard = VideoCard;
33753
34397
  exports.VideoGridView = VideoGridView;
@@ -33781,12 +34425,14 @@ exports.createStreamProxyHandler = createStreamProxyHandler;
33781
34425
  exports.createSupabaseClient = createSupabaseClient;
33782
34426
  exports.createThrottledReload = createThrottledReload;
33783
34427
  exports.dashboardService = dashboardService;
34428
+ exports.deleteThread = deleteThread;
33784
34429
  exports.formatDateInZone = formatDateInZone;
33785
34430
  exports.formatDateTimeInZone = formatDateTimeInZone;
33786
34431
  exports.formatISTDate = formatISTDate;
33787
34432
  exports.formatIdleTime = formatIdleTime;
33788
34433
  exports.formatTimeInZone = formatTimeInZone;
33789
34434
  exports.fromUrlFriendlyName = fromUrlFriendlyName;
34435
+ exports.getAllThreadMessages = getAllThreadMessages;
33790
34436
  exports.getAllWorkspaceDisplayNamesAsync = getAllWorkspaceDisplayNamesAsync;
33791
34437
  exports.getAnonClient = getAnonClient;
33792
34438
  exports.getCameraNumber = getCameraNumber;
@@ -33807,6 +34453,9 @@ exports.getS3VideoSrc = getS3VideoSrc;
33807
34453
  exports.getShortWorkspaceDisplayName = getShortWorkspaceDisplayName;
33808
34454
  exports.getShortWorkspaceDisplayNameAsync = getShortWorkspaceDisplayNameAsync;
33809
34455
  exports.getStoredWorkspaceMappings = getStoredWorkspaceMappings;
34456
+ exports.getThreadMessages = getThreadMessages;
34457
+ exports.getUserThreads = getUserThreads;
34458
+ exports.getUserThreadsPaginated = getUserThreadsPaginated;
33810
34459
  exports.getWorkspaceDisplayName = getWorkspaceDisplayName;
33811
34460
  exports.getWorkspaceDisplayNameAsync = getWorkspaceDisplayNameAsync;
33812
34461
  exports.getWorkspaceDisplayNamesMap = getWorkspaceDisplayNamesMap;
@@ -33838,6 +34487,7 @@ exports.throttledReloadDashboard = throttledReloadDashboard;
33838
34487
  exports.toUrlFriendlyName = toUrlFriendlyName;
33839
34488
  exports.trackCoreEvent = trackCoreEvent;
33840
34489
  exports.trackCorePageView = trackCorePageView;
34490
+ exports.updateThreadTitle = updateThreadTitle;
33841
34491
  exports.useAnalyticsConfig = useAnalyticsConfig;
33842
34492
  exports.useAuth = useAuth;
33843
34493
  exports.useAuthConfig = useAuthConfig;
@@ -33861,6 +34511,7 @@ exports.useLineDetailedMetrics = useLineDetailedMetrics;
33861
34511
  exports.useLineKPIs = useLineKPIs;
33862
34512
  exports.useLineMetrics = useLineMetrics;
33863
34513
  exports.useLineWorkspaceMetrics = useLineWorkspaceMetrics;
34514
+ exports.useMessages = useMessages;
33864
34515
  exports.useMetrics = useMetrics;
33865
34516
  exports.useNavigation = useNavigation;
33866
34517
  exports.useOverrides = useOverrides;
@@ -33874,6 +34525,7 @@ exports.useSupabaseClient = useSupabaseClient;
33874
34525
  exports.useTargets = useTargets;
33875
34526
  exports.useTheme = useTheme;
33876
34527
  exports.useThemeConfig = useThemeConfig;
34528
+ exports.useThreads = useThreads;
33877
34529
  exports.useWorkspaceConfig = useWorkspaceConfig;
33878
34530
  exports.useWorkspaceDetailedMetrics = useWorkspaceDetailedMetrics;
33879
34531
  exports.useWorkspaceDisplayName = useWorkspaceDisplayName;