@data-club/ai-hub 0.0.11 → 0.0.13

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/dist/auth/locales/cs.d.ts.map +1 -1
  2. package/dist/auth/locales/cs.js +5 -0
  3. package/dist/auth/locales/cs.js.map +1 -1
  4. package/dist/auth/locales/sk.d.ts.map +1 -1
  5. package/dist/auth/locales/sk.js +5 -0
  6. package/dist/auth/locales/sk.js.map +1 -1
  7. package/dist/auth/texts.d.ts +13 -0
  8. package/dist/auth/texts.d.ts.map +1 -1
  9. package/dist/auth/texts.js +5 -0
  10. package/dist/auth/texts.js.map +1 -1
  11. package/dist/components/ChatShell/index.d.ts +6 -0
  12. package/dist/components/ChatShell/index.d.ts.map +1 -1
  13. package/dist/components/ChatShell/index.js +66 -3
  14. package/dist/components/ChatShell/index.js.map +1 -1
  15. package/dist/components/DataClubAIHub/index.d.ts +6 -0
  16. package/dist/components/DataClubAIHub/index.d.ts.map +1 -1
  17. package/dist/components/DataClubAIHub/index.js +6 -0
  18. package/dist/components/DataClubAIHub/index.js.map +1 -1
  19. package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -1
  20. package/dist/components/DataClubAIHubAdmin/SettingsPage.js +20 -5
  21. package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -1
  22. package/dist/components/MainColumn/index.d.ts +8 -0
  23. package/dist/components/MainColumn/index.d.ts.map +1 -1
  24. package/dist/components/MainColumn/index.js +40 -2
  25. package/dist/components/MainColumn/index.js.map +1 -1
  26. package/dist/components/SessionsPanel/index.d.ts +5 -0
  27. package/dist/components/SessionsPanel/index.d.ts.map +1 -1
  28. package/dist/components/SessionsPanel/index.js +8 -3
  29. package/dist/components/SessionsPanel/index.js.map +1 -1
  30. package/dist/state/affordances.d.ts +7 -1
  31. package/dist/state/affordances.d.ts.map +1 -1
  32. package/dist/state/affordances.js +7 -1
  33. package/dist/state/affordances.js.map +1 -1
  34. package/dist/types.d.ts +15 -0
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/util/exportConversation.d.ts +64 -0
  37. package/dist/util/exportConversation.d.ts.map +1 -0
  38. package/dist/util/exportConversation.js +78 -0
  39. package/dist/util/exportConversation.js.map +1 -0
  40. package/package.json +1 -1
  41. package/src/auth/locales/cs.ts +5 -0
  42. package/src/auth/locales/sk.ts +5 -0
  43. package/src/auth/texts.ts +18 -0
  44. package/src/components/ChatShell/index.tsx +67 -0
  45. package/src/components/DataClubAIHub/index.tsx +15 -0
  46. package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +21 -5
  47. package/src/components/MainColumn/index.tsx +85 -1
  48. package/src/components/SessionsPanel/index.tsx +13 -3
  49. package/src/state/affordances.ts +13 -1
  50. package/src/styles/default.css +21 -0
  51. package/src/types.ts +15 -0
  52. package/src/util/exportConversation.ts +101 -0
@@ -47,8 +47,12 @@ export interface AIHubAffordanceFlags {
47
47
  sessionsPanelResizable: boolean;
48
48
  /** Whether the sessions panel can be collapsed. Default true. */
49
49
  sessionsPanelCollapsible: boolean;
50
+ /** Whether the sessions panel starts collapsed on load. Default **false**. */
51
+ sessionsPanelDefaultCollapsed: boolean;
50
52
  /** Whether the "+ New chat" button renders on the panel. Default true. */
51
53
  createSessionButtonEnabled: boolean;
54
+ /** Whether the last session auto-opens on load (created when none). Default **false**. */
55
+ loadLastSessionEnabled: boolean;
52
56
  /** Whether the input area renders (read-only when false). Default true. */
53
57
  userInputEnabled: boolean;
54
58
  /** Whether the stop button is exposed while a turn is running. Default true. */
@@ -67,6 +71,8 @@ export interface AIHubAffordanceFlags {
67
71
  scrollToPresentVisible: boolean;
68
72
  /** Whether the turn-status indicator renders in the chat header. Default true. */
69
73
  turnStatusVisible: boolean;
74
+ /** Whether the export-conversation (.md) button renders in the chat header. Default true. */
75
+ exportConversationEnabled: boolean;
70
76
  }
71
77
 
72
78
  /** Built-in defaults — applied when neither server nor React prop sets a value. */
@@ -74,7 +80,9 @@ export const DEFAULT_AFFORDANCE_FLAGS: AIHubAffordanceFlags = Object.freeze({
74
80
  sessionsPanelEnabled: true,
75
81
  sessionsPanelResizable: true,
76
82
  sessionsPanelCollapsible: true,
83
+ sessionsPanelDefaultCollapsed: false,
77
84
  createSessionButtonEnabled: true,
85
+ loadLastSessionEnabled: false,
78
86
  userInputEnabled: true,
79
87
  stopButtonEnabled: true,
80
88
  toolCallsVisible: true,
@@ -84,6 +92,7 @@ export const DEFAULT_AFFORDANCE_FLAGS: AIHubAffordanceFlags = Object.freeze({
84
92
  effortControlVisible: true,
85
93
  scrollToPresentVisible: true,
86
94
  turnStatusVisible: true,
95
+ exportConversationEnabled: true,
87
96
  }) as AIHubAffordanceFlags;
88
97
 
89
98
  /**
@@ -97,7 +106,9 @@ export const FEATURE_TOGGLE_KEY_MAP: ReadonlyArray<
97
106
  ["sessions_panel_enabled", "sessionsPanelEnabled"],
98
107
  ["sessions_panel_resizable", "sessionsPanelResizable"],
99
108
  ["sessions_panel_collapsible", "sessionsPanelCollapsible"],
109
+ ["sessions_panel_default_collapsed", "sessionsPanelDefaultCollapsed"],
100
110
  ["create_session_button_enabled", "createSessionButtonEnabled"],
111
+ ["load_last_session_enabled", "loadLastSessionEnabled"],
101
112
  ["user_input_enabled", "userInputEnabled"],
102
113
  ["stop_button_enabled", "stopButtonEnabled"],
103
114
  ["tool_calls_visible", "toolCallsVisible"],
@@ -107,6 +118,7 @@ export const FEATURE_TOGGLE_KEY_MAP: ReadonlyArray<
107
118
  ["effort_control_visible", "effortControlVisible"],
108
119
  ["scroll_to_present_visible", "scrollToPresentVisible"],
109
120
  ["turn_status_visible", "turnStatusVisible"],
121
+ ["export_conversation_enabled", "exportConversationEnabled"],
110
122
  ]);
111
123
 
112
124
  /**
@@ -134,7 +146,7 @@ function resolveOne(
134
146
  }
135
147
 
136
148
  /**
137
- * Composes the three layers into a flat resolved record of all 13
149
+ * Composes the three layers into a flat resolved record of all 16
138
150
  * affordance booleans.
139
151
  *
140
152
  * Per §8.4: server toggle wins; falling through, the React prop wins;
@@ -1492,6 +1492,27 @@
1492
1492
  white-space: nowrap;
1493
1493
  }
1494
1494
 
1495
+ .data-club-ai-hub__chat-header__export {
1496
+ padding: var(--dc-aih-space-xs) var(--dc-aih-space-sm);
1497
+ font: inherit;
1498
+ font-size: var(--dc-aih-font-size-sm);
1499
+ color: var(--dc-aih-fg-muted);
1500
+ background: transparent;
1501
+ border: 1px solid var(--dc-aih-border);
1502
+ border-radius: var(--dc-aih-radius-sm);
1503
+ white-space: nowrap;
1504
+ }
1505
+
1506
+ .data-club-ai-hub__chat-header__export:hover:not(:disabled) {
1507
+ color: var(--dc-aih-fg);
1508
+ background-color: color-mix(in srgb, var(--dc-aih-fg) 6%, transparent);
1509
+ }
1510
+
1511
+ .data-club-ai-hub__chat-header__export:disabled {
1512
+ opacity: 0.5;
1513
+ cursor: not-allowed;
1514
+ }
1515
+
1495
1516
  .data-club-ai-hub__chat-header__extras {
1496
1517
  display: flex;
1497
1518
  align-items: center;
package/src/types.ts CHANGED
@@ -696,8 +696,21 @@ export interface AIHubFeatureToggles {
696
696
  sessions_panel_resizable?: boolean;
697
697
  /** Overrides `sessionsPanelCollapsible` prop. */
698
698
  sessions_panel_collapsible?: boolean;
699
+ /**
700
+ * Overrides `sessionsPanelDefaultCollapsed` prop. When true the sessions
701
+ * panel starts collapsed on page load (until the user toggles it).
702
+ * Default **false** (panel starts extended).
703
+ */
704
+ sessions_panel_default_collapsed?: boolean;
699
705
  /** Overrides `showCreateSessionButton` prop. */
700
706
  create_session_button_enabled?: boolean;
707
+ /**
708
+ * Overrides `loadLastSession` prop. When true the most recently updated
709
+ * session opens automatically on page load (a brand-new session is
710
+ * created when the caller has none) instead of the "pick a chat" hint.
711
+ * Default **false**.
712
+ */
713
+ load_last_session_enabled?: boolean;
701
714
  /** Overrides `userInput` prop. */
702
715
  user_input_enabled?: boolean;
703
716
  /** Overrides `showStopButton` prop. */
@@ -716,6 +729,8 @@ export interface AIHubFeatureToggles {
716
729
  scroll_to_present_visible?: boolean;
717
730
  /** Overrides `showTurnStatus` prop. */
718
731
  turn_status_visible?: boolean;
732
+ /** Overrides `showExportButton` prop. */
733
+ export_conversation_enabled?: boolean;
719
734
  }
720
735
 
721
736
  /* ────────────────────────────────────────────────────────────────────────── */
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Conversation → Markdown export helpers.
3
+ *
4
+ * Pure functions — the `<MainColumn />` export button calls
5
+ * `buildConversationMarkdown` on the already-loaded `useAIHub().messages`
6
+ * array and hands the result to a Blob download. Keeping the serialization
7
+ * here (instead of inline in the component) makes the export rules
8
+ * unit-testable without a DOM.
9
+ *
10
+ * Export rules:
11
+ *
12
+ * - Only `user` and `assistant` messages are exported. `system` rows (the
13
+ * compiled system prompt travels in the transcript as `role: "system"`)
14
+ * and `tool` rows never appear in the file.
15
+ * - Assistant messages with no text content (tool-call-only / reasoning-only
16
+ * bubbles) are skipped — the export is the human-readable dialog, not the
17
+ * agent trace.
18
+ * - Rotating-message sessions need no special casing: the rotation runner
19
+ * deletes its own prompt's `user_message` from the Letta conversation, so
20
+ * the transcript (and therefore the export) opens with the agent's reply.
21
+ *
22
+ * @see ../components/MainColumn/index.tsx — the export button
23
+ * @see SPECIFICATIONS.md §8.4 — `export_conversation_enabled` toggle
24
+ */
25
+
26
+ import type { AIHubMessage } from "../types.js";
27
+
28
+ /** Inputs for {@link buildConversationMarkdown}. */
29
+ export interface ConversationExportArgs {
30
+ /** The session's user-supplied title; used as the document heading. */
31
+ sessionTitle: string;
32
+ /** Agent display name; rendered as the assistant speaker label. */
33
+ agentDisplayName: string;
34
+ /** Speaker label for `user` messages (localized, e.g. "You"). */
35
+ userLabel: string;
36
+ /** The transcript, chronologically sorted (as served by the provider). */
37
+ messages: ReadonlyArray<AIHubMessage>;
38
+ /** Export timestamp; injected so tests are deterministic. */
39
+ exportedAt: Date;
40
+ }
41
+
42
+ /**
43
+ * Serializes a conversation to a Markdown document.
44
+ *
45
+ * Format: an `# <title>` heading plus an "exported" byline, then one
46
+ * `## <speaker>` section per user/assistant message with the message's
47
+ * (already-markdown) content verbatim underneath.
48
+ *
49
+ * @param args - see {@link ConversationExportArgs}.
50
+ * @returns the Markdown document text (trailing newline included).
51
+ */
52
+ export function buildConversationMarkdown(args: ConversationExportArgs): string {
53
+ const lines: string[] = [];
54
+ lines.push(`# ${args.sessionTitle}`);
55
+ lines.push("");
56
+ lines.push(
57
+ `> ${args.agentDisplayName} — exported ${args.exportedAt.toISOString()}`,
58
+ );
59
+
60
+ for (const msg of args.messages) {
61
+ if (msg.role !== "user" && msg.role !== "assistant") continue;
62
+ const content = msg.content.trim();
63
+ if (content.length === 0) continue;
64
+ const speaker =
65
+ msg.role === "user" ? args.userLabel : args.agentDisplayName;
66
+ lines.push("");
67
+ lines.push(`## ${speaker}`);
68
+ lines.push("");
69
+ lines.push(content);
70
+ }
71
+
72
+ return `${lines.join("\n")}\n`;
73
+ }
74
+
75
+ /**
76
+ * Derives a safe `.md` filename from the session title.
77
+ *
78
+ * Lowercases, strips diacritics (Czech/Slovak titles are common here),
79
+ * collapses every non-alphanumeric run to a single `-`, and appends the
80
+ * export date so repeated exports don't shadow each other in the user's
81
+ * downloads folder. Falls back to `conversation` for titles that reduce
82
+ * to nothing.
83
+ *
84
+ * @param sessionTitle - the session's title.
85
+ * @param exportedAt - export timestamp (date part is used).
86
+ * @returns e.g. `muj-rozhovor-2026-07-17.md`.
87
+ */
88
+ export function conversationExportFilename(
89
+ sessionTitle: string,
90
+ exportedAt: Date,
91
+ ): string {
92
+ const slug = sessionTitle
93
+ .normalize("NFKD")
94
+ .replace(/[\u0300-\u036f]/g, "")
95
+ .toLowerCase()
96
+ .replace(/[^a-z0-9]+/g, "-")
97
+ .replace(/^-+|-+$/g, "")
98
+ .slice(0, 60);
99
+ const date = exportedAt.toISOString().slice(0, 10);
100
+ return `${slug.length > 0 ? slug : "conversation"}-${date}.md`;
101
+ }