@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.
- package/dist/auth/locales/cs.d.ts.map +1 -1
- package/dist/auth/locales/cs.js +5 -0
- package/dist/auth/locales/cs.js.map +1 -1
- package/dist/auth/locales/sk.d.ts.map +1 -1
- package/dist/auth/locales/sk.js +5 -0
- package/dist/auth/locales/sk.js.map +1 -1
- package/dist/auth/texts.d.ts +13 -0
- package/dist/auth/texts.d.ts.map +1 -1
- package/dist/auth/texts.js +5 -0
- package/dist/auth/texts.js.map +1 -1
- package/dist/components/ChatShell/index.d.ts +6 -0
- package/dist/components/ChatShell/index.d.ts.map +1 -1
- package/dist/components/ChatShell/index.js +66 -3
- package/dist/components/ChatShell/index.js.map +1 -1
- package/dist/components/DataClubAIHub/index.d.ts +6 -0
- package/dist/components/DataClubAIHub/index.d.ts.map +1 -1
- package/dist/components/DataClubAIHub/index.js +6 -0
- package/dist/components/DataClubAIHub/index.js.map +1 -1
- package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -1
- package/dist/components/DataClubAIHubAdmin/SettingsPage.js +20 -5
- package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -1
- package/dist/components/MainColumn/index.d.ts +8 -0
- package/dist/components/MainColumn/index.d.ts.map +1 -1
- package/dist/components/MainColumn/index.js +40 -2
- package/dist/components/MainColumn/index.js.map +1 -1
- package/dist/components/SessionsPanel/index.d.ts +5 -0
- package/dist/components/SessionsPanel/index.d.ts.map +1 -1
- package/dist/components/SessionsPanel/index.js +8 -3
- package/dist/components/SessionsPanel/index.js.map +1 -1
- package/dist/state/affordances.d.ts +7 -1
- package/dist/state/affordances.d.ts.map +1 -1
- package/dist/state/affordances.js +7 -1
- package/dist/state/affordances.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/util/exportConversation.d.ts +64 -0
- package/dist/util/exportConversation.d.ts.map +1 -0
- package/dist/util/exportConversation.js +78 -0
- package/dist/util/exportConversation.js.map +1 -0
- package/package.json +1 -1
- package/src/auth/locales/cs.ts +5 -0
- package/src/auth/locales/sk.ts +5 -0
- package/src/auth/texts.ts +18 -0
- package/src/components/ChatShell/index.tsx +67 -0
- package/src/components/DataClubAIHub/index.tsx +15 -0
- package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +21 -5
- package/src/components/MainColumn/index.tsx +85 -1
- package/src/components/SessionsPanel/index.tsx +13 -3
- package/src/state/affordances.ts +13 -1
- package/src/styles/default.css +21 -0
- package/src/types.ts +15 -0
- package/src/util/exportConversation.ts +101 -0
package/src/state/affordances.ts
CHANGED
|
@@ -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
|
|
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;
|
package/src/styles/default.css
CHANGED
|
@@ -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
|
+
}
|