@burdenoff/microfe-buildmyiq 2026.1009.2 → 2026.1024.1
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/buildmyiq/components/GroundedTutorPanel.d.ts +33 -0
- package/dist/buildmyiq/components/GroundedTutorPanel.d.ts.map +1 -0
- package/dist/buildmyiq/components/GroundedTutorPanel.js +197 -0
- package/dist/buildmyiq/components/GroundedTutorPanel.js.map +1 -0
- package/dist/buildmyiq/components/TutorPanel.d.ts +5 -1
- package/dist/buildmyiq/components/TutorPanel.d.ts.map +1 -1
- package/dist/buildmyiq/components/TutorPanel.js +20 -12
- package/dist/buildmyiq/components/TutorPanel.js.map +1 -1
- package/dist/buildmyiq/hooks/useBuiFiles.d.ts +1 -1
- package/dist/buildmyiq/hooks/useBuiFiles.d.ts.map +1 -1
- package/dist/buildmyiq/hooks/useBuiFiles.js +4 -1
- package/dist/buildmyiq/hooks/useBuiFiles.js.map +1 -1
- package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts +16 -0
- package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts.map +1 -1
- package/dist/buildmyiq/hooks/useBuildmyiqApi.js +185 -129
- package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -1
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.js +16 -13
- package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GroundedTutorPanel — the native 24/7 AI tutor chat for a lesson (BOFF-4426).
|
|
3
|
+
* @module buildmyiq/components/GroundedTutorPanel
|
|
4
|
+
*
|
|
5
|
+
* Rendered by `TutorPanel` for every course that has no course teacher
|
|
6
|
+
* (a configured course assistant) bound, so every lesson has a working,
|
|
7
|
+
* content-grounded tutor without any per-course setup.
|
|
8
|
+
*
|
|
9
|
+
* Grounded Q&A: every assistant answer comes from `buiTutorAsk`, which
|
|
10
|
+
* retrieves the actual lesson content server-side before calling the model
|
|
11
|
+
* (see `TutorService.retrieveGrounding` in wspace-buildmyiq-svc) — never an
|
|
12
|
+
* ungrounded chat completion. Continues the learner's own session for this
|
|
13
|
+
* lesson (multi-turn) via `useBuiMyTutorSessions` / `useBuiTutorAsk`.
|
|
14
|
+
*
|
|
15
|
+
* Each assistant turn carries the `groundingLessonIds` the answer was actually
|
|
16
|
+
* derived from; they are rendered as source chips so the learner can see which
|
|
17
|
+
* lesson content backs the answer instead of taking it on trust. Ids the caller
|
|
18
|
+
* cannot name are shown as-is rather than guessed at or hidden.
|
|
19
|
+
*/
|
|
20
|
+
interface GroundedTutorPanelProps {
|
|
21
|
+
lessonId: string;
|
|
22
|
+
lessonTitle: string;
|
|
23
|
+
/**
|
|
24
|
+
* Titles for the lessons the tutor may ground an answer in, keyed by lesson
|
|
25
|
+
* id — supplied by the host page, which has already loaded the course. Only
|
|
26
|
+
* used to LABEL the ids the server returned; a missing entry falls back to
|
|
27
|
+
* the raw id, never to an invented title.
|
|
28
|
+
*/
|
|
29
|
+
lessonTitlesById?: ReadonlyMap<string, string>;
|
|
30
|
+
}
|
|
31
|
+
export declare function GroundedTutorPanel({ lessonId, lessonTitle, lessonTitlesById, }: GroundedTutorPanelProps): import("react").JSX.Element;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=GroundedTutorPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroundedTutorPanel.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/GroundedTutorPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAWH,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;AAmBD,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,WAAW,EACX,gBAAgB,GACjB,EAAE,uBAAuB,+BAoMzB"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
|
|
2
|
+
import "../context/index.js";
|
|
3
|
+
import { useTr as t } from "../shared/hooks/useTr.js";
|
|
4
|
+
import { LoadingView as n } from "./StateViews.js";
|
|
5
|
+
import { useBuiMyTutorSessions as r, useBuiTutorAsk as i } from "../hooks/useBuildmyiqApi.js";
|
|
6
|
+
import "../hooks/index.js";
|
|
7
|
+
import { useEffect as a, useRef as o, useState as s } from "react";
|
|
8
|
+
import { BookOpen as c, Bot as l, Send as u, Sparkles as d } from "lucide-react";
|
|
9
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
10
|
+
import { Button as m, GlassCard as h, Textarea as g } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
//#region src/buildmyiq/components/GroundedTutorPanel.tsx
|
|
12
|
+
function _(e) {
|
|
13
|
+
let t = e.trim();
|
|
14
|
+
return t ? t[0].toUpperCase() : "?";
|
|
15
|
+
}
|
|
16
|
+
var v = {
|
|
17
|
+
STRUGGLING: "Extra step-by-step help — based on your recent scores",
|
|
18
|
+
ON_TRACK: "Standard depth",
|
|
19
|
+
ADVANCED: "Deeper dive — based on your recent scores"
|
|
20
|
+
};
|
|
21
|
+
function y(e) {
|
|
22
|
+
if (!e) return "You";
|
|
23
|
+
let t = [e.firstName, e.lastName].filter(Boolean).join(" ").trim();
|
|
24
|
+
return e.name || t || e.username || e.email || "You";
|
|
25
|
+
}
|
|
26
|
+
function b({ lessonId: b, lessonTitle: x, lessonTitlesById: S }) {
|
|
27
|
+
let { currentUser: C } = e(), w = t(), T = r(b), E = i(), [D, O] = s(void 0), [k, A] = s(""), [j, M] = s(null), [N, P] = s(null), F = o(null), I = T.data?.[0], L = D ? T.data?.find((e) => e.id === D)?.messages ?? [] : I?.messages ?? [];
|
|
28
|
+
a(() => {
|
|
29
|
+
F.current?.scrollTo({
|
|
30
|
+
top: F.current.scrollHeight,
|
|
31
|
+
behavior: "smooth"
|
|
32
|
+
});
|
|
33
|
+
}, [L.length, N]);
|
|
34
|
+
let R = T.isSuccess, z = y(C), B = (e) => e === b ? x : S?.get(e) ?? e, V = async () => {
|
|
35
|
+
let e = k.trim();
|
|
36
|
+
if (!(!e || !R)) {
|
|
37
|
+
M(null), A(""), P(e);
|
|
38
|
+
try {
|
|
39
|
+
O((await E.mutateAsync({
|
|
40
|
+
lessonId: b,
|
|
41
|
+
question: e,
|
|
42
|
+
sessionId: D ?? I?.id
|
|
43
|
+
})).session.id);
|
|
44
|
+
} catch (t) {
|
|
45
|
+
M(t instanceof Error ? t.message : "The AI tutor is temporarily unavailable."), A(e);
|
|
46
|
+
} finally {
|
|
47
|
+
P(null);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return /* @__PURE__ */ p(h, {
|
|
52
|
+
treatment: "plain",
|
|
53
|
+
className: "flex flex-col overflow-hidden p-0",
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ p("div", {
|
|
56
|
+
className: "flex items-center gap-2 border-b border-border-seam px-4 py-3",
|
|
57
|
+
children: [/* @__PURE__ */ f("span", {
|
|
58
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]",
|
|
59
|
+
children: /* @__PURE__ */ f(d, { className: "h-4 w-4 text-[var(--color-accent-teal)]" })
|
|
60
|
+
}), /* @__PURE__ */ p("div", {
|
|
61
|
+
className: "min-w-0",
|
|
62
|
+
children: [/* @__PURE__ */ f("p", {
|
|
63
|
+
className: "text-sm font-semibold text-text-primary",
|
|
64
|
+
children: "AI Tutor"
|
|
65
|
+
}), /* @__PURE__ */ p("p", {
|
|
66
|
+
className: "truncate text-xs text-text-secondary",
|
|
67
|
+
children: [
|
|
68
|
+
"Grounded in “",
|
|
69
|
+
x,
|
|
70
|
+
"”"
|
|
71
|
+
]
|
|
72
|
+
})]
|
|
73
|
+
})]
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ f("div", {
|
|
76
|
+
ref: F,
|
|
77
|
+
className: "max-h-96 min-h-40 flex-1 overflow-y-auto px-4 py-4",
|
|
78
|
+
children: T.isLoading ? /* @__PURE__ */ f(n, { label: "Loading your tutor conversation…" }) : L.length === 0 ? /* @__PURE__ */ p("div", {
|
|
79
|
+
className: "flex flex-col items-center gap-2 py-6 text-center",
|
|
80
|
+
children: [/* @__PURE__ */ f(l, { className: "h-8 w-8 text-text-tertiary" }), /* @__PURE__ */ f("p", {
|
|
81
|
+
className: "text-sm text-text-secondary",
|
|
82
|
+
children: "Ask a question about this lesson — the tutor answers from the lesson content, with hints first."
|
|
83
|
+
})]
|
|
84
|
+
}) : /* @__PURE__ */ p("ul", {
|
|
85
|
+
className: "flex flex-col gap-4",
|
|
86
|
+
children: [
|
|
87
|
+
L.map((e) => {
|
|
88
|
+
let t = e.role === "ASSISTANT";
|
|
89
|
+
return /* @__PURE__ */ p("li", {
|
|
90
|
+
className: "flex gap-3",
|
|
91
|
+
children: [/* @__PURE__ */ f("div", {
|
|
92
|
+
className: t ? "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]" : "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary",
|
|
93
|
+
children: t ? /* @__PURE__ */ f(l, { className: "h-4 w-4 text-[var(--color-accent-teal)]" }) : _(z)
|
|
94
|
+
}), /* @__PURE__ */ p("div", {
|
|
95
|
+
className: "min-w-0 flex-1",
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ p("div", {
|
|
98
|
+
className: "flex flex-wrap items-baseline gap-x-2",
|
|
99
|
+
children: [/* @__PURE__ */ f("span", {
|
|
100
|
+
className: "text-sm font-semibold text-text-primary",
|
|
101
|
+
children: t ? "AI Tutor" : z
|
|
102
|
+
}), t && e.adaptiveLevel && /* @__PURE__ */ f("span", {
|
|
103
|
+
className: "text-xs text-text-tertiary",
|
|
104
|
+
children: v[e.adaptiveLevel]
|
|
105
|
+
})]
|
|
106
|
+
}),
|
|
107
|
+
/* @__PURE__ */ f("p", {
|
|
108
|
+
className: "mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary",
|
|
109
|
+
children: e.content
|
|
110
|
+
}),
|
|
111
|
+
t && e.groundingLessonIds.length > 0 && /* @__PURE__ */ p("div", {
|
|
112
|
+
className: "mt-2 flex flex-wrap items-center gap-1.5",
|
|
113
|
+
children: [/* @__PURE__ */ p("span", {
|
|
114
|
+
className: "inline-flex items-center gap-1 text-xs text-text-tertiary",
|
|
115
|
+
children: [/* @__PURE__ */ f(c, { className: "h-3.5 w-3.5" }), w("buildmyiq.tutor.groundedIn", "Answered from")]
|
|
116
|
+
}), e.groundingLessonIds.map((e) => /* @__PURE__ */ f("span", {
|
|
117
|
+
title: e,
|
|
118
|
+
className: "max-w-[14rem] truncate rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
119
|
+
children: B(e)
|
|
120
|
+
}, e))]
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
})]
|
|
124
|
+
}, e.id);
|
|
125
|
+
}),
|
|
126
|
+
N && /* @__PURE__ */ p("li", {
|
|
127
|
+
className: "flex gap-3",
|
|
128
|
+
children: [/* @__PURE__ */ f("div", {
|
|
129
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary",
|
|
130
|
+
children: _(z)
|
|
131
|
+
}), /* @__PURE__ */ p("div", {
|
|
132
|
+
className: "min-w-0 flex-1",
|
|
133
|
+
children: [/* @__PURE__ */ f("span", {
|
|
134
|
+
className: "text-sm font-semibold text-text-primary",
|
|
135
|
+
children: z
|
|
136
|
+
}), /* @__PURE__ */ f("p", {
|
|
137
|
+
className: "mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary",
|
|
138
|
+
children: N
|
|
139
|
+
})]
|
|
140
|
+
})]
|
|
141
|
+
}),
|
|
142
|
+
E.isPending && /* @__PURE__ */ p("li", {
|
|
143
|
+
className: "flex items-center gap-3",
|
|
144
|
+
children: [/* @__PURE__ */ f("span", {
|
|
145
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]",
|
|
146
|
+
children: /* @__PURE__ */ f(l, { className: "h-4 w-4 animate-pulse text-[var(--color-accent-teal)]" })
|
|
147
|
+
}), /* @__PURE__ */ f("span", {
|
|
148
|
+
className: "text-sm text-text-secondary",
|
|
149
|
+
children: "Thinking…"
|
|
150
|
+
})]
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
})
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ p("div", {
|
|
156
|
+
className: "border-t border-border-seam p-3",
|
|
157
|
+
children: [/* @__PURE__ */ f(g, {
|
|
158
|
+
value: k,
|
|
159
|
+
onChange: (e) => A(e.target.value),
|
|
160
|
+
onKeyDown: (e) => {
|
|
161
|
+
e.key === "Enter" && !e.shiftKey && (e.preventDefault(), V());
|
|
162
|
+
},
|
|
163
|
+
placeholder: "Ask about this lesson…",
|
|
164
|
+
rows: 2,
|
|
165
|
+
disabled: E.isPending || !R
|
|
166
|
+
}), /* @__PURE__ */ p("div", {
|
|
167
|
+
className: "mt-2 flex items-center justify-end gap-2",
|
|
168
|
+
children: [
|
|
169
|
+
j && /* @__PURE__ */ f("span", {
|
|
170
|
+
className: "mr-auto text-xs text-status-error-text",
|
|
171
|
+
children: j
|
|
172
|
+
}),
|
|
173
|
+
T.isError && !j && /* @__PURE__ */ p("span", {
|
|
174
|
+
className: "mr-auto flex items-center gap-2 text-xs text-status-error-text",
|
|
175
|
+
children: ["Your tutor history could not be loaded.", /* @__PURE__ */ f(m, {
|
|
176
|
+
size: "sm",
|
|
177
|
+
variant: "outline",
|
|
178
|
+
onClick: () => void T.refetch(),
|
|
179
|
+
children: "Retry"
|
|
180
|
+
})]
|
|
181
|
+
}),
|
|
182
|
+
/* @__PURE__ */ p(m, {
|
|
183
|
+
size: "sm",
|
|
184
|
+
onClick: V,
|
|
185
|
+
disabled: !k.trim() || E.isPending || !R,
|
|
186
|
+
children: [/* @__PURE__ */ f(u, { className: "mr-1.5 h-4 w-4" }), E.isPending ? "Asking…" : "Ask"]
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
})]
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
export { b as GroundedTutorPanel };
|
|
196
|
+
|
|
197
|
+
//# sourceMappingURL=GroundedTutorPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroundedTutorPanel.js","names":[],"sources":["../../../src/buildmyiq/components/GroundedTutorPanel.tsx"],"sourcesContent":["/**\n * GroundedTutorPanel — the native 24/7 AI tutor chat for a lesson (BOFF-4426).\n * @module buildmyiq/components/GroundedTutorPanel\n *\n * Rendered by `TutorPanel` for every course that has no course teacher\n * (a configured course assistant) bound, so every lesson has a working,\n * content-grounded tutor without any per-course setup.\n *\n * Grounded Q&A: every assistant answer comes from `buiTutorAsk`, which\n * retrieves the actual lesson content server-side before calling the model\n * (see `TutorService.retrieveGrounding` in wspace-buildmyiq-svc) — never an\n * ungrounded chat completion. Continues the learner's own session for this\n * lesson (multi-turn) via `useBuiMyTutorSessions` / `useBuiTutorAsk`.\n *\n * Each assistant turn carries the `groundingLessonIds` the answer was actually\n * derived from; they are rendered as source chips so the learner can see which\n * lesson content backs the answer instead of taking it on trust. Ids the caller\n * cannot name are shown as-is rather than guessed at or hidden.\n */\n\nimport { useEffect, useRef, useState } from 'react';\nimport { Bot, BookOpen, Send, Sparkles } from 'lucide-react';\nimport { Button, GlassCard, Textarea } from '@burdenoff/fe-libs/ui';\nimport { LoadingView } from './StateViews';\nimport { useBuiMyTutorSessions, useBuiTutorAsk } from '../hooks';\nimport { useBuildmyiq } from '../context';\nimport { useTr } from '../shared/hooks/useTr';\nimport type { BuiTutorAdaptiveLevel, BuiTutorMessage } from '../types';\n\ninterface GroundedTutorPanelProps {\n lessonId: string;\n lessonTitle: string;\n /**\n * Titles for the lessons the tutor may ground an answer in, keyed by lesson\n * id — supplied by the host page, which has already loaded the course. Only\n * used to LABEL the ids the server returned; a missing entry falls back to\n * the raw id, never to an invented title.\n */\n lessonTitlesById?: ReadonlyMap<string, string>;\n}\n\nfunction initialFor(label: string): string {\n const trimmed = label.trim();\n return trimmed ? trimmed[0]!.toUpperCase() : '?';\n}\n\nconst ADAPTIVE_LABEL: Record<BuiTutorAdaptiveLevel, string> = {\n STRUGGLING: 'Extra step-by-step help — based on your recent scores',\n ON_TRACK: 'Standard depth',\n ADVANCED: 'Deeper dive — based on your recent scores',\n};\n\nfunction displayName(user: ReturnType<typeof useBuildmyiq>['currentUser']): string {\n if (!user) return 'You';\n const full = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();\n return user.name || full || user.username || user.email || 'You';\n}\n\nexport function GroundedTutorPanel({\n lessonId,\n lessonTitle,\n lessonTitlesById,\n}: GroundedTutorPanelProps) {\n const { currentUser } = useBuildmyiq();\n const tr = useTr();\n const sessionsQuery = useBuiMyTutorSessions(lessonId);\n const ask = useBuiTutorAsk();\n\n const [sessionId, setSessionId] = useState<string | undefined>(undefined);\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n // Echo of the question in flight, shown until the refetched transcript lands.\n const [pendingQuestion, setPendingQuestion] = useState<string | null>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Resume the learner's most recently active session for this lesson, if any.\n const existingSession = sessionsQuery.data?.[0];\n const messages: BuiTutorMessage[] = sessionId\n ? (sessionsQuery.data?.find((s) => s.id === sessionId)?.messages ?? [])\n : (existingSession?.messages ?? []);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });\n }, [messages.length, pendingQuestion]);\n\n // Asking before the history loads would start a second conversation instead\n // of continuing the learner's existing one, so the composer waits for it.\n const historyReady = sessionsQuery.isSuccess;\n\n const selfName = displayName(currentUser);\n\n /** Label a grounding lesson id; an id we can't name is shown verbatim. */\n const groundingLabel = (id: string): string => {\n if (id === lessonId) return lessonTitle;\n return lessonTitlesById?.get(id) ?? id;\n };\n\n const handleAsk = async () => {\n const question = draft.trim();\n if (!question || !historyReady) return;\n setError(null);\n setDraft('');\n setPendingQuestion(question);\n try {\n const result = await ask.mutateAsync({\n lessonId,\n question,\n sessionId: sessionId ?? existingSession?.id,\n });\n setSessionId(result.session.id);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'The AI tutor is temporarily unavailable.');\n setDraft(question);\n } finally {\n setPendingQuestion(null);\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n void handleAsk();\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"flex flex-col overflow-hidden p-0\">\n <div className=\"flex items-center gap-2 border-b border-border-seam px-4 py-3\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Sparkles className=\"h-4 w-4 text-[var(--color-accent-teal)]\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-semibold text-text-primary\">AI Tutor</p>\n <p className=\"truncate text-xs text-text-secondary\">Grounded in “{lessonTitle}”</p>\n </div>\n </div>\n\n <div ref={scrollRef} className=\"max-h-96 min-h-40 flex-1 overflow-y-auto px-4 py-4\">\n {sessionsQuery.isLoading ? (\n <LoadingView label=\"Loading your tutor conversation…\" />\n ) : messages.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-6 text-center\">\n <Bot className=\"h-8 w-8 text-text-tertiary\" />\n <p className=\"text-sm text-text-secondary\">\n Ask a question about this lesson — the tutor answers from the lesson content, with\n hints first.\n </p>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {messages.map((message) => {\n const isAssistant = message.role === 'ASSISTANT';\n return (\n <li key={message.id} className=\"flex gap-3\">\n <div\n className={\n isAssistant\n ? 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]'\n : 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary'\n }\n >\n {isAssistant ? (\n <Bot className=\"h-4 w-4 text-[var(--color-accent-teal)]\" />\n ) : (\n initialFor(selfName)\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-baseline gap-x-2\">\n <span className=\"text-sm font-semibold text-text-primary\">\n {isAssistant ? 'AI Tutor' : selfName}\n </span>\n {isAssistant && message.adaptiveLevel && (\n <span className=\"text-xs text-text-tertiary\">\n {ADAPTIVE_LABEL[message.adaptiveLevel]}\n </span>\n )}\n </div>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {message.content}\n </p>\n {isAssistant && message.groundingLessonIds.length > 0 && (\n <div className=\"mt-2 flex flex-wrap items-center gap-1.5\">\n <span className=\"inline-flex items-center gap-1 text-xs text-text-tertiary\">\n <BookOpen className=\"h-3.5 w-3.5\" />\n {tr('buildmyiq.tutor.groundedIn', 'Answered from')}\n </span>\n {message.groundingLessonIds.map((groundingId) => (\n <span\n key={groundingId}\n title={groundingId}\n className=\"max-w-[14rem] truncate rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {groundingLabel(groundingId)}\n </span>\n ))}\n </div>\n )}\n </div>\n </li>\n );\n })}\n {pendingQuestion && (\n <li className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary\">\n {initialFor(selfName)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <span className=\"text-sm font-semibold text-text-primary\">{selfName}</span>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {pendingQuestion}\n </p>\n </div>\n </li>\n )}\n {ask.isPending && (\n <li className=\"flex items-center gap-3\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Bot className=\"h-4 w-4 animate-pulse text-[var(--color-accent-teal)]\" />\n </span>\n <span className=\"text-sm text-text-secondary\">Thinking…</span>\n </li>\n )}\n </ul>\n )}\n </div>\n\n <div className=\"border-t border-border-seam p-3\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Ask about this lesson…\"\n rows={2}\n disabled={ask.isPending || !historyReady}\n />\n <div className=\"mt-2 flex items-center justify-end gap-2\">\n {error && <span className=\"mr-auto text-xs text-status-error-text\">{error}</span>}\n {sessionsQuery.isError && !error && (\n <span className=\"mr-auto flex items-center gap-2 text-xs text-status-error-text\">\n Your tutor history could not be loaded.\n <Button size=\"sm\" variant=\"outline\" onClick={() => void sessionsQuery.refetch()}>\n Retry\n </Button>\n </span>\n )}\n <Button\n size=\"sm\"\n onClick={handleAsk}\n disabled={!draft.trim() || ask.isPending || !historyReady}\n >\n <Send className=\"mr-1.5 h-4 w-4\" />\n {ask.isPending ? 'Asking…' : 'Ask'}\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
interface TutorPanelProps {
|
|
2
2
|
courseId: string;
|
|
3
|
+
enrollmentId: string;
|
|
4
|
+
lessonId: string;
|
|
3
5
|
lessonTitle: string;
|
|
6
|
+
/** Titles for the course's lessons, used to label grounding sources. */
|
|
7
|
+
lessonTitlesById?: ReadonlyMap<string, string>;
|
|
4
8
|
}
|
|
5
|
-
export declare function TutorPanel({ courseId, lessonTitle }: TutorPanelProps): import("react").JSX.Element;
|
|
9
|
+
export declare function TutorPanel({ courseId, enrollmentId, lessonId, lessonTitle, lessonTitlesById, }: TutorPanelProps): import("react").JSX.Element;
|
|
6
10
|
export {};
|
|
7
11
|
//# sourceMappingURL=TutorPanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TutorPanel.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/TutorPanel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TutorPanel.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/TutorPanel.tsx"],"names":[],"mappings":"AAeA,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;AAED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,gBAAgB,GACjB,EAAE,eAAe,+BA0BjB"}
|
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
|
|
2
2
|
import "../context/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { LoadingView as t } from "./StateViews.js";
|
|
4
|
+
import { useBuiTutorMode as n } from "../hooks/useBuildmyiqApi.js";
|
|
5
|
+
import "../hooks/index.js";
|
|
6
|
+
import { GroundedTutorPanel as r } from "./GroundedTutorPanel.js";
|
|
7
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
8
|
+
import { WorkflowAssistantPanel as a } from "@burdenoff/fe-libs/shared/assistant";
|
|
5
9
|
//#region src/buildmyiq/components/TutorPanel.tsx
|
|
6
|
-
function
|
|
7
|
-
let { currentUser:
|
|
8
|
-
return /* @__PURE__ */ t(
|
|
10
|
+
function o({ courseId: o, enrollmentId: s, lessonId: c, lessonTitle: l, lessonTitlesById: u }) {
|
|
11
|
+
let { currentUser: d, workspaceId: f, authToken: p } = e(), m = n(s);
|
|
12
|
+
return m.isLoading ? /* @__PURE__ */ i(t, { label: "Loading the AI tutor…" }) : m.data === "teacher" ? /* @__PURE__ */ i(a, {
|
|
9
13
|
target: {
|
|
10
14
|
kind: "buildmyiq-course",
|
|
11
|
-
courseId:
|
|
15
|
+
courseId: o
|
|
12
16
|
},
|
|
13
17
|
context: {
|
|
14
|
-
workspaceId:
|
|
15
|
-
authContext: { accessToken:
|
|
18
|
+
workspaceId: f ?? "",
|
|
19
|
+
authContext: { accessToken: p }
|
|
16
20
|
},
|
|
17
|
-
ownerId:
|
|
21
|
+
ownerId: d?.id ?? "",
|
|
18
22
|
title: "AI Tutor",
|
|
19
|
-
description: `Answers from your course content. Current lesson: “${
|
|
20
|
-
})
|
|
23
|
+
description: `Answers from your course content. Current lesson: “${l}”`
|
|
24
|
+
}) : /* @__PURE__ */ i(r, {
|
|
25
|
+
lessonId: c,
|
|
26
|
+
lessonTitle: l,
|
|
27
|
+
lessonTitlesById: u
|
|
28
|
+
}, c);
|
|
21
29
|
}
|
|
22
30
|
//#endregion
|
|
23
|
-
export {
|
|
31
|
+
export { o as TutorPanel };
|
|
24
32
|
|
|
25
33
|
//# sourceMappingURL=TutorPanel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TutorPanel.js","names":[],"sources":["../../../src/buildmyiq/components/TutorPanel.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"TutorPanel.js","names":[],"sources":["../../../src/buildmyiq/components/TutorPanel.tsx"],"sourcesContent":["/**\n * TutorPanel — the lesson player's AI tutor (BOFF-4426).\n * @module buildmyiq/components/TutorPanel\n *\n * A course with a bound course teacher uses the shared Fluidgrids workflow\n * assistant. Every other course falls back to the native grounded tutor, so a\n * learner never sees a tutor that cannot answer just because no educator has\n * configured a course teacher yet.\n */\nimport { WorkflowAssistantPanel } from '@burdenoff/fe-libs/shared/assistant';\nimport { useBuildmyiq } from '../context';\nimport { useBuiTutorMode } from '../hooks';\nimport { GroundedTutorPanel } from './GroundedTutorPanel';\nimport { LoadingView } from './StateViews';\n\ninterface TutorPanelProps {\n courseId: string;\n enrollmentId: string;\n lessonId: string;\n lessonTitle: string;\n /** Titles for the course's lessons, used to label grounding sources. */\n lessonTitlesById?: ReadonlyMap<string, string>;\n}\n\nexport function TutorPanel({\n courseId,\n enrollmentId,\n lessonId,\n lessonTitle,\n lessonTitlesById,\n}: TutorPanelProps) {\n const { currentUser, workspaceId, authToken } = useBuildmyiq();\n const mode = useBuiTutorMode(enrollmentId);\n\n if (mode.isLoading) return <LoadingView label=\"Loading the AI tutor…\" />;\n\n if (mode.data === 'teacher') {\n return (\n <WorkflowAssistantPanel\n target={{ kind: 'buildmyiq-course', courseId }}\n context={{ workspaceId: workspaceId ?? '', authContext: { accessToken: authToken } }}\n ownerId={currentUser?.id ?? ''}\n title=\"AI Tutor\"\n description={`Answers from your course content. Current lesson: “${lessonTitle}”`}\n />\n );\n }\n\n return (\n <GroundedTutorPanel\n key={lessonId}\n lessonId={lessonId}\n lessonTitle={lessonTitle}\n lessonTitlesById={lessonTitlesById}\n />\n );\n}\n"],"mappings":";;;;;;;;;AAwBA,SAAgB,EAAW,EACzB,aACA,iBACA,aACA,gBACA,uBACkB;CAClB,IAAM,EAAE,gBAAa,gBAAa,iBAAc,EAAa,GACvD,IAAO,EAAgB,CAAY;CAgBzC,OAdI,EAAK,YAAkB,kBAAC,GAAD,EAAa,OAAM,wBAAyB,CAAA,IAEnE,EAAK,SAAS,YAEd,kBAAC,GAAD;EACE,QAAQ;GAAE,MAAM;GAAoB;EAAS;EAC7C,SAAS;GAAE,aAAa,KAAe;GAAI,aAAa,EAAE,aAAa,EAAU;EAAE;EACnF,SAAS,GAAa,MAAM;EAC5B,OAAM;EACN,aAAa,sDAAsD,EAAY;CAChF,CAAA,IAKH,kBAAC,GAAD;EAEY;EACG;EACK;CACnB,GAJM,CAIN;AAEL"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the canonical 3-step upload flow composed into the WORKSPACE supergraph:
|
|
9
9
|
* 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`
|
|
10
10
|
* 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`
|
|
11
|
-
* 3. `completeUpload(input: { sessionId })` → `{ fileId }`
|
|
11
|
+
* 3. `completeUpload(input: { sessionId, fileSize })` → `{ fileId }`
|
|
12
12
|
* plus `getFile(id)` to resolve an existing evidence/report file's name + URL.
|
|
13
13
|
*
|
|
14
14
|
* Transport mirrors `useBuildmyiqApi` exactly — `graphqlFetch({ gateway:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiFiles.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA8BH,8EAA8E;AAC9E,eAAO,MAAM,WAAW,GAAU,KAAK,EACrC,OAAO,MAAM,EACb,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,UAAU,MAAM,KACf,OAAO,CAAC,KAAK,CAmBf,CAAC;AAMF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AA+BD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAO,
|
|
1
|
+
{"version":3,"file":"useBuiFiles.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA8BH,8EAA8E;AAC9E,eAAO,MAAM,WAAW,GAAU,KAAK,EACrC,OAAO,MAAM,EACb,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,UAAU,MAAM,KACf,OAAO,CAAC,KAAK,CAmBf,CAAC;AAMF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AA+BD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAO,sBAqFnC,CAAC;AAMF,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,UAAU,GAAI,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS,uFAsBvD,CAAC"}
|
|
@@ -46,7 +46,10 @@ var c = () => {
|
|
|
46
46
|
} }, r), { uploadUrl: c } = a.initiateUpload, u = a.initiateUpload.session.id;
|
|
47
47
|
if (!c) throw Error("The file service did not return an upload URL.");
|
|
48
48
|
await s(c, e, i, l);
|
|
49
|
-
let d = await o("mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }", { input: {
|
|
49
|
+
let d = await o("mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }", { input: {
|
|
50
|
+
sessionId: u,
|
|
51
|
+
fileSize: String(e.size)
|
|
52
|
+
} }, r);
|
|
50
53
|
return l(100), {
|
|
51
54
|
fileId: d.completeUpload.fileId,
|
|
52
55
|
fileName: d.completeUpload.file?.name ?? e.name
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiFiles.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-files-svc REUSE hooks (Integration A).\n * @module buildmyiq/hooks/useBuiFiles\n *\n * Reuses the platform file store (`wspace-files-svc`) WITHOUT any buildmyiq-svc\n * change: the buildmyiq schema already exposes `BuiLesson.mediaFileId` /\n * `BuiCertificate.fileId`; this module lets the UI actually produce a `fileId` via\n * the canonical 3-step upload flow composed into the WORKSPACE supergraph:\n * 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`\n * 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`\n * 3. `completeUpload(input: { sessionId })` → `{ fileId }`\n * plus `getFile(id)` to resolve an existing evidence/report file's name + URL.\n *\n * Transport mirrors `useBuildmyiqApi` exactly — `graphqlFetch({ gateway:\n * 'workspace', workspaceToken: true })` — so no ApolloProvider is required.\n * These hooks live in a SEPARATE file by design; `useBuildmyiqApi.ts` is\n * left untouched.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\n\n// ============================================================================\n// Shared workspace-gateway transport (reused by the comments + tags hooks too)\n// ============================================================================\n\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache. After a write we drop those caches so the invalidation refetch that\n * follows reads fresh data (same remedy `useBuildmyiqApi` applies).\n */\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// ============================================================================\n// File upload (initiate → PUT → complete)\n// ============================================================================\n\nexport interface UploadedBuiFile {\n fileId: string;\n fileName: string;\n}\n\n/** `PUT` the blob to the signed URL, reporting byte-progress via `onProgress`. */\nfunction putWithProgress(\n url: string,\n file: File,\n contentType: string,\n onProgress: (percent: number) => void\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', contentType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`File storage rejected the upload (HTTP ${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Network error while uploading the file.'));\n xhr.onabort = () => reject(new Error('The upload was cancelled.'));\n xhr.send(file);\n });\n}\n\nexport interface UseBuiFileUploadResult {\n upload: (file: File) => Promise<UploadedBuiFile>;\n isUploading: boolean;\n /** 0–100 byte-progress of the signed-URL PUT. */\n progress: number;\n error: string | null;\n reset: () => void;\n}\n\n/**\n * Imperative upload hook: `await upload(file)` runs the platform 3-step flow and\n * resolves to `{ fileId, fileName }`. `fileId` is what the buildmyiq mutations accept\n * (`buiCreateCourse.coverFileId`, `buiIssueCertificate.fileId`).\n */\nexport const useBuiFileUpload = (): UseBuiFileUploadResult => {\n const { apiGatewayUrl } = useBuildmyiq();\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n const upload = useCallback(\n async (file: File): Promise<UploadedBuiFile> => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n try {\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : '';\n const mimeType = file.type || 'application/octet-stream';\n\n // 1. initiate\n const initiated = await wspaceFetch<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation BuiInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const { uploadUrl } = initiated.initiateUpload;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl) {\n throw new Error('The file service did not return an upload URL.');\n }\n\n // 2. PUT the bytes\n await putWithProgress(uploadUrl, file, mimeType, setProgress);\n\n // 3. complete\n const completed = await wspaceFetch<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null };\n }>(\n `mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId } },\n apiGatewayUrl\n );\n\n setProgress(100);\n return {\n fileId: completed.completeUpload.fileId,\n fileName: completed.completeUpload.file?.name ?? file.name,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'The file could not be uploaded.';\n setError(message);\n throw err instanceof Error ? err : new Error(message);\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { upload, isUploading, progress, error, reset };\n};\n\n// ============================================================================\n// Resolve an existing file (name + download URL) for rendering evidence links\n// ============================================================================\n\nexport interface BuiFileMeta {\n id: string;\n name: string;\n downloadUrl: string | null;\n mimeType: string;\n size: string;\n}\n\nexport const useEcoFile = (id: string | null | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: ['buildmyiq', 'file', id ?? ''],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await wspaceFetch<{ getFile: BuiFileMeta | null }>(\n `query BuiGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.getFile;\n },\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useBuiFiles.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-files-svc REUSE hooks (Integration A).\n * @module buildmyiq/hooks/useBuiFiles\n *\n * Reuses the platform file store (`wspace-files-svc`) WITHOUT any buildmyiq-svc\n * change: the buildmyiq schema already exposes `BuiLesson.mediaFileId` /\n * `BuiCertificate.fileId`; this module lets the UI actually produce a `fileId` via\n * the canonical 3-step upload flow composed into the WORKSPACE supergraph:\n * 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`\n * 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`\n * 3. `completeUpload(input: { sessionId, fileSize })` → `{ fileId }`\n * plus `getFile(id)` to resolve an existing evidence/report file's name + URL.\n *\n * Transport mirrors `useBuildmyiqApi` exactly — `graphqlFetch({ gateway:\n * 'workspace', workspaceToken: true })` — so no ApolloProvider is required.\n * These hooks live in a SEPARATE file by design; `useBuildmyiqApi.ts` is\n * left untouched.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\n\n// ============================================================================\n// Shared workspace-gateway transport (reused by the comments + tags hooks too)\n// ============================================================================\n\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache. After a write we drop those caches so the invalidation refetch that\n * follows reads fresh data (same remedy `useBuildmyiqApi` applies).\n */\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// ============================================================================\n// File upload (initiate → PUT → complete)\n// ============================================================================\n\nexport interface UploadedBuiFile {\n fileId: string;\n fileName: string;\n}\n\n/** `PUT` the blob to the signed URL, reporting byte-progress via `onProgress`. */\nfunction putWithProgress(\n url: string,\n file: File,\n contentType: string,\n onProgress: (percent: number) => void\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', contentType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`File storage rejected the upload (HTTP ${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Network error while uploading the file.'));\n xhr.onabort = () => reject(new Error('The upload was cancelled.'));\n xhr.send(file);\n });\n}\n\nexport interface UseBuiFileUploadResult {\n upload: (file: File) => Promise<UploadedBuiFile>;\n isUploading: boolean;\n /** 0–100 byte-progress of the signed-URL PUT. */\n progress: number;\n error: string | null;\n reset: () => void;\n}\n\n/**\n * Imperative upload hook: `await upload(file)` runs the platform 3-step flow and\n * resolves to `{ fileId, fileName }`. `fileId` is what the buildmyiq mutations accept\n * (`buiCreateCourse.coverFileId`, `buiIssueCertificate.fileId`).\n */\nexport const useBuiFileUpload = (): UseBuiFileUploadResult => {\n const { apiGatewayUrl } = useBuildmyiq();\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n const upload = useCallback(\n async (file: File): Promise<UploadedBuiFile> => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n try {\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : '';\n const mimeType = file.type || 'application/octet-stream';\n\n // 1. initiate\n const initiated = await wspaceFetch<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation BuiInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const { uploadUrl } = initiated.initiateUpload;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl) {\n throw new Error('The file service did not return an upload URL.');\n }\n\n // 2. PUT the bytes\n await putWithProgress(uploadUrl, file, mimeType, setProgress);\n\n // 3. complete — `CompleteUploadInput.fileSize` is a REQUIRED BigInt! in\n // the composed schema (quota tracking; must match initiateUpload's fileSize).\n const completed = await wspaceFetch<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null };\n }>(\n `mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId, fileSize: String(file.size) } },\n apiGatewayUrl\n );\n\n setProgress(100);\n return {\n fileId: completed.completeUpload.fileId,\n fileName: completed.completeUpload.file?.name ?? file.name,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'The file could not be uploaded.';\n setError(message);\n throw err instanceof Error ? err : new Error(message);\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { upload, isUploading, progress, error, reset };\n};\n\n// ============================================================================\n// Resolve an existing file (name + download URL) for rendering evidence links\n// ============================================================================\n\nexport interface BuiFileMeta {\n id: string;\n name: string;\n downloadUrl: string | null;\n mimeType: string;\n size: string;\n}\n\nexport const useEcoFile = (id: string | null | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: ['buildmyiq', 'file', id ?? ''],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await wspaceFetch<{ getFile: BuiFileMeta | null }>(\n `query BuiGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.getFile;\n },\n });\n};\n"],"mappings":"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"}
|
|
@@ -164,6 +164,22 @@ export declare const useBuiCertificates: (courseId?: string, enabled?: boolean)
|
|
|
164
164
|
export declare const useBuiMyCertificates: (courseId?: string, enabled?: boolean) => import('@tanstack/react-query').UseQueryResult<NoInfer<BuiCertificate[]>, Error>;
|
|
165
165
|
export declare const useBuiIssueCertificate: () => import('@tanstack/react-query').UseMutationResult<BuiCertificate, Error, BuiIssueCertificateInput, unknown>;
|
|
166
166
|
export declare const useBuiRevokeCertificate: () => import('@tanstack/react-query').UseMutationResult<BuiCertificate, Error, string, unknown>;
|
|
167
|
+
/**
|
|
168
|
+
* Which tutor the lesson player shows for an enrollment.
|
|
169
|
+
* - `teacher`: an educator bound a course teacher (course assistant) to the
|
|
170
|
+
* course, so the Fluidgrids workflow assistant answers from its knowledge base.
|
|
171
|
+
* - `native`: no course teacher is bound, so the built-in grounded tutor
|
|
172
|
+
* (`buiTutorAsk`) answers from the lesson content. This keeps a working tutor
|
|
173
|
+
* on every course without per-course setup.
|
|
174
|
+
*/
|
|
175
|
+
export type BuiTutorMode = 'teacher' | 'native';
|
|
176
|
+
/**
|
|
177
|
+
* Resolves the tutor mode through the learner-scoped `buiMyCourseAssistantTarget`
|
|
178
|
+
* query: it returns the course only when an enabled binding exists and fails
|
|
179
|
+
* with FAILED_PRECONDITION otherwise. Any failure resolves to `native`, because
|
|
180
|
+
* the native tutor enforces its own access checks and needs no binding.
|
|
181
|
+
*/
|
|
182
|
+
export declare const useBuiTutorMode: (enrollmentId: string | undefined) => import('@tanstack/react-query').UseQueryResult<BuiTutorMode, Error>;
|
|
167
183
|
/**
|
|
168
184
|
* The calling learner's own tutor sessions for a lesson (Type-C self-scoped
|
|
169
185
|
* query — no `learnerId` arg on the wire; identity comes from the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuildmyiqApi.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuildmyiqApi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAA0B,KAAK,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AAChG,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,cAAc,EACd,eAAe,EACf,SAAS,EACT,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,4BAA4B,EAC5B,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,0BAA0B,EAC1B,0BAA0B,EAC1B,qBAAqB,EACrB,wBAAwB,EACxB,sBAAsB,EACtB,cAAc,EACd,2BAA2B,EAE5B,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"useBuildmyiqApi.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuildmyiqApi.ts"],"names":[],"mappings":"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"}
|