@data-club/ai-hub 0.0.11 → 0.0.12
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 +4 -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 +4 -0
- package/dist/auth/locales/sk.js.map +1 -1
- package/dist/auth/texts.d.ts +8 -0
- package/dist/auth/texts.d.ts.map +1 -1
- package/dist/auth/texts.js +4 -0
- package/dist/auth/texts.js.map +1 -1
- package/dist/components/ChatShell/index.d.ts +2 -0
- package/dist/components/ChatShell/index.d.ts.map +1 -1
- package/dist/components/ChatShell/index.js +3 -1
- package/dist/components/ChatShell/index.js.map +1 -1
- package/dist/components/DataClubAIHub/index.d.ts +2 -0
- package/dist/components/DataClubAIHub/index.d.ts.map +1 -1
- package/dist/components/DataClubAIHub/index.js +2 -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 +3 -1
- 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/state/affordances.d.ts +3 -1
- package/dist/state/affordances.d.ts.map +1 -1
- package/dist/state/affordances.js +3 -1
- package/dist/state/affordances.js.map +1 -1
- package/dist/types.d.ts +2 -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 +4 -0
- package/src/auth/locales/sk.ts +4 -0
- package/src/auth/texts.ts +12 -0
- package/src/components/ChatShell/index.tsx +5 -0
- package/src/components/DataClubAIHub/index.tsx +5 -0
- package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +3 -1
- package/src/components/MainColumn/index.tsx +85 -1
- package/src/state/affordances.ts +5 -1
- package/src/styles/default.css +21 -0
- package/src/types.ts +2 -0
- package/src/util/exportConversation.ts +101 -0
package/src/state/affordances.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface AIHubAffordanceFlags {
|
|
|
67
67
|
scrollToPresentVisible: boolean;
|
|
68
68
|
/** Whether the turn-status indicator renders in the chat header. Default true. */
|
|
69
69
|
turnStatusVisible: boolean;
|
|
70
|
+
/** Whether the export-conversation (.md) button renders in the chat header. Default true. */
|
|
71
|
+
exportConversationEnabled: boolean;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
/** Built-in defaults — applied when neither server nor React prop sets a value. */
|
|
@@ -84,6 +86,7 @@ export const DEFAULT_AFFORDANCE_FLAGS: AIHubAffordanceFlags = Object.freeze({
|
|
|
84
86
|
effortControlVisible: true,
|
|
85
87
|
scrollToPresentVisible: true,
|
|
86
88
|
turnStatusVisible: true,
|
|
89
|
+
exportConversationEnabled: true,
|
|
87
90
|
}) as AIHubAffordanceFlags;
|
|
88
91
|
|
|
89
92
|
/**
|
|
@@ -107,6 +110,7 @@ export const FEATURE_TOGGLE_KEY_MAP: ReadonlyArray<
|
|
|
107
110
|
["effort_control_visible", "effortControlVisible"],
|
|
108
111
|
["scroll_to_present_visible", "scrollToPresentVisible"],
|
|
109
112
|
["turn_status_visible", "turnStatusVisible"],
|
|
113
|
+
["export_conversation_enabled", "exportConversationEnabled"],
|
|
110
114
|
]);
|
|
111
115
|
|
|
112
116
|
/**
|
|
@@ -134,7 +138,7 @@ function resolveOne(
|
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
/**
|
|
137
|
-
* Composes the three layers into a flat resolved record of all
|
|
141
|
+
* Composes the three layers into a flat resolved record of all 14
|
|
138
142
|
* affordance booleans.
|
|
139
143
|
*
|
|
140
144
|
* 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
|
@@ -716,6 +716,8 @@ export interface AIHubFeatureToggles {
|
|
|
716
716
|
scroll_to_present_visible?: boolean;
|
|
717
717
|
/** Overrides `showTurnStatus` prop. */
|
|
718
718
|
turn_status_visible?: boolean;
|
|
719
|
+
/** Overrides `showExportButton` prop. */
|
|
720
|
+
export_conversation_enabled?: boolean;
|
|
719
721
|
}
|
|
720
722
|
|
|
721
723
|
/* ────────────────────────────────────────────────────────────────────────── */
|
|
@@ -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
|
+
}
|