@cryptiklemur/lattice 1.13.0 → 1.14.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.
Files changed (52) hide show
  1. package/client/src/components/analytics/ChartCard.tsx +3 -0
  2. package/client/src/components/analytics/QuickStats.tsx +5 -3
  3. package/client/src/components/analytics/chartTokens.ts +182 -0
  4. package/client/src/components/analytics/charts/ActivityCalendar.tsx +3 -1
  5. package/client/src/components/analytics/charts/CacheEfficiencyChart.tsx +8 -14
  6. package/client/src/components/analytics/charts/ContextUtilizationChart.tsx +6 -20
  7. package/client/src/components/analytics/charts/CostAreaChart.tsx +17 -23
  8. package/client/src/components/analytics/charts/CostDistributionChart.tsx +8 -14
  9. package/client/src/components/analytics/charts/CostDonutChart.tsx +5 -17
  10. package/client/src/components/analytics/charts/CumulativeCostChart.tsx +8 -14
  11. package/client/src/components/analytics/charts/DailySummaryCards.tsx +9 -7
  12. package/client/src/components/analytics/charts/HourlyHeatmap.tsx +3 -2
  13. package/client/src/components/analytics/charts/PermissionBreakdown.tsx +5 -8
  14. package/client/src/components/analytics/charts/ProjectRadar.tsx +6 -6
  15. package/client/src/components/analytics/charts/ResponseTimeScatter.tsx +7 -20
  16. package/client/src/components/analytics/charts/SessionBubbleChart.tsx +7 -24
  17. package/client/src/components/analytics/charts/SessionComplexityList.tsx +2 -7
  18. package/client/src/components/analytics/charts/SessionTimeline.tsx +3 -11
  19. package/client/src/components/analytics/charts/TokenFlowChart.tsx +14 -20
  20. package/client/src/components/analytics/charts/TokenSankeyChart.tsx +16 -14
  21. package/client/src/components/analytics/charts/ToolSunburst.tsx +3 -9
  22. package/client/src/components/analytics/charts/ToolTreemap.tsx +6 -6
  23. package/client/src/components/chat/ChatInput.tsx +44 -11
  24. package/client/src/components/chat/ChatView.tsx +58 -37
  25. package/client/src/components/chat/Message.tsx +170 -17
  26. package/client/src/components/chat/ToolGroup.tsx +1 -1
  27. package/client/src/components/dashboard/DashboardView.tsx +30 -6
  28. package/client/src/components/project-settings/ProjectMemory.tsx +18 -2
  29. package/client/src/components/project-settings/ProjectSettingsView.tsx +2 -2
  30. package/client/src/components/settings/SettingsView.tsx +2 -2
  31. package/client/src/components/settings/skill-shared.tsx +10 -2
  32. package/client/src/components/sidebar/AddProjectModal.tsx +10 -1
  33. package/client/src/components/sidebar/NodeSettingsModal.tsx +10 -1
  34. package/client/src/components/sidebar/ProjectRail.tsx +9 -1
  35. package/client/src/components/sidebar/SessionList.tsx +205 -20
  36. package/client/src/components/sidebar/Sidebar.tsx +2 -2
  37. package/client/src/components/sidebar/UserMenu.tsx +5 -1
  38. package/client/src/components/ui/IconPicker.tsx +2 -2
  39. package/client/src/components/ui/PopupMenu.tsx +25 -5
  40. package/client/src/components/ui/Toast.tsx +1 -1
  41. package/client/src/components/workspace/TaskEditModal.tsx +16 -6
  42. package/client/src/hooks/useSession.ts +1 -0
  43. package/client/src/hooks/useSwipeDrawer.ts +28 -4
  44. package/client/src/hooks/useWebSocket.ts +3 -0
  45. package/client/src/stores/session.ts +10 -0
  46. package/client/src/styles/global.css +62 -2
  47. package/client/src/utils/formatSessionTitle.ts +17 -0
  48. package/package.json +1 -1
  49. package/server/src/handlers/session.ts +19 -1
  50. package/server/src/project/session.ts +83 -1
  51. package/shared/src/messages.ts +21 -2
  52. package/shared/src/models.ts +9 -0
@@ -8,13 +8,12 @@ import {
8
8
  ResponsiveContainer,
9
9
  Legend,
10
10
  } from "recharts";
11
+ import { getChartColors } from "../chartTokens";
11
12
 
12
13
  interface ProjectRadarProps {
13
14
  data: Array<{ project: string; cost: number; sessions: number; avgDuration: number; toolDiversity: number; tokensPerSession: number }>;
14
15
  }
15
16
 
16
- var PROJECT_COLORS = ["#a855f7", "#22c55e", "#f59e0b", "#ef4444", "oklch(55% 0.25 280)"];
17
-
18
17
  var AXIS_KEYS = ["cost", "sessions", "avgDuration", "toolDiversity", "tokensPerSession"] as const;
19
18
  var AXIS_LABELS: Record<string, string> = {
20
19
  cost: "Cost",
@@ -50,6 +49,7 @@ function CustomTooltip({ active, payload, label }: { active?: boolean; payload?:
50
49
  }
51
50
 
52
51
  export function ProjectRadar({ data }: ProjectRadarProps) {
52
+ var colors = getChartColors();
53
53
  if (!data || data.length === 0) {
54
54
  return (
55
55
  <div className="flex items-center justify-center h-[250px] text-base-content/25 font-mono text-[11px]">
@@ -87,10 +87,10 @@ export function ProjectRadar({ data }: ProjectRadarProps) {
87
87
  return (
88
88
  <ResponsiveContainer width="100%" height={280}>
89
89
  <RadarChart data={radarData} margin={{ top: 10, right: 30, bottom: 10, left: 30 }}>
90
- <PolarGrid stroke="oklch(0.9 0.02 280 / 0.08)" />
90
+ <PolarGrid stroke={colors.gridStroke} />
91
91
  <PolarAngleAxis
92
92
  dataKey="axis"
93
- tick={{ fontSize: 9, fontFamily: "var(--font-mono)", fill: "oklch(0.9 0.02 280 / 0.4)" }}
93
+ tick={{ fontSize: 9, fontFamily: "var(--font-mono)", fill: colors.tickFill }}
94
94
  />
95
95
  <PolarRadiusAxis
96
96
  angle={90}
@@ -104,8 +104,8 @@ export function ProjectRadar({ data }: ProjectRadarProps) {
104
104
  key={project.project}
105
105
  name={project.project}
106
106
  dataKey={project.project}
107
- stroke={PROJECT_COLORS[index % PROJECT_COLORS.length]}
108
- fill={PROJECT_COLORS[index % PROJECT_COLORS.length]}
107
+ stroke={colors.palette[index % colors.palette.length]}
108
+ fill={colors.palette[index % colors.palette.length]}
109
109
  fillOpacity={0.1}
110
110
  strokeWidth={1.5}
111
111
  />
@@ -8,21 +8,7 @@ import {
8
8
  ResponsiveContainer,
9
9
  } from "recharts";
10
10
  import { useChartFullscreen } from "../ChartCard";
11
-
12
- var TICK_STYLE = {
13
- fontSize: 10,
14
- fontFamily: "var(--font-mono)",
15
- fill: "oklch(0.9 0.02 280 / 0.3)",
16
- };
17
-
18
- var GRID_COLOR = "oklch(0.9 0.02 280 / 0.06)";
19
-
20
- var MODEL_COLORS: Record<string, string> = {
21
- opus: "#a855f7",
22
- sonnet: "oklch(55% 0.25 280)",
23
- haiku: "#22c55e",
24
- other: "#f59e0b",
25
- };
11
+ import { getChartColors, getTickStyle, getModelColor } from "../chartTokens";
26
12
 
27
13
  interface ResponseTimeDatum {
28
14
  tokens: number;
@@ -51,12 +37,13 @@ function CustomTooltip({ active, payload }: { active?: boolean; payload?: Array<
51
37
 
52
38
  export function ResponseTimeScatter({ data }: ResponseTimeScatterProps) {
53
39
  var fullscreenHeight = useChartFullscreen();
40
+ var colors = getChartColors();
54
41
  var models = Array.from(new Set(data.map(function (d) { return d.model; })));
55
42
 
56
43
  var byModel = models.map(function (model) {
57
44
  return {
58
45
  model: model,
59
- color: MODEL_COLORS[model] || "#f59e0b",
46
+ color: getModelColor(model),
60
47
  points: data
61
48
  .filter(function (d) { return d.model === model; })
62
49
  .map(function (d) { return { tokens: d.tokens, durationSec: d.duration / 1000, model: d.model }; }),
@@ -66,11 +53,11 @@ export function ResponseTimeScatter({ data }: ResponseTimeScatterProps) {
66
53
  return (
67
54
  <ResponsiveContainer width="100%" height={fullscreenHeight || 200}>
68
55
  <ScatterChart margin={{ top: 4, right: 4, left: -20, bottom: 0 }}>
69
- <CartesianGrid strokeDasharray="3 3" stroke={GRID_COLOR} />
56
+ <CartesianGrid strokeDasharray="3 3" stroke={colors.gridStroke} />
70
57
  <XAxis
71
58
  dataKey="tokens"
72
59
  type="number"
73
- tick={TICK_STYLE}
60
+ tick={getTickStyle()}
74
61
  axisLine={false}
75
62
  tickLine={false}
76
63
  tickFormatter={function (v) { return v >= 1000 ? (v / 1000).toFixed(0) + "k" : String(v); }}
@@ -79,13 +66,13 @@ export function ResponseTimeScatter({ data }: ResponseTimeScatterProps) {
79
66
  <YAxis
80
67
  dataKey="durationSec"
81
68
  type="number"
82
- tick={TICK_STYLE}
69
+ tick={getTickStyle()}
83
70
  axisLine={false}
84
71
  tickLine={false}
85
72
  tickFormatter={function (v) { return v.toFixed(0) + "s"; }}
86
73
  name="duration"
87
74
  />
88
- <Tooltip content={<CustomTooltip />} cursor={{ strokeDasharray: "3 3", stroke: GRID_COLOR }} />
75
+ <Tooltip content={<CustomTooltip />} cursor={{ strokeDasharray: "3 3", stroke: colors.gridStroke }} />
89
76
  {byModel.map(function (group) {
90
77
  return (
91
78
  <Scatter
@@ -9,25 +9,7 @@ import {
9
9
  ZAxis,
10
10
  } from "recharts";
11
11
  import { useChartFullscreen } from "../ChartCard";
12
-
13
- var TICK_STYLE = {
14
- fontSize: 10,
15
- fontFamily: "var(--font-mono)",
16
- fill: "oklch(0.9 0.02 280 / 0.3)",
17
- };
18
-
19
- var GRID_COLOR = "oklch(0.9 0.02 280 / 0.06)";
20
-
21
- var PROJECT_PALETTE = [
22
- "oklch(55% 0.25 280)",
23
- "#a855f7",
24
- "#22c55e",
25
- "#f59e0b",
26
- "oklch(65% 0.2 240)",
27
- "oklch(65% 0.25 25)",
28
- "oklch(65% 0.25 150)",
29
- "oklch(70% 0.2 60)",
30
- ];
12
+ import { getChartColors, getTickStyle } from "../chartTokens";
31
13
 
32
14
  interface SessionBubbleDatum {
33
15
  id: string;
@@ -64,11 +46,12 @@ function CustomTooltip({ active, payload }: { active?: boolean; payload?: Array<
64
46
 
65
47
  export function SessionBubbleChart({ data }: SessionBubbleChartProps) {
66
48
  var fullscreenHeight = useChartFullscreen();
49
+ var colors = getChartColors();
67
50
  var projects = Array.from(new Set(data.map(function (d) { return d.project; })));
68
51
 
69
52
  function getColor(project: string): string {
70
53
  var idx = projects.indexOf(project);
71
- return PROJECT_PALETTE[idx % PROJECT_PALETTE.length];
54
+ return colors.palette[idx % colors.palette.length];
72
55
  }
73
56
 
74
57
  var byProject = projects.map(function (project) {
@@ -87,12 +70,12 @@ export function SessionBubbleChart({ data }: SessionBubbleChartProps) {
87
70
  return (
88
71
  <ResponsiveContainer width="100%" height={fullscreenHeight || 200}>
89
72
  <ScatterChart margin={{ top: 4, right: 4, left: -20, bottom: 0 }}>
90
- <CartesianGrid strokeDasharray="3 3" stroke={GRID_COLOR} />
73
+ <CartesianGrid strokeDasharray="3 3" stroke={colors.gridStroke} />
91
74
  <XAxis
92
75
  dataKey="x"
93
76
  type="number"
94
77
  domain={[minTs, maxTs]}
95
- tick={TICK_STYLE}
78
+ tick={getTickStyle()}
96
79
  axisLine={false}
97
80
  tickLine={false}
98
81
  tickFormatter={function (v) { return formatDate(v); }}
@@ -100,13 +83,13 @@ export function SessionBubbleChart({ data }: SessionBubbleChartProps) {
100
83
  <YAxis
101
84
  dataKey="y"
102
85
  type="number"
103
- tick={TICK_STYLE}
86
+ tick={getTickStyle()}
104
87
  axisLine={false}
105
88
  tickLine={false}
106
89
  tickFormatter={function (v) { return v >= 1000 ? (v / 1000).toFixed(0) + "k" : String(v); }}
107
90
  />
108
91
  <ZAxis dataKey="z" range={[20, 300]} />
109
- <Tooltip content={<CustomTooltip />} cursor={{ strokeDasharray: "3 3", stroke: GRID_COLOR }} />
92
+ <Tooltip content={<CustomTooltip />} cursor={{ strokeDasharray: "3 3", stroke: colors.gridStroke }} />
110
93
  {byProject.map(function (group) {
111
94
  return (
112
95
  <Scatter
@@ -1,14 +1,9 @@
1
+ import { getScoreColor } from "../chartTokens";
2
+
1
3
  interface SessionComplexityListProps {
2
4
  data: Array<{ id: string; title: string; score: number; messages: number; tools: number; contextPercent: number }>;
3
5
  }
4
6
 
5
- function getScoreColor(score: number, maxScore: number): string {
6
- var intensity = maxScore > 0 ? Math.min(score / maxScore, 1) : 0;
7
- var lightness = 0.45 - intensity * 0.1;
8
- var chroma = 0.1 + intensity * 0.18;
9
- return "oklch(" + lightness + " " + chroma + " 280)";
10
- }
11
-
12
7
  export function SessionComplexityList({ data }: SessionComplexityListProps) {
13
8
  if (!data || data.length === 0) {
14
9
  return (
@@ -1,4 +1,5 @@
1
1
  import { useState } from "react";
2
+ import { getChartColors } from "../chartTokens";
2
3
 
3
4
  interface TimelineDatum {
4
5
  id: string;
@@ -19,16 +20,6 @@ var ROW_STEP = ROW_HEIGHT + ROW_GAP;
19
20
  var LEFT_MARGIN = 8;
20
21
  var RIGHT_MARGIN = 8;
21
22
 
22
- var PROJECT_PALETTE = [
23
- "oklch(55% 0.25 280)",
24
- "#a855f7",
25
- "#22c55e",
26
- "#f59e0b",
27
- "oklch(65% 0.2 240)",
28
- "oklch(65% 0.25 25)",
29
- "oklch(65% 0.25 150)",
30
- "oklch(70% 0.2 60)",
31
- ];
32
23
 
33
24
  function formatTime(ts: number): string {
34
25
  var d = new Date(ts);
@@ -37,6 +28,7 @@ function formatTime(ts: number): string {
37
28
 
38
29
  export function SessionTimeline({ data }: SessionTimelineProps) {
39
30
  var [hover, setHover] = useState<{ x: number; y: number; datum: TimelineDatum } | null>(null);
31
+ var colors = getChartColors();
40
32
 
41
33
  if (!data || data.length === 0) {
42
34
  return (
@@ -49,7 +41,7 @@ export function SessionTimeline({ data }: SessionTimelineProps) {
49
41
  var projects = Array.from(new Set(data.map(function (d) { return d.project; })));
50
42
  function getColor(project: string): string {
51
43
  var idx = projects.indexOf(project);
52
- return PROJECT_PALETTE[idx % PROJECT_PALETTE.length];
44
+ return colors.palette[idx % colors.palette.length];
53
45
  }
54
46
 
55
47
  var minTime = Infinity;
@@ -8,14 +8,7 @@ import {
8
8
  ResponsiveContainer,
9
9
  } from "recharts";
10
10
  import { useChartFullscreen } from "../ChartCard";
11
-
12
- var TICK_STYLE = {
13
- fontSize: 10,
14
- fontFamily: "var(--font-mono)",
15
- fill: "oklch(0.9 0.02 280 / 0.3)",
16
- };
17
-
18
- var GRID_COLOR = "oklch(0.9 0.02 280 / 0.06)";
11
+ import { getChartColors, getTickStyle } from "../chartTokens";
19
12
 
20
13
  interface TokenFlowDatum {
21
14
  date: string;
@@ -54,30 +47,31 @@ function formatTokens(v: number): string {
54
47
 
55
48
  export function TokenFlowChart({ data }: TokenFlowChartProps) {
56
49
  var fullscreenHeight = useChartFullscreen();
50
+ var colors = getChartColors();
57
51
  return (
58
52
  <ResponsiveContainer width="100%" height={fullscreenHeight || 200}>
59
53
  <AreaChart data={data} margin={{ top: 4, right: 4, left: -20, bottom: 0 }}>
60
54
  <defs>
61
55
  <linearGradient id="inputGrad" x1="0" y1="0" x2="0" y2="1">
62
- <stop offset="5%" stopColor="oklch(55% 0.25 280)" stopOpacity={0.4} />
63
- <stop offset="95%" stopColor="oklch(55% 0.25 280)" stopOpacity={0.05} />
56
+ <stop offset="5%" stopColor={colors.primary} stopOpacity={0.4} />
57
+ <stop offset="95%" stopColor={colors.primary} stopOpacity={0.05} />
64
58
  </linearGradient>
65
59
  <linearGradient id="outputGrad" x1="0" y1="0" x2="0" y2="1">
66
- <stop offset="5%" stopColor="#22c55e" stopOpacity={0.4} />
67
- <stop offset="95%" stopColor="#22c55e" stopOpacity={0.05} />
60
+ <stop offset="5%" stopColor={colors.success} stopOpacity={0.4} />
61
+ <stop offset="95%" stopColor={colors.success} stopOpacity={0.05} />
68
62
  </linearGradient>
69
63
  <linearGradient id="cacheReadGrad" x1="0" y1="0" x2="0" y2="1">
70
- <stop offset="5%" stopColor="#f59e0b" stopOpacity={0.4} />
71
- <stop offset="95%" stopColor="#f59e0b" stopOpacity={0.05} />
64
+ <stop offset="5%" stopColor={colors.warning} stopOpacity={0.4} />
65
+ <stop offset="95%" stopColor={colors.warning} stopOpacity={0.05} />
72
66
  </linearGradient>
73
67
  </defs>
74
- <CartesianGrid strokeDasharray="3 3" stroke={GRID_COLOR} vertical={false} />
75
- <XAxis dataKey="date" tick={TICK_STYLE} axisLine={false} tickLine={false} />
76
- <YAxis tick={TICK_STYLE} axisLine={false} tickLine={false} tickFormatter={formatTokens} />
68
+ <CartesianGrid strokeDasharray="3 3" stroke={colors.gridStroke} vertical={false} />
69
+ <XAxis dataKey="date" tick={getTickStyle()} axisLine={false} tickLine={false} />
70
+ <YAxis tick={getTickStyle()} axisLine={false} tickLine={false} tickFormatter={formatTokens} />
77
71
  <Tooltip content={<CustomTooltip />} />
78
- <Area type="monotone" dataKey="input" stackId="1" stroke="oklch(55% 0.25 280)" fill="url(#inputGrad)" strokeWidth={1.5} />
79
- <Area type="monotone" dataKey="output" stackId="1" stroke="#22c55e" fill="url(#outputGrad)" strokeWidth={1.5} />
80
- <Area type="monotone" dataKey="cacheRead" stackId="1" stroke="#f59e0b" fill="url(#cacheReadGrad)" strokeWidth={1.5} />
72
+ <Area type="monotone" dataKey="input" stackId="1" stroke={colors.primary} fill="url(#inputGrad)" strokeWidth={1.5} />
73
+ <Area type="monotone" dataKey="output" stackId="1" stroke={colors.success} fill="url(#outputGrad)" strokeWidth={1.5} />
74
+ <Area type="monotone" dataKey="cacheRead" stackId="1" stroke={colors.warning} fill="url(#cacheReadGrad)" strokeWidth={1.5} />
81
75
  </AreaChart>
82
76
  </ResponsiveContainer>
83
77
  );
@@ -1,16 +1,6 @@
1
1
  import { Sankey, Tooltip, ResponsiveContainer } from "recharts";
2
2
  import { useChartFullscreen } from "../ChartCard";
3
-
4
- var NODE_COLORS: Record<string, string> = {
5
- "Input Tokens": "oklch(55% 0.25 280)",
6
- "Cache Read": "#f59e0b",
7
- "Cache Creation": "oklch(65% 0.2 240)",
8
- "Opus": "#a855f7",
9
- "Sonnet": "oklch(55% 0.25 280)",
10
- "Haiku": "#22c55e",
11
- "Other": "#f59e0b",
12
- "Output Tokens": "#22c55e",
13
- };
3
+ import { getChartColors } from "../chartTokens";
14
4
 
15
5
  interface SankeyData {
16
6
  nodes: Array<{ name: string }>;
@@ -44,7 +34,18 @@ function CustomTooltip({ active, payload }: { active?: boolean; payload?: Array<
44
34
  }
45
35
 
46
36
  function SankeyNode({ x, y, width, height, index, payload }: { x: number; y: number; width: number; height: number; index: number; payload: { name: string } }) {
47
- var color = NODE_COLORS[payload.name] || "oklch(0.5 0.1 280)";
37
+ var colors = getChartColors();
38
+ var NODE_COLORS: Record<string, string> = {
39
+ "Input Tokens": colors.primary,
40
+ "Cache Read": colors.warning,
41
+ "Cache Creation": colors.accent,
42
+ "Opus": colors.secondary,
43
+ "Sonnet": colors.primary,
44
+ "Haiku": colors.success,
45
+ "Other": colors.warning,
46
+ "Output Tokens": colors.success,
47
+ };
48
+ var color = NODE_COLORS[payload.name] || colors.primary;
48
49
  return (
49
50
  <g>
50
51
  <rect x={x} y={y} width={width} height={height} fill={color} fillOpacity={0.85} rx={2} />
@@ -53,7 +54,7 @@ function SankeyNode({ x, y, width, height, index, payload }: { x: number; y: num
53
54
  x={x + width + 6}
54
55
  y={y + height / 2}
55
56
  dy={4}
56
- fill="oklch(0.9 0.02 280 / 0.5)"
57
+ fill={colors.tickFill}
57
58
  fontSize={9}
58
59
  fontFamily="var(--font-mono)"
59
60
  >
@@ -66,6 +67,7 @@ function SankeyNode({ x, y, width, height, index, payload }: { x: number; y: num
66
67
 
67
68
  export function TokenSankeyChart({ data }: TokenSankeyChartProps) {
68
69
  var fullscreenHeight = useChartFullscreen();
70
+ var colors = getChartColors();
69
71
  if (!data.links || data.links.length === 0) {
70
72
  return (
71
73
  <div className="flex items-center justify-center h-[250px] text-base-content/30 font-mono text-[12px]">
@@ -79,7 +81,7 @@ export function TokenSankeyChart({ data }: TokenSankeyChartProps) {
79
81
  <Sankey
80
82
  data={data}
81
83
  node={<SankeyNode x={0} y={0} width={0} height={0} index={0} payload={{ name: "" }} />}
82
- link={{ stroke: "oklch(0.9 0.02 280 / 0.1)" }}
84
+ link={{ stroke: colors.gridStroke }}
83
85
  margin={{ top: 10, right: 100, left: 10, bottom: 10 }}
84
86
  nodeWidth={12}
85
87
  nodePadding={14}
@@ -1,19 +1,13 @@
1
1
  import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from "recharts";
2
+ import { getChartColors } from "../chartTokens";
2
3
 
3
4
  interface ToolSunburstProps {
4
5
  data: Array<{ name: string; category: string; count: number }>;
5
6
  }
6
7
 
7
- var CATEGORY_COLORS: Record<string, string> = {
8
- Read: "#22c55e",
9
- Write: "#f59e0b",
10
- Execute: "#ef4444",
11
- AI: "#a855f7",
12
- Other: "oklch(55% 0.15 280)",
13
- };
14
-
15
8
  function getCategoryColor(category: string): string {
16
- return CATEGORY_COLORS[category] || CATEGORY_COLORS.Other;
9
+ var colors = getChartColors();
10
+ return colors.category[category] || colors.category.Other;
17
11
  }
18
12
 
19
13
  function getToolColor(category: string, index: number): string {
@@ -1,5 +1,6 @@
1
1
  import { Treemap, ResponsiveContainer, Tooltip } from "recharts";
2
2
  import { useChartFullscreen } from "../ChartCard";
3
+ import { getChartColors, getIntensityColor } from "../chartTokens";
3
4
 
4
5
  interface ToolTreemapProps {
5
6
  data: Array<{ name: string; count: number; avgCost: number }>;
@@ -9,9 +10,7 @@ var MAX_COST_INTENSITY = 0.5;
9
10
 
10
11
  function getColor(avgCost: number, maxCost: number): string {
11
12
  var intensity = maxCost > 0 ? Math.min(avgCost / maxCost, 1) : 0.3;
12
- var lightness = 0.45 - intensity * 0.15;
13
- var chroma = 0.15 + intensity * 0.12;
14
- return "oklch(" + lightness + " " + chroma + " 280)";
13
+ return getIntensityColor(intensity);
15
14
  }
16
15
 
17
16
  function CustomContent(props: {
@@ -21,6 +20,7 @@ function CustomContent(props: {
21
20
  }) {
22
21
  var { x = 0, y = 0, width = 0, height = 0, name = "", count = 0, avgCost = 0, maxCost = MAX_COST_INTENSITY } = props;
23
22
  if (width < 4 || height < 4) return null;
23
+ var colors = getChartColors();
24
24
  var showLabel = width > 40 && height > 20;
25
25
  var showCount = width > 50 && height > 34;
26
26
  return (
@@ -33,7 +33,7 @@ function CustomContent(props: {
33
33
  rx={3}
34
34
  fill={getColor(avgCost, maxCost)}
35
35
  fillOpacity={0.85}
36
- stroke="oklch(0.2 0.02 280)"
36
+ stroke={colors.gridStroke}
37
37
  strokeWidth={1}
38
38
  />
39
39
  {showLabel && (
@@ -42,7 +42,7 @@ function CustomContent(props: {
42
42
  y={y + height / 2 + (showCount ? -5 : 0)}
43
43
  textAnchor="middle"
44
44
  dominantBaseline="middle"
45
- style={{ fontSize: 10, fontFamily: "var(--font-mono)", fill: "oklch(0.95 0.02 280 / 0.9)" }}
45
+ style={{ fontSize: 10, fontFamily: "var(--font-mono)", fill: colors.tickFill }}
46
46
  >
47
47
  {name}
48
48
  </text>
@@ -53,7 +53,7 @@ function CustomContent(props: {
53
53
  y={y + height / 2 + 9}
54
54
  textAnchor="middle"
55
55
  dominantBaseline="middle"
56
- style={{ fontSize: 9, fontFamily: "var(--font-mono)", fill: "oklch(0.95 0.02 280 / 0.5)" }}
56
+ style={{ fontSize: 9, fontFamily: "var(--font-mono)", fill: colors.tickFill }}
57
57
  >
58
58
  {count}
59
59
  </text>
@@ -1,4 +1,5 @@
1
1
  import { useRef, useState, useEffect, useMemo } from "react";
2
+ import { useStore } from "@tanstack/react-store";
2
3
  import { SendHorizontal, Settings, Paperclip } from "lucide-react";
3
4
  import { useSkills } from "../../hooks/useSkills";
4
5
  import { CommandPalette, getFilteredItems } from "./CommandPalette";
@@ -17,6 +18,7 @@ interface ChatInputProps {
17
18
  onFailedInputConsumed?: () => void;
18
19
  prefillText?: string | null;
19
20
  onPrefillConsumed?: () => void;
21
+ sessionId?: string | null;
20
22
  }
21
23
 
22
24
  function getModKey(): string {
@@ -26,9 +28,37 @@ function getModKey(): string {
26
28
  return "Ctrl";
27
29
  }
28
30
 
29
- var inputHistory: string[] = [];
31
+ var historyBySession = new Map<string, string[]>();
30
32
  var MAX_HISTORY = 100;
31
33
 
34
+ function extractTypedInput(text: string): string | null {
35
+ var firstNewline = text.search(/\r?\n/);
36
+ var firstLine = firstNewline !== -1 ? text.slice(0, firstNewline).trim() : text;
37
+ if (firstLine.indexOf(":") !== -1 && /\n---[\r\n]/.test(text)) {
38
+ return "/" + firstLine;
39
+ }
40
+ if (text.startsWith("<skill-name>")) {
41
+ var endTag = text.indexOf("</skill-name>");
42
+ if (endTag !== -1) {
43
+ return "/" + text.slice(12, endTag);
44
+ }
45
+ return null;
46
+ }
47
+ if (text.startsWith("<skill-content>")) return null;
48
+ if (text.length > 500) return null;
49
+ return text;
50
+ }
51
+
52
+ function getHistory(sessionId: string | null | undefined): string[] {
53
+ if (!sessionId) return [];
54
+ var hist = historyBySession.get(sessionId);
55
+ if (!hist) {
56
+ hist = [];
57
+ historyBySession.set(sessionId, hist);
58
+ }
59
+ return hist;
60
+ }
61
+
32
62
  export function ChatInput(props: ChatInputProps) {
33
63
  var textareaRef = useRef<HTMLTextAreaElement>(null);
34
64
  var popupRef = useRef<HTMLDivElement>(null);
@@ -41,26 +71,29 @@ export function ChatInput(props: ChatInputProps) {
41
71
  var modKey = useMemo(getModKey, []);
42
72
  var [historyIndex, setHistoryIndex] = useState(-1);
43
73
  var savedCurrentRef = useRef("");
74
+ var inputHistory = getHistory(props.sessionId);
75
+ var historyLoading = useStore(getSessionStore(), function (s) { return s.historyLoading; });
76
+ var messages = useStore(getSessionStore(), function (s) { return s.messages; });
77
+
78
+ useEffect(function () {
79
+ setHistoryIndex(-1);
80
+ savedCurrentRef.current = "";
81
+ }, [props.sessionId]);
44
82
 
45
83
  useEffect(function () {
46
- var store = getSessionStore();
47
- var messages = store.state.messages;
48
- var seen = new Set<string>();
84
+ if (!props.sessionId || historyLoading) return;
49
85
  for (var i = 0; i < messages.length; i++) {
50
86
  if (messages[i].type === "user" && messages[i].text) {
51
- var text = messages[i].text!.trim();
52
- if (text && !seen.has(text)) {
53
- seen.add(text);
54
- if (inputHistory.indexOf(text) === -1) {
55
- inputHistory.push(text);
56
- }
87
+ var typed = extractTypedInput(messages[i].text!.trim());
88
+ if (typed && inputHistory.indexOf(typed) === -1) {
89
+ inputHistory.push(typed);
57
90
  }
58
91
  }
59
92
  }
60
93
  if (inputHistory.length > MAX_HISTORY) {
61
94
  inputHistory.splice(0, inputHistory.length - MAX_HISTORY);
62
95
  }
63
- }, []);
96
+ }, [props.sessionId, historyLoading]);
64
97
 
65
98
  var attachmentsHook = useAttachments();
66
99
  var voice = useVoiceRecorder();