@optifye/dashboard-core 4.1.1 → 4.1.2

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.mjs CHANGED
@@ -25308,6 +25308,32 @@ var S3ClipsService = class {
25308
25308
  return null;
25309
25309
  }
25310
25310
  }
25311
+ /**
25312
+ * Fetches full metadata including timestamps
25313
+ */
25314
+ async getFullMetadata(playlistUri) {
25315
+ try {
25316
+ const metadataUri = playlistUri.replace(/playlist\.m3u8$/, "metadata.json");
25317
+ const url = new URL(metadataUri);
25318
+ const bucket = url.hostname;
25319
+ const key = url.pathname.substring(1);
25320
+ const command = new GetObjectCommand({
25321
+ Bucket: bucket,
25322
+ Key: key
25323
+ });
25324
+ const response = await this.s3Client.send(command);
25325
+ if (!response.Body) {
25326
+ console.warn(`Empty response body for metadata file: ${key}`);
25327
+ return null;
25328
+ }
25329
+ const metadataContent = await response.Body.transformToString();
25330
+ const metadata = JSON.parse(metadataContent);
25331
+ return metadata;
25332
+ } catch (error) {
25333
+ console.error(`Error fetching or parsing metadata:`, error);
25334
+ return null;
25335
+ }
25336
+ }
25311
25337
  /**
25312
25338
  * Converts S3 URI to CloudFront URL
25313
25339
  * Uses streaming proxy for localhost development to handle CORS
@@ -25320,15 +25346,22 @@ var S3ClipsService = class {
25320
25346
  /**
25321
25347
  * Processes a single video completely
25322
25348
  */
25323
- async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime) {
25349
+ async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime, includeMetadata = false) {
25324
25350
  const parsedInfo = parseS3Uri(uri);
25325
25351
  if (!parsedInfo) {
25326
25352
  console.warn(`Skipping URI due to parsing failure: ${uri}`);
25327
25353
  return null;
25328
25354
  }
25329
25355
  let cycleTimeSeconds = null;
25330
- if (includeCycleTime && (parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time") || parsedInfo.type === "best_cycle_time" || parsedInfo.type === "worst_cycle_time")) {
25331
- cycleTimeSeconds = await this.getMetadataCycleTime(uri);
25356
+ let creationTimestamp = void 0;
25357
+ if (includeMetadata || includeCycleTime && (parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time") || parsedInfo.type === "best_cycle_time" || parsedInfo.type === "worst_cycle_time")) {
25358
+ const metadata = await this.getFullMetadata(uri);
25359
+ if (metadata) {
25360
+ if (metadata.original_task_metadata?.cycle_time) {
25361
+ cycleTimeSeconds = metadata.original_task_metadata.cycle_time;
25362
+ }
25363
+ creationTimestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp || metadata[""];
25364
+ }
25332
25365
  }
25333
25366
  const cloudfrontPlaylistUrl = this.s3UriToCloudfront(uri);
25334
25367
  const { type: videoType, timestamp: videoTimestamp } = parsedInfo;
@@ -25337,7 +25370,8 @@ var S3ClipsService = class {
25337
25370
  src: cloudfrontPlaylistUrl,
25338
25371
  // Direct CloudFront playlist URL
25339
25372
  ...parsedInfo,
25340
- cycle_time_seconds: cycleTimeSeconds || void 0
25373
+ cycle_time_seconds: cycleTimeSeconds || void 0,
25374
+ creation_timestamp: creationTimestamp
25341
25375
  };
25342
25376
  }
25343
25377
  /**
@@ -25399,7 +25433,7 @@ var S3ClipsService = class {
25399
25433
  * Main method to fetch clips based on parameters
25400
25434
  */
25401
25435
  async fetchClips(params) {
25402
- const { workspaceId, date: inputDate, shift, category, limit, mode, includeCycleTime } = params;
25436
+ const { workspaceId, date: inputDate, shift, category, limit, mode, includeCycleTime, includeMetadata, timestampStart, timestampEnd } = params;
25403
25437
  if (!workspaceId) {
25404
25438
  throw new Error("Valid Workspace ID is required");
25405
25439
  }
@@ -25504,7 +25538,7 @@ var S3ClipsService = class {
25504
25538
  const batch = filteredUris.slice(i, i + concurrencyLimit);
25505
25539
  const batchPromises = batch.map(async (uri, batchIndex) => {
25506
25540
  const index = i + batchIndex;
25507
- const result = await this.processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime || false);
25541
+ const result = await this.processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime || false, includeMetadata || (!!timestampStart || !!timestampEnd));
25508
25542
  processedCount++;
25509
25543
  if (processedCount % 10 === 0) {
25510
25544
  console.log(`S3ClipsService: Processed ${processedCount}/${filteredUris.length} videos for category '${category || "all"}'...`);
@@ -25514,7 +25548,26 @@ var S3ClipsService = class {
25514
25548
  const batchResults = await Promise.all(batchPromises);
25515
25549
  videoResults.push(...batchResults);
25516
25550
  }
25517
- const videos = videoResults.filter((v) => v !== null);
25551
+ let videos = videoResults.filter((v) => v !== null);
25552
+ if (timestampStart || timestampEnd) {
25553
+ videos = videos.filter((video) => {
25554
+ if (!video.creation_timestamp) return false;
25555
+ const videoTimestamp = new Date(video.creation_timestamp).getTime();
25556
+ if (timestampStart && timestampEnd) {
25557
+ const start = new Date(timestampStart).getTime();
25558
+ const end = new Date(timestampEnd).getTime();
25559
+ return videoTimestamp >= start && videoTimestamp <= end;
25560
+ } else if (timestampStart) {
25561
+ const start = new Date(timestampStart).getTime();
25562
+ return videoTimestamp >= start;
25563
+ } else if (timestampEnd) {
25564
+ const end = new Date(timestampEnd).getTime();
25565
+ return videoTimestamp <= end;
25566
+ }
25567
+ return true;
25568
+ });
25569
+ console.log(`S3ClipsService: Filtered by timestamp - ${videos.length} videos remain after filtering`);
25570
+ }
25518
25571
  console.log(`S3ClipsService: Successfully processed ${videos.length} out of ${filteredUris.length} video clips for category '${category || "all"}' (limit: ${limitPerCategory} per category).`);
25519
25572
  const typeCounts = videos.reduce((acc, video) => {
25520
25573
  acc[video.type] = (acc[video.type] || 0) + 1;
@@ -25559,6 +25612,7 @@ var BottlenecksContent = ({
25559
25612
  const dashboardConfig = useDashboardConfig();
25560
25613
  const videoRef = useRef(null);
25561
25614
  const fullscreenContainerRef = useRef(null);
25615
+ const timestampFilterRef = useRef(null);
25562
25616
  const [isPlaying, setIsPlaying] = useState(false);
25563
25617
  const [currentTime, setCurrentTime] = useState(0);
25564
25618
  const [duration, setDuration] = useState(0);
@@ -25568,6 +25622,22 @@ var BottlenecksContent = ({
25568
25622
  const [allVideos, setAllVideos] = useState([]);
25569
25623
  const [isLoading, setIsLoading] = useState(true);
25570
25624
  const [error, setError] = useState(null);
25625
+ const [showTimestampFilter, setShowTimestampFilter] = useState(false);
25626
+ const [timestampStart, setTimestampStart] = useState("");
25627
+ const [timestampEnd, setTimestampEnd] = useState("");
25628
+ useEffect(() => {
25629
+ const handleClickOutside = (event) => {
25630
+ if (timestampFilterRef.current && !timestampFilterRef.current.contains(event.target)) {
25631
+ setShowTimestampFilter(false);
25632
+ }
25633
+ };
25634
+ if (showTimestampFilter) {
25635
+ document.addEventListener("mousedown", handleClickOutside);
25636
+ }
25637
+ return () => {
25638
+ document.removeEventListener("mousedown", handleClickOutside);
25639
+ };
25640
+ }, [showTimestampFilter]);
25571
25641
  const s3ClipsService = useMemo(() => {
25572
25642
  if (!dashboardConfig?.s3Config) {
25573
25643
  console.warn("S3 configuration not found in dashboard config");
@@ -25580,13 +25650,26 @@ var BottlenecksContent = ({
25580
25650
  setIsLoading(true);
25581
25651
  setError(null);
25582
25652
  try {
25653
+ const operationalDate = date || getOperationalDate();
25654
+ let timestampStartFull;
25655
+ let timestampEndFull;
25656
+ if (timestampStart) {
25657
+ timestampStartFull = `${operationalDate}T${timestampStart}:00`;
25658
+ }
25659
+ if (timestampEnd) {
25660
+ timestampEndFull = `${operationalDate}T${timestampEnd}:00`;
25661
+ }
25583
25662
  const videos = await s3ClipsService.fetchClips({
25584
25663
  workspaceId,
25585
- date: date || getOperationalDate(),
25664
+ date: operationalDate,
25586
25665
  mode: "full",
25587
25666
  includeCycleTime: true,
25588
- limit: 50
25667
+ includeMetadata: true,
25668
+ // Always include metadata for timestamp info
25669
+ limit: 50,
25589
25670
  // Reasonable limit for UI performance
25671
+ timestampStart: timestampStartFull,
25672
+ timestampEnd: timestampEndFull
25590
25673
  });
25591
25674
  if (Array.isArray(videos) && videos.length > 0) {
25592
25675
  preloadVideoUrl2(videos[0].src);
@@ -25614,7 +25697,7 @@ var BottlenecksContent = ({
25614
25697
  } finally {
25615
25698
  setIsLoading(false);
25616
25699
  }
25617
- }, [workspaceId, date, s3ClipsService]);
25700
+ }, [workspaceId, date, s3ClipsService, timestampStart, timestampEnd]);
25618
25701
  useEffect(() => {
25619
25702
  if (s3ClipsService) {
25620
25703
  fetchClips();
@@ -25983,6 +26066,43 @@ var BottlenecksContent = ({
25983
26066
  return "Bottleneck";
25984
26067
  }
25985
26068
  };
26069
+ const formatTimestamp = (timestamp) => {
26070
+ if (!timestamp) return "";
26071
+ try {
26072
+ const date2 = new Date(timestamp);
26073
+ const today = /* @__PURE__ */ new Date();
26074
+ const isToday = date2.toDateString() === today.toDateString();
26075
+ if (isToday) {
26076
+ return date2.toLocaleString("en-US", {
26077
+ hour: "numeric",
26078
+ minute: "2-digit",
26079
+ hour12: true
26080
+ });
26081
+ } else {
26082
+ return date2.toLocaleString("en-US", {
26083
+ month: "short",
26084
+ day: "numeric",
26085
+ hour: "numeric",
26086
+ minute: "2-digit",
26087
+ hour12: true
26088
+ });
26089
+ }
26090
+ } catch {
26091
+ return "";
26092
+ }
26093
+ };
26094
+ const formatTimeOnly = (time2) => {
26095
+ if (!time2) return "";
26096
+ try {
26097
+ const [hours, minutes] = time2.split(":");
26098
+ const hour = parseInt(hours);
26099
+ const ampm = hour >= 12 ? "PM" : "AM";
26100
+ const displayHour = hour % 12 || 12;
26101
+ return `${displayHour}:${minutes} ${ampm}`;
26102
+ } catch {
26103
+ return time2;
26104
+ }
26105
+ };
25986
26106
  if (!dashboardConfig?.s3Config) {
25987
26107
  return /* @__PURE__ */ jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
25988
26108
  /* @__PURE__ */ jsx(XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
@@ -26091,6 +26211,74 @@ var BottlenecksContent = ({
26091
26211
  /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b border-gray-100", children: /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
26092
26212
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-800", children: activeFilter === "low_value" ? `Low Value Moments (${clipCounts.lowValue})` : activeFilter === "best_cycle_time" ? `Best Cycle Times (${clipCounts.bestCycleTimes})` : activeFilter === "worst_cycle_time" ? `Worst Cycle Times (${clipCounts.worstCycleTimes})` : activeFilter === "long_cycle_time" ? `Long Cycle Time (${clipCounts.longCycleTimes})` : `All Clips (${clipCounts.total})` }),
26093
26213
  /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
26214
+ /* @__PURE__ */ jsxs("div", { className: "relative", ref: timestampFilterRef, children: [
26215
+ /* @__PURE__ */ jsx(
26216
+ "button",
26217
+ {
26218
+ onClick: () => setShowTimestampFilter(!showTimestampFilter),
26219
+ className: `p-2 rounded-lg transition-all ${timestampStart || timestampEnd ? "bg-blue-50 text-blue-600 hover:bg-blue-100" : "text-gray-600 hover:bg-gray-100"}`,
26220
+ "aria-label": "Filter by time",
26221
+ title: "Filter by time",
26222
+ children: /* @__PURE__ */ jsx(Clock, { className: "h-5 w-5" })
26223
+ }
26224
+ ),
26225
+ showTimestampFilter && /* @__PURE__ */ jsxs("div", { className: "absolute right-0 mt-2 w-80 bg-white rounded-lg shadow-lg border border-gray-200 p-4 z-20", children: [
26226
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Filter by Time" }),
26227
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
26228
+ /* @__PURE__ */ jsxs("div", { children: [
26229
+ /* @__PURE__ */ jsx("label", { htmlFor: "timestamp-start", className: "block text-xs font-medium text-gray-600 mb-1", children: "Start Time" }),
26230
+ /* @__PURE__ */ jsx(
26231
+ "input",
26232
+ {
26233
+ id: "timestamp-start",
26234
+ type: "time",
26235
+ value: timestampStart,
26236
+ onChange: (e) => setTimestampStart(e.target.value),
26237
+ className: "w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
26238
+ }
26239
+ )
26240
+ ] }),
26241
+ /* @__PURE__ */ jsxs("div", { children: [
26242
+ /* @__PURE__ */ jsx("label", { htmlFor: "timestamp-end", className: "block text-xs font-medium text-gray-600 mb-1", children: "End Time" }),
26243
+ /* @__PURE__ */ jsx(
26244
+ "input",
26245
+ {
26246
+ id: "timestamp-end",
26247
+ type: "time",
26248
+ value: timestampEnd,
26249
+ onChange: (e) => setTimestampEnd(e.target.value),
26250
+ className: "w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
26251
+ }
26252
+ )
26253
+ ] }),
26254
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between pt-2", children: [
26255
+ /* @__PURE__ */ jsx(
26256
+ "button",
26257
+ {
26258
+ onClick: () => {
26259
+ setTimestampStart("");
26260
+ setTimestampEnd("");
26261
+ setShowTimestampFilter(false);
26262
+ },
26263
+ className: "px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 transition-colors",
26264
+ children: "Clear"
26265
+ }
26266
+ ),
26267
+ /* @__PURE__ */ jsx(
26268
+ "button",
26269
+ {
26270
+ onClick: () => {
26271
+ setShowTimestampFilter(false);
26272
+ fetchClips();
26273
+ },
26274
+ className: "px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
26275
+ children: "Apply Filter"
26276
+ }
26277
+ )
26278
+ ] })
26279
+ ] })
26280
+ ] })
26281
+ ] }),
26094
26282
  /* @__PURE__ */ jsx(
26095
26283
  "button",
26096
26284
  {
@@ -26114,6 +26302,29 @@ var BottlenecksContent = ({
26114
26302
  )
26115
26303
  ] })
26116
26304
  ] }) }),
26305
+ (timestampStart || timestampEnd) && /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 bg-blue-50 border-b border-blue-100 flex items-center justify-between", children: [
26306
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2 text-sm text-blue-700", children: [
26307
+ /* @__PURE__ */ jsx(Clock, { className: "h-4 w-4" }),
26308
+ /* @__PURE__ */ jsxs("span", { children: [
26309
+ "Filtered by time: ",
26310
+ timestampStart ? formatTimeOnly(timestampStart) : "Any",
26311
+ " - ",
26312
+ timestampEnd ? formatTimeOnly(timestampEnd) : "Any"
26313
+ ] })
26314
+ ] }),
26315
+ /* @__PURE__ */ jsx(
26316
+ "button",
26317
+ {
26318
+ onClick: () => {
26319
+ setTimestampStart("");
26320
+ setTimestampEnd("");
26321
+ fetchClips();
26322
+ },
26323
+ className: "text-sm text-blue-600 hover:text-blue-800 transition-colors",
26324
+ children: "Clear filter"
26325
+ }
26326
+ )
26327
+ ] }),
26117
26328
  isLoading && allVideos.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsx(LoadingSpinner2, { size: "md", message: "Loading clips..." }) }) : allVideos.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxs("div", { className: "text-center p-8", children: [
26118
26329
  /* @__PURE__ */ jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
26119
26330
  /* @__PURE__ */ jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Clips Found" }),
@@ -26173,6 +26384,7 @@ var BottlenecksContent = ({
26173
26384
  /* @__PURE__ */ jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
26174
26385
  ] }) })
26175
26386
  ),
26387
+ currentVideo.creation_timestamp && /* @__PURE__ */ jsx("div", { className: "absolute bottom-3 left-3 z-10 bg-black/50 backdrop-blur-sm px-2 py-1 rounded text-white shadow-sm text-xs", children: /* @__PURE__ */ jsx("span", { className: "opacity-80", children: formatTimestamp(currentVideo.creation_timestamp) }) }),
26176
26388
  /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-white", children: [
26177
26389
  /* @__PURE__ */ jsx(
26178
26390
  "button",
@@ -28721,6 +28933,23 @@ var ThreadSidebar = ({
28721
28933
  ] });
28722
28934
  };
28723
28935
  var axelProfilePng = "/axel-profile.png";
28936
+ var ProfilePicture = React46__default.memo(({ alt = "Axel", className = "w-12 h-12" }) => {
28937
+ return /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: `${className} rounded-xl overflow-hidden shadow-sm`, children: /* @__PURE__ */ jsx(
28938
+ "img",
28939
+ {
28940
+ src: axelProfilePng,
28941
+ alt,
28942
+ className: "w-full h-full object-cover",
28943
+ loading: "eager",
28944
+ decoding: "async"
28945
+ }
28946
+ ) }) });
28947
+ });
28948
+ ProfilePicture.displayName = "ProfilePicture";
28949
+ var preloadImage = (src) => {
28950
+ const img = new Image();
28951
+ img.src = src;
28952
+ };
28724
28953
  var AIAgentView = () => {
28725
28954
  const { navigate, pathname } = useNavigation();
28726
28955
  const config = useDashboardConfig();
@@ -28739,12 +28968,85 @@ var AIAgentView = () => {
28739
28968
  const [isTransitioning, setIsTransitioning] = useState(false);
28740
28969
  const [typedText, setTypedText] = useState("");
28741
28970
  const [newChatCount, setNewChatCount] = useState(0);
28971
+ const [hasStartedTyping, setHasStartedTyping] = useState(false);
28972
+ const [typingStartTime, setTypingStartTime] = useState(null);
28973
+ const [lastTypingTime, setLastTypingTime] = useState(null);
28974
+ const [characterCount, setCharacterCount] = useState(0);
28975
+ const typingTimeoutRef = useRef(null);
28742
28976
  const isThreadLoading = (threadId) => {
28743
28977
  return threadId ? loadingThreads.has(threadId) : false;
28744
28978
  };
28745
28979
  const getStreamingState = (threadId) => {
28746
28980
  return threadId ? streamingStates.get(threadId) || { message: "", reasoning: "" } : { message: "", reasoning: "" };
28747
28981
  };
28982
+ const trackTypingStart = () => {
28983
+ if (!hasStartedTyping) {
28984
+ const now2 = Date.now();
28985
+ setHasStartedTyping(true);
28986
+ setTypingStartTime(now2);
28987
+ setLastTypingTime(now2);
28988
+ trackCoreEvent("AI Agent Input Started", {
28989
+ line_id: lineId,
28990
+ company_id: companyId,
28991
+ shift_id: shiftId,
28992
+ active_thread_id: activeThreadId,
28993
+ has_existing_messages: messages.length > 0,
28994
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
28995
+ });
28996
+ }
28997
+ };
28998
+ const trackTypingProgress = (newValue) => {
28999
+ const now2 = Date.now();
29000
+ setLastTypingTime(now2);
29001
+ setCharacterCount(newValue.length);
29002
+ if (typingTimeoutRef.current) {
29003
+ clearTimeout(typingTimeoutRef.current);
29004
+ }
29005
+ typingTimeoutRef.current = setTimeout(() => {
29006
+ if (hasStartedTyping && typingStartTime && newValue.length > 0) {
29007
+ const typingDuration = now2 - typingStartTime;
29008
+ trackCoreEvent("AI Agent Input Typing Progress", {
29009
+ line_id: lineId,
29010
+ company_id: companyId,
29011
+ shift_id: shiftId,
29012
+ active_thread_id: activeThreadId,
29013
+ character_count: newValue.length,
29014
+ typing_duration_ms: typingDuration,
29015
+ has_existing_messages: messages.length > 0,
29016
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
29017
+ });
29018
+ }
29019
+ }, 2e3);
29020
+ };
29021
+ const trackMessageSent = (messageContent) => {
29022
+ if (hasStartedTyping && typingStartTime) {
29023
+ const now2 = Date.now();
29024
+ const totalTypingDuration = now2 - typingStartTime;
29025
+ trackCoreEvent("AI Agent Message Sent", {
29026
+ line_id: lineId,
29027
+ company_id: companyId,
29028
+ shift_id: shiftId,
29029
+ active_thread_id: activeThreadId,
29030
+ message_length: messageContent.length,
29031
+ character_count: messageContent.length,
29032
+ typing_duration_ms: totalTypingDuration,
29033
+ has_existing_messages: messages.length > 0,
29034
+ is_new_conversation: !activeThreadId,
29035
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
29036
+ });
29037
+ }
29038
+ resetTypingState();
29039
+ };
29040
+ const resetTypingState = () => {
29041
+ setHasStartedTyping(false);
29042
+ setTypingStartTime(null);
29043
+ setLastTypingTime(null);
29044
+ setCharacterCount(0);
29045
+ if (typingTimeoutRef.current) {
29046
+ clearTimeout(typingTimeoutRef.current);
29047
+ typingTimeoutRef.current = null;
29048
+ }
29049
+ };
28748
29050
  const textareaRef = useRef(null);
28749
29051
  const messagesEndRef = useRef(null);
28750
29052
  const containerRef = useRef(null);
@@ -28767,7 +29069,7 @@ var AIAgentView = () => {
28767
29069
  const { shiftId } = getCurrentShift(dateTimeConfig.defaultTimezone || "Asia/Kolkata", shiftConfig);
28768
29070
  const companyId = entityConfig.companyId || "default-company-id";
28769
29071
  const ACTIVE_THREAD_STORAGE_KEY = `ai-agent-active-thread-${lineId}`;
28770
- useEffect(() => {
29072
+ useLayoutEffect(() => {
28771
29073
  const savedThreadId = localStorage.getItem(ACTIVE_THREAD_STORAGE_KEY);
28772
29074
  if (savedThreadId && savedThreadId !== "undefined") {
28773
29075
  setActiveThreadId(savedThreadId);
@@ -28780,6 +29082,27 @@ var AIAgentView = () => {
28780
29082
  localStorage.removeItem(ACTIVE_THREAD_STORAGE_KEY);
28781
29083
  }
28782
29084
  }, [activeThreadId, ACTIVE_THREAD_STORAGE_KEY]);
29085
+ useEffect(() => {
29086
+ const handleVisibilityChange = () => {
29087
+ if (document.visibilityState === "hidden" && activeThreadId) {
29088
+ localStorage.setItem(ACTIVE_THREAD_STORAGE_KEY, activeThreadId);
29089
+ }
29090
+ };
29091
+ const handleBeforeUnload = () => {
29092
+ if (activeThreadId) {
29093
+ localStorage.setItem(ACTIVE_THREAD_STORAGE_KEY, activeThreadId);
29094
+ }
29095
+ };
29096
+ document.addEventListener("visibilitychange", handleVisibilityChange);
29097
+ window.addEventListener("beforeunload", handleBeforeUnload);
29098
+ return () => {
29099
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
29100
+ window.removeEventListener("beforeunload", handleBeforeUnload);
29101
+ if (activeThreadId) {
29102
+ localStorage.setItem(ACTIVE_THREAD_STORAGE_KEY, activeThreadId);
29103
+ }
29104
+ };
29105
+ }, [activeThreadId, ACTIVE_THREAD_STORAGE_KEY]);
28783
29106
  useEffect(() => {
28784
29107
  if (textareaRef.current) {
28785
29108
  textareaRef.current.style.height = "auto";
@@ -28830,9 +29153,20 @@ var AIAgentView = () => {
28830
29153
  setInputValue("");
28831
29154
  setPendingThreadId(null);
28832
29155
  setTypedText("");
29156
+ resetTypingState();
28833
29157
  localStorage.removeItem(ACTIVE_THREAD_STORAGE_KEY);
28834
29158
  textareaRef.current?.focus();
28835
29159
  };
29160
+ useEffect(() => {
29161
+ preloadImage(axelProfilePng);
29162
+ }, []);
29163
+ useEffect(() => {
29164
+ return () => {
29165
+ if (typingTimeoutRef.current) {
29166
+ clearTimeout(typingTimeoutRef.current);
29167
+ }
29168
+ };
29169
+ }, []);
28836
29170
  useEffect(() => {
28837
29171
  const checkAuth = async () => {
28838
29172
  const supabase2 = _getSupabaseInstance();
@@ -28863,6 +29197,7 @@ var AIAgentView = () => {
28863
29197
  let currentThreadId = activeThreadId || `temp-${Date.now()}`;
28864
29198
  if (isThreadLoading(currentThreadId)) return;
28865
29199
  const userMessage = inputValue.trim();
29200
+ trackMessageSent(userMessage);
28866
29201
  if (displayMessages.length === 0) {
28867
29202
  setIsTransitioning(true);
28868
29203
  setTimeout(() => {
@@ -29270,18 +29605,11 @@ var AIAgentView = () => {
29270
29605
  {
29271
29606
  ref: containerRef,
29272
29607
  className: `flex-1 bg-gray-50/50 min-h-0 ${displayMessages.length === 0 && !isTransitioning ? "flex items-center justify-center" : "overflow-y-auto"}`,
29273
- children: displayMessages.length === 0 && !isTransitioning ? (
29608
+ children: !activeThreadId && displayMessages.length === 0 && !isTransitioning ? (
29274
29609
  /* Centered welcome and input for new chat */
29275
29610
  /* @__PURE__ */ jsxs("div", { className: "w-full max-w-3xl mx-auto px-4 sm:px-6 flex flex-col items-center justify-center space-y-12 -mt-16", children: [
29276
29611
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
29277
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-8", children: /* @__PURE__ */ jsx("div", { className: "w-24 h-24 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsx(
29278
- "img",
29279
- {
29280
- src: axelProfilePng,
29281
- alt: "Axel - AI Manufacturing Expert",
29282
- className: "w-full h-full object-cover"
29283
- }
29284
- ) }) }),
29612
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-8", children: /* @__PURE__ */ jsx(ProfilePicture, { alt: "Axel - AI Manufacturing Expert", className: "w-24 h-24" }) }),
29285
29613
  /* @__PURE__ */ jsxs("h2", { className: "text-3xl font-semibold text-gray-900", children: [
29286
29614
  typedText,
29287
29615
  typedText.length < "Hi, I'm Axel - Your AI Supervisor".length && /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "|" })
@@ -29306,8 +29634,30 @@ var AIAgentView = () => {
29306
29634
  {
29307
29635
  ref: textareaRef,
29308
29636
  value: inputValue,
29309
- onChange: (e) => setInputValue(e.target.value),
29637
+ onChange: (e) => {
29638
+ const newValue = e.target.value;
29639
+ setInputValue(newValue);
29640
+ if (newValue.length > 0 && !hasStartedTyping) {
29641
+ trackTypingStart();
29642
+ }
29643
+ if (newValue.length > 0) {
29644
+ trackTypingProgress(newValue);
29645
+ }
29646
+ if (newValue.length === 0) {
29647
+ resetTypingState();
29648
+ }
29649
+ },
29310
29650
  onKeyDown: handleKeyDown,
29651
+ onFocus: () => {
29652
+ trackCoreEvent("AI Agent Input Focused", {
29653
+ line_id: lineId,
29654
+ company_id: companyId,
29655
+ shift_id: shiftId,
29656
+ active_thread_id: activeThreadId,
29657
+ has_existing_messages: messages.length > 0,
29658
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
29659
+ });
29660
+ },
29311
29661
  placeholder: "Ask me about production optimization, quality metrics, or any manufacturing challenge...",
29312
29662
  className: "w-full resize-none bg-transparent px-2 py-2 pr-12 focus:outline-none placeholder-gray-500 text-gray-900 text-sm leading-relaxed",
29313
29663
  rows: 1,
@@ -29342,14 +29692,7 @@ var AIAgentView = () => {
29342
29692
  {
29343
29693
  className: `flex gap-4 ${message.role === "user" ? "justify-end" : "justify-start"}`,
29344
29694
  children: [
29345
- message.role === "assistant" && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsx(
29346
- "img",
29347
- {
29348
- src: axelProfilePng,
29349
- alt: "Axel",
29350
- className: "w-full h-full object-cover"
29351
- }
29352
- ) }) }),
29695
+ message.role === "assistant" && /* @__PURE__ */ jsx(ProfilePicture, {}),
29353
29696
  /* @__PURE__ */ jsx("div", { className: `max-w-none w-full group ${message.role === "user" ? "order-1" : ""}`, children: /* @__PURE__ */ jsx(
29354
29697
  "div",
29355
29698
  {
@@ -29362,17 +29705,10 @@ var AIAgentView = () => {
29362
29705
  ) })
29363
29706
  ]
29364
29707
  },
29365
- message.id === -1 ? `streaming-${currentStreaming.message.length}` : `${message.id}-${index}`
29708
+ message.id === -1 ? "streaming-message" : `${message.id}-${index}`
29366
29709
  )),
29367
29710
  /* @__PURE__ */ jsxs("div", { className: "flex gap-4 justify-start", children: [
29368
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsx(
29369
- "img",
29370
- {
29371
- src: axelProfilePng,
29372
- alt: "Axel",
29373
- className: "w-full h-full object-cover"
29374
- }
29375
- ) }) }),
29711
+ /* @__PURE__ */ jsx(ProfilePicture, {}),
29376
29712
  /* @__PURE__ */ jsx("div", { className: "bg-white border border-gray-200/80 px-5 py-4 rounded-2xl shadow-sm max-w-full", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
29377
29713
  /* @__PURE__ */ jsxs("div", { className: "flex space-x-1", children: [
29378
29714
  /* @__PURE__ */ jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce" }),
@@ -29391,14 +29727,7 @@ var AIAgentView = () => {
29391
29727
  {
29392
29728
  className: `flex gap-4 ${message.role === "user" ? "justify-end" : "justify-start"}`,
29393
29729
  children: [
29394
- message.role === "assistant" && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsx(
29395
- "img",
29396
- {
29397
- src: axelProfilePng,
29398
- alt: "Axel",
29399
- className: "w-full h-full object-cover"
29400
- }
29401
- ) }) }),
29730
+ message.role === "assistant" && /* @__PURE__ */ jsx(ProfilePicture, {}),
29402
29731
  /* @__PURE__ */ jsxs("div", { className: `max-w-none w-full group ${message.role === "user" ? "order-1" : ""}`, children: [
29403
29732
  /* @__PURE__ */ jsxs(
29404
29733
  "div",
@@ -29435,7 +29764,7 @@ var AIAgentView = () => {
29435
29764
  ] })
29436
29765
  ]
29437
29766
  },
29438
- message.id === -1 ? `streaming-${currentStreaming.message.length}` : `${message.id}-${index}`
29767
+ message.id === -1 ? "streaming-message" : `${message.id}-${index}`
29439
29768
  )),
29440
29769
  lastError && /* @__PURE__ */ jsxs("div", { className: "flex gap-4 justify-start", children: [
29441
29770
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-xl bg-red-100 flex items-center justify-center", children: /* @__PURE__ */ jsx(AlertCircle, { className: "w-6 h-6 text-red-600" }) }) }),
@@ -29452,14 +29781,7 @@ var AIAgentView = () => {
29452
29781
  ] })
29453
29782
  ] }),
29454
29783
  isCurrentThreadLoading && !currentStreaming.message && /* @__PURE__ */ jsxs("div", { className: "flex gap-4 justify-start", children: [
29455
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__PURE__ */ jsx(
29456
- "img",
29457
- {
29458
- src: axelProfilePng,
29459
- alt: "Axel",
29460
- className: "w-full h-full object-cover"
29461
- }
29462
- ) }) }),
29784
+ /* @__PURE__ */ jsx(ProfilePicture, {}),
29463
29785
  /* @__PURE__ */ jsx("div", { className: "bg-white border border-gray-200/80 px-5 py-4 rounded-2xl shadow-sm max-w-full", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
29464
29786
  /* @__PURE__ */ jsxs("div", { className: "flex space-x-1", children: [
29465
29787
  /* @__PURE__ */ jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce" }),
@@ -29500,8 +29822,30 @@ var AIAgentView = () => {
29500
29822
  {
29501
29823
  ref: textareaRef,
29502
29824
  value: inputValue,
29503
- onChange: (e) => setInputValue(e.target.value),
29825
+ onChange: (e) => {
29826
+ const newValue = e.target.value;
29827
+ setInputValue(newValue);
29828
+ if (newValue.length > 0 && !hasStartedTyping) {
29829
+ trackTypingStart();
29830
+ }
29831
+ if (newValue.length > 0) {
29832
+ trackTypingProgress(newValue);
29833
+ }
29834
+ if (newValue.length === 0) {
29835
+ resetTypingState();
29836
+ }
29837
+ },
29504
29838
  onKeyDown: handleKeyDown,
29839
+ onFocus: () => {
29840
+ trackCoreEvent("AI Agent Input Focused", {
29841
+ line_id: lineId,
29842
+ company_id: companyId,
29843
+ shift_id: shiftId,
29844
+ active_thread_id: activeThreadId,
29845
+ has_existing_messages: messages.length > 0,
29846
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
29847
+ });
29848
+ },
29505
29849
  placeholder: "Ask me about production optimization, quality metrics, or any manufacturing challenge...",
29506
29850
  className: "w-full resize-none bg-transparent px-2 py-2 pr-12 focus:outline-none placeholder-gray-500 text-gray-900 text-sm leading-relaxed",
29507
29851
  rows: 1,
@@ -31415,7 +31759,8 @@ var parseBreaksFromDB = (dbBreaks) => {
31415
31759
  duration: calculateBreakDuration(
31416
31760
  breakItem.start || breakItem.startTime || "00:00",
31417
31761
  breakItem.end || breakItem.endTime || "00:00"
31418
- )
31762
+ ),
31763
+ remarks: breakItem.remarks || breakItem.name || ""
31419
31764
  }));
31420
31765
  } else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
31421
31766
  return dbBreaks.breaks.map((breakItem) => ({
@@ -31424,7 +31769,8 @@ var parseBreaksFromDB = (dbBreaks) => {
31424
31769
  duration: calculateBreakDuration(
31425
31770
  breakItem.start || breakItem.startTime || "00:00",
31426
31771
  breakItem.end || breakItem.endTime || "00:00"
31427
- )
31772
+ ),
31773
+ remarks: breakItem.remarks || breakItem.name || ""
31428
31774
  }));
31429
31775
  } else {
31430
31776
  console.warn("Unexpected breaks format:", dbBreaks);
@@ -31442,7 +31788,8 @@ var formatBreaks = (breaks) => {
31442
31788
  return {
31443
31789
  breaks: breaks.map((breakItem) => ({
31444
31790
  start: breakItem.startTime,
31445
- end: breakItem.endTime
31791
+ end: breakItem.endTime,
31792
+ remarks: breakItem.remarks || ""
31446
31793
  }))
31447
31794
  };
31448
31795
  };
@@ -31452,13 +31799,14 @@ var BreakRow = memo(({
31452
31799
  onRemove,
31453
31800
  index
31454
31801
  }) => {
31455
- return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-9 gap-2 sm:gap-4 items-center w-full bg-white hover:bg-gray-50 rounded-md transition-all duration-200 p-2", children: [
31802
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-2 sm:gap-4 items-center w-full bg-white hover:bg-gray-50 rounded-md transition-all duration-200 p-2", children: [
31456
31803
  /* @__PURE__ */ jsx("div", { className: "col-span-3", children: /* @__PURE__ */ jsx(
31457
31804
  "input",
31458
31805
  {
31459
31806
  type: "time",
31460
31807
  value: breakItem.startTime,
31461
31808
  onChange: (e) => onUpdate(index, "startTime", e.target.value),
31809
+ step: "60",
31462
31810
  className: "w-full px-2 sm:px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
31463
31811
  }
31464
31812
  ) }),
@@ -31468,6 +31816,7 @@ var BreakRow = memo(({
31468
31816
  type: "time",
31469
31817
  value: breakItem.endTime,
31470
31818
  onChange: (e) => onUpdate(index, "endTime", e.target.value),
31819
+ step: "60",
31471
31820
  className: "w-full px-2 sm:px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
31472
31821
  }
31473
31822
  ) }),
@@ -31475,6 +31824,16 @@ var BreakRow = memo(({
31475
31824
  breakItem.duration,
31476
31825
  " min"
31477
31826
  ] }) }),
31827
+ /* @__PURE__ */ jsx("div", { className: "col-span-3", children: /* @__PURE__ */ jsx(
31828
+ "input",
31829
+ {
31830
+ type: "text",
31831
+ value: breakItem.remarks || "",
31832
+ onChange: (e) => onUpdate(index, "remarks", e.target.value),
31833
+ placeholder: "Break remarks",
31834
+ className: "w-full px-2 sm:px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
31835
+ }
31836
+ ) }),
31478
31837
  /* @__PURE__ */ jsx("div", { className: "col-span-1 flex justify-center", children: /* @__PURE__ */ jsx(
31479
31838
  "button",
31480
31839
  {
@@ -31591,10 +31950,11 @@ var ShiftPanel = memo(({
31591
31950
  "Breaks"
31592
31951
  ] }) }),
31593
31952
  /* @__PURE__ */ jsx("div", { className: "space-y-2 mb-4 w-full", children: breaks.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
31594
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-9 gap-2 sm:gap-4 text-xs font-medium text-gray-500 mb-1 w-full px-2", children: [
31953
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-2 sm:gap-4 text-xs font-medium text-gray-500 mb-1 w-full px-2", children: [
31595
31954
  /* @__PURE__ */ jsx("div", { className: "col-span-3", children: "Break Start" }),
31596
31955
  /* @__PURE__ */ jsx("div", { className: "col-span-3", children: "Break End" }),
31597
31956
  /* @__PURE__ */ jsx("div", { className: "col-span-2", children: "Duration" }),
31957
+ /* @__PURE__ */ jsx("div", { className: "col-span-3", children: "Remarks" }),
31598
31958
  /* @__PURE__ */ jsx("div", { className: "col-span-1" })
31599
31959
  ] }),
31600
31960
  /* @__PURE__ */ jsx("div", { className: "bg-gray-50/80 p-2 rounded-md", children: breaks.map((breakItem, index) => /* @__PURE__ */ jsx(
@@ -31807,7 +32167,8 @@ var ShiftsView = ({
31807
32167
  const newBreak = {
31808
32168
  startTime: dayShift.startTime,
31809
32169
  endTime: dayShift.startTime,
31810
- duration: 0
32170
+ duration: 0,
32171
+ remarks: ""
31811
32172
  };
31812
32173
  return {
31813
32174
  ...typedConfig,
@@ -31848,14 +32209,16 @@ var ShiftsView = ({
31848
32209
  const dayShift = { ...typedConfig.dayShift };
31849
32210
  const newBreaks = [...dayShift.breaks];
31850
32211
  newBreaks[index] = { ...newBreaks[index], [field]: value };
31851
- const startParts = newBreaks[index].startTime.split(":").map(Number);
31852
- const endParts = newBreaks[index].endTime.split(":").map(Number);
31853
- let startMinutes = startParts[0] * 60 + startParts[1];
31854
- let endMinutes = endParts[0] * 60 + endParts[1];
31855
- if (endMinutes < startMinutes) {
31856
- endMinutes += 24 * 60;
31857
- }
31858
- newBreaks[index].duration = endMinutes - startMinutes;
32212
+ if (field === "startTime" || field === "endTime") {
32213
+ const startParts = newBreaks[index].startTime.split(":").map(Number);
32214
+ const endParts = newBreaks[index].endTime.split(":").map(Number);
32215
+ let startMinutes = startParts[0] * 60 + startParts[1];
32216
+ let endMinutes = endParts[0] * 60 + endParts[1];
32217
+ if (endMinutes < startMinutes) {
32218
+ endMinutes += 24 * 60;
32219
+ }
32220
+ newBreaks[index].duration = endMinutes - startMinutes;
32221
+ }
31859
32222
  return {
31860
32223
  ...typedConfig,
31861
32224
  dayShift: {
@@ -31874,14 +32237,16 @@ var ShiftsView = ({
31874
32237
  const nightShift = { ...typedConfig.nightShift };
31875
32238
  const newBreaks = [...nightShift.breaks];
31876
32239
  newBreaks[index] = { ...newBreaks[index], [field]: value };
31877
- const startParts = newBreaks[index].startTime.split(":").map(Number);
31878
- const endParts = newBreaks[index].endTime.split(":").map(Number);
31879
- let startMinutes = startParts[0] * 60 + startParts[1];
31880
- let endMinutes = endParts[0] * 60 + endParts[1];
31881
- if (endMinutes < startMinutes) {
31882
- endMinutes += 24 * 60;
31883
- }
31884
- newBreaks[index].duration = endMinutes - startMinutes;
32240
+ if (field === "startTime" || field === "endTime") {
32241
+ const startParts = newBreaks[index].startTime.split(":").map(Number);
32242
+ const endParts = newBreaks[index].endTime.split(":").map(Number);
32243
+ let startMinutes = startParts[0] * 60 + startParts[1];
32244
+ let endMinutes = endParts[0] * 60 + endParts[1];
32245
+ if (endMinutes < startMinutes) {
32246
+ endMinutes += 24 * 60;
32247
+ }
32248
+ newBreaks[index].duration = endMinutes - startMinutes;
32249
+ }
31885
32250
  return {
31886
32251
  ...typedConfig,
31887
32252
  nightShift: {