beads-map 0.3.3 → 0.3.5
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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +2 -2
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/api/beads.body +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/page.js +3 -3
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +5 -5
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/app/page-7a8908706a09b720.js +1 -0
- package/.next/static/css/a506e3d172da58ef.css +3 -0
- package/app/page.tsx +66 -3
- package/components/BeadsGraph.tsx +108 -10
- package/components/ContextMenu.tsx +51 -5
- package/components/DescriptionModal.tsx +313 -10
- package/components/HelpPanel.tsx +4 -1
- package/components/NodeDetail.tsx +3 -1
- package/components/SettingsModal.tsx +235 -0
- package/components/TutorialOverlay.tsx +3 -3
- package/lib/settings.ts +42 -0
- package/lib/tts.ts +137 -0
- package/package.json +1 -1
- package/.next/static/chunks/app/page-4a4f07fcb5bd4637.js +0 -1
- package/.next/static/css/df2737696baac0fa.css +0 -3
- /package/.next/static/{bsmkR-2y8Ra7VuoNZWLzB → e6v54SLUeGDtx1DXW7JjL}/_buildManifest.js +0 -0
- /package/.next/static/{bsmkR-2y8Ra7VuoNZWLzB → e6v54SLUeGDtx1DXW7JjL}/_ssgManifest.js +0 -0
|
@@ -27,7 +27,7 @@ export const TUTORIAL_STEPS: TutorialStep[] = [
|
|
|
27
27
|
target: "view-controls",
|
|
28
28
|
title: "View Controls",
|
|
29
29
|
description:
|
|
30
|
-
"Collapse or expand epic groups, toggle cluster label overlays,
|
|
30
|
+
"Collapse or expand epic groups, toggle cluster label overlays, control auto-fit, and toggle the pulse animation. When auto-fit is on (green), the camera re-centers after every update. Pulse highlights the most recently active node with emerald ripples.",
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
target: "legend",
|
|
@@ -51,14 +51,14 @@ export const TUTORIAL_STEPS: TutorialStep[] = [
|
|
|
51
51
|
target: "graph",
|
|
52
52
|
title: "Interacting with Nodes",
|
|
53
53
|
description:
|
|
54
|
-
"Click a node to see details. Hover for a quick tooltip. Right-click for actions like viewing descriptions, commenting, claiming tasks, or
|
|
54
|
+
"Click a node to see details. Hover for a quick tooltip. Right-click for actions like viewing descriptions, commenting, claiming tasks, collapsing epics, or focusing on an epic to isolate its subgraph.",
|
|
55
55
|
padding: 0,
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
target: "nav-pills",
|
|
59
59
|
title: "Navigation Bar",
|
|
60
60
|
description:
|
|
61
|
-
"Replay steps through your project\u2019s history. Comments shows conversations. Activity is a real-time feed. And Learn brings you right back here.",
|
|
61
|
+
"Replay steps through your project\u2019s history. Comments shows conversations across your beads. Activity is a real-time feed filtered to only your local issues. And Learn brings you right back here.",
|
|
62
62
|
},
|
|
63
63
|
];
|
|
64
64
|
|
package/lib/settings.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// User settings — persisted in localStorage
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
export interface BeadsMapSettings {
|
|
6
|
+
elevenLabsApiKey?: string;
|
|
7
|
+
elevenLabsVoiceId: string;
|
|
8
|
+
elevenLabsModel: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const STORAGE_KEY = "beads-map-settings";
|
|
12
|
+
|
|
13
|
+
const DEFAULTS: BeadsMapSettings = {
|
|
14
|
+
elevenLabsVoiceId: "UgBBYS2sOqTuMpoF3BR0", // Mark - Natural Conversations
|
|
15
|
+
elevenLabsModel: "eleven_multilingual_v2",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Read settings from localStorage, merged with defaults */
|
|
19
|
+
export function getSettings(): BeadsMapSettings {
|
|
20
|
+
if (typeof window === "undefined") return { ...DEFAULTS };
|
|
21
|
+
try {
|
|
22
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
23
|
+
if (!raw) return { ...DEFAULTS };
|
|
24
|
+
return { ...DEFAULTS, ...JSON.parse(raw) };
|
|
25
|
+
} catch {
|
|
26
|
+
return { ...DEFAULTS };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Write settings to localStorage */
|
|
31
|
+
export function saveSettings(settings: Partial<BeadsMapSettings>): void {
|
|
32
|
+
if (typeof window === "undefined") return;
|
|
33
|
+
const current = getSettings();
|
|
34
|
+
const merged = { ...current, ...settings };
|
|
35
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(merged));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Check if ElevenLabs API key is configured */
|
|
39
|
+
export function hasApiKey(): boolean {
|
|
40
|
+
const s = getSettings();
|
|
41
|
+
return !!s.elevenLabsApiKey && s.elevenLabsApiKey.trim().length > 0;
|
|
42
|
+
}
|
package/lib/tts.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ElevenLabs Text-to-Speech helper
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import { getSettings } from "./settings";
|
|
6
|
+
|
|
7
|
+
// --- Markdown stripping ---------------------------------------------------
|
|
8
|
+
|
|
9
|
+
/** Strip Markdown syntax to produce clean plain text for TTS */
|
|
10
|
+
export function stripMarkdown(md: string): string {
|
|
11
|
+
return (
|
|
12
|
+
md
|
|
13
|
+
// Remove code blocks (triple backtick)
|
|
14
|
+
.replace(/```[\s\S]*?```/g, "")
|
|
15
|
+
// Remove inline code
|
|
16
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
17
|
+
// Remove images 
|
|
18
|
+
.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1")
|
|
19
|
+
// Convert links [text](url) to just text
|
|
20
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
21
|
+
// Remove headers (# ## ### etc)
|
|
22
|
+
.replace(/^#{1,6}\s+/gm, "")
|
|
23
|
+
// Remove bold/italic markers
|
|
24
|
+
.replace(/(\*{1,3}|_{1,3})(.*?)\1/g, "$2")
|
|
25
|
+
// Remove strikethrough
|
|
26
|
+
.replace(/~~(.*?)~~/g, "$1")
|
|
27
|
+
// Remove horizontal rules
|
|
28
|
+
.replace(/^[-*_]{3,}\s*$/gm, "")
|
|
29
|
+
// Remove blockquotes
|
|
30
|
+
.replace(/^>\s+/gm, "")
|
|
31
|
+
// Remove list markers
|
|
32
|
+
.replace(/^[\s]*[-*+]\s+/gm, "")
|
|
33
|
+
.replace(/^[\s]*\d+\.\s+/gm, "")
|
|
34
|
+
// Remove HTML tags
|
|
35
|
+
.replace(/<[^>]+>/g, "")
|
|
36
|
+
// Collapse multiple newlines
|
|
37
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
38
|
+
.trim()
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// --- Audio playback -------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/** Current audio element for stop control */
|
|
45
|
+
let currentAudio: HTMLAudioElement | null = null;
|
|
46
|
+
let currentBlobUrl: string | null = null;
|
|
47
|
+
|
|
48
|
+
export type TtsState = "idle" | "loading" | "playing" | "error";
|
|
49
|
+
|
|
50
|
+
/** Set playback speed on the current audio element */
|
|
51
|
+
export function setTtsPlaybackRate(rate: number): void {
|
|
52
|
+
if (currentAudio) {
|
|
53
|
+
currentAudio.playbackRate = rate;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Stop any currently playing TTS audio */
|
|
58
|
+
export function stopTts(): void {
|
|
59
|
+
if (currentAudio) {
|
|
60
|
+
currentAudio.pause();
|
|
61
|
+
currentAudio.currentTime = 0;
|
|
62
|
+
currentAudio = null;
|
|
63
|
+
}
|
|
64
|
+
if (currentBlobUrl) {
|
|
65
|
+
URL.revokeObjectURL(currentBlobUrl);
|
|
66
|
+
currentBlobUrl = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Call ElevenLabs TTS API and play the result.
|
|
72
|
+
* @param text - Plain text to speak (already stripped of Markdown)
|
|
73
|
+
* @param onStateChange - Callback for state transitions
|
|
74
|
+
*/
|
|
75
|
+
export async function speakWithElevenLabs(
|
|
76
|
+
text: string,
|
|
77
|
+
onStateChange: (state: TtsState, error?: string) => void
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
// Stop any existing playback
|
|
80
|
+
stopTts();
|
|
81
|
+
|
|
82
|
+
const settings = getSettings();
|
|
83
|
+
if (!settings.elevenLabsApiKey) {
|
|
84
|
+
onStateChange("error", "No API key configured");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onStateChange("loading");
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const voiceId = settings.elevenLabsVoiceId;
|
|
92
|
+
const res = await fetch(
|
|
93
|
+
`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}?output_format=mp3_44100_128`,
|
|
94
|
+
{
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
"xi-api-key": settings.elevenLabsApiKey,
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
text,
|
|
102
|
+
model_id: settings.elevenLabsModel,
|
|
103
|
+
}),
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const errorText = await res.text().catch(() => res.statusText);
|
|
109
|
+
throw new Error(`ElevenLabs API error (${res.status}): ${errorText}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const blob = await res.blob();
|
|
113
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
114
|
+
currentBlobUrl = blobUrl;
|
|
115
|
+
|
|
116
|
+
const audio = new Audio(blobUrl);
|
|
117
|
+
currentAudio = audio;
|
|
118
|
+
|
|
119
|
+
onStateChange("playing");
|
|
120
|
+
|
|
121
|
+
audio.onended = () => {
|
|
122
|
+
stopTts();
|
|
123
|
+
onStateChange("idle");
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
audio.onerror = () => {
|
|
127
|
+
stopTts();
|
|
128
|
+
onStateChange("error", "Audio playback failed");
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
await audio.play();
|
|
132
|
+
} catch (err) {
|
|
133
|
+
stopTts();
|
|
134
|
+
const message = err instanceof Error ? err.message : "TTS failed";
|
|
135
|
+
onStateChange("error", message);
|
|
136
|
+
}
|
|
137
|
+
}
|