@orkg/scidquest 1.0.4 → 1.1.0
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/README.md +533 -40
- package/dist/scidquest.es.js +17990 -17403
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,19 +8,38 @@ ScidQuest provides a reusable UI and workflow foundation for extracting structur
|
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
- [SciD-QuESt: From Scientific Documents to Knowledge](#scid-quest-from-scientific-documents-to-knowledge)
|
|
12
|
+
- [Table of Contents](#table-of-contents)
|
|
13
|
+
- [About](#about)
|
|
14
|
+
- [Key Features](#key-features)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Styles](#styles)
|
|
17
|
+
- [Quick start](#quick-start)
|
|
18
|
+
- [Provider stack](#provider-stack)
|
|
19
|
+
- [Library API reference](#library-api-reference)
|
|
20
|
+
- [ScidQuestProvider and useScidQuestContext](#scidquestprovider-and-usescidquestcontext)
|
|
21
|
+
- [LLMService, ScidQuestAdapter, and createScidQuestAdapter](#llmservice-scidquestadapter-and-createscidquestadapter)
|
|
22
|
+
- [useSuggestionGenerator](#usesuggestiongenerator)
|
|
23
|
+
- [ResearchQuestionnaireApp](#researchquestionnaireapp)
|
|
24
|
+
- [TemplateQuestionnaire](#templatequestionnaire)
|
|
25
|
+
- [PDFUpload](#pdfupload)
|
|
26
|
+
- [PdfViewer](#pdfviewer)
|
|
27
|
+
- [ResearchQuestionnaireWorkspaceProvider and useResearchQuestionnaireWorkspace](#researchquestionnaireworkspaceprovider-and-useresearchquestionnaireworkspace)
|
|
28
|
+
- [ResearchQuestionnaireFieldAiWrapper](#researchquestionnairefieldaiwrapper)
|
|
29
|
+
- [AIAssistantButton and SuggestionBox](#aiassistantbutton-and-suggestionbox)
|
|
30
|
+
- [QuestionnaireAIProvider and QuestionnaireAIContext](#questionnaireaiprovider-and-questionnaireaicontext)
|
|
31
|
+
- [buildQuestionDefinitions and siblingQuestionIdsFor](#buildquestiondefinitions-and-siblingquestionidsfor)
|
|
32
|
+
- [Types](#types)
|
|
33
|
+
- [Configuration](#configuration)
|
|
34
|
+
- [Peer Dependencies](#peer-dependencies)
|
|
35
|
+
- [Further reading](#further-reading)
|
|
36
|
+
- [Development](#development)
|
|
37
|
+
- [Project Structure](#project-structure)
|
|
38
|
+
- [License](#license)
|
|
20
39
|
|
|
21
40
|
## About
|
|
22
41
|
|
|
23
|
-
ScidQuest is distributed as an npm package: `@
|
|
42
|
+
ScidQuest is distributed as an npm package: `@orkg/scidquest`.
|
|
24
43
|
|
|
25
44
|
It is intended for teams building research tooling that requires:
|
|
26
45
|
|
|
@@ -39,7 +58,7 @@ It is intended for teams building research tooling that requires:
|
|
|
39
58
|
- **AI suggestions** for draft answers from document context
|
|
40
59
|
- **AI verification** to validate user answers against source content
|
|
41
60
|
- **Local persistence** for in-progress analysis sessions
|
|
42
|
-
- **
|
|
61
|
+
- **Embeddable workflows** with custom forms while keeping PDF extraction and per-field AI
|
|
43
62
|
|
|
44
63
|
[(back to top)](#table-of-contents)
|
|
45
64
|
|
|
@@ -48,26 +67,84 @@ It is intended for teams building research tooling that requires:
|
|
|
48
67
|
Install the package with npm:
|
|
49
68
|
|
|
50
69
|
```bash
|
|
51
|
-
npm install @
|
|
70
|
+
npm install @orkg/scidquest
|
|
52
71
|
```
|
|
53
72
|
|
|
54
|
-
|
|
73
|
+
[(back to top)](#table-of-contents)
|
|
74
|
+
|
|
75
|
+
## Styles
|
|
76
|
+
|
|
77
|
+
Import the published stylesheet in your application entrypoint (or layout):
|
|
55
78
|
|
|
56
79
|
```ts
|
|
57
|
-
import "@
|
|
80
|
+
import "@orkg/scidquest/dist/contribute-standalone.css";
|
|
58
81
|
```
|
|
59
82
|
|
|
60
83
|
[(back to top)](#table-of-contents)
|
|
61
84
|
|
|
62
|
-
## Quick
|
|
85
|
+
## Quick start
|
|
63
86
|
|
|
64
|
-
|
|
87
|
+
The smallest end-to-end flow is: implement `LLMService`, wrap the tree with `ScidQuestProvider` (and `QuestionnaireAIProvider` if you use AI chrome such as `AIAssistantButton` or `ResearchQuestionnaireFieldAiWrapper`), then render `ResearchQuestionnaireApp` with a `QuestionnaireTemplate` and either the default questionnaire or a custom `questionnaireSlot`.
|
|
65
88
|
|
|
66
89
|
```tsx
|
|
67
|
-
import React from "react";
|
|
90
|
+
import React, { useMemo, useState } from "react";
|
|
68
91
|
import { createRoot } from "react-dom/client";
|
|
69
|
-
import "@
|
|
70
|
-
import
|
|
92
|
+
import "@orkg/scidquest/dist/contribute-standalone.css";
|
|
93
|
+
import {
|
|
94
|
+
ScidQuestProvider,
|
|
95
|
+
QuestionnaireAIProvider,
|
|
96
|
+
ResearchQuestionnaireApp,
|
|
97
|
+
type LLMService,
|
|
98
|
+
type QuestionnaireTemplate,
|
|
99
|
+
} from "@orkg/scidquest";
|
|
100
|
+
|
|
101
|
+
const templateSpec: QuestionnaireTemplate = {
|
|
102
|
+
version: "1",
|
|
103
|
+
template: "demo",
|
|
104
|
+
template_id: "demo",
|
|
105
|
+
sections: [
|
|
106
|
+
{
|
|
107
|
+
id: "sec1",
|
|
108
|
+
title: "Example",
|
|
109
|
+
questions: [
|
|
110
|
+
{
|
|
111
|
+
id: "q1",
|
|
112
|
+
label: "Main contribution",
|
|
113
|
+
type: "text",
|
|
114
|
+
required: true,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
class DemoLLM implements LLMService {
|
|
122
|
+
async generateText(prompt: string, options?: { systemContext?: string }) {
|
|
123
|
+
// Replace with your provider (OpenAI, Azure, local, etc.).
|
|
124
|
+
return { text: '{"suggestions":[{"rank":1,"text":"…","confidence":0.9,"evidence":[]}]}' };
|
|
125
|
+
}
|
|
126
|
+
isConfigured() {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function App() {
|
|
132
|
+
const llmService = useMemo(() => new DemoLLM(), []);
|
|
133
|
+
const [answers, setAnswers] = useState<Record<string, unknown>>({});
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<QuestionnaireAIProvider>
|
|
137
|
+
<ScidQuestProvider llmService={llmService}>
|
|
138
|
+
<ResearchQuestionnaireApp
|
|
139
|
+
templateSpec={templateSpec}
|
|
140
|
+
answers={answers}
|
|
141
|
+
setAnswers={setAnswers}
|
|
142
|
+
layout="split"
|
|
143
|
+
/>
|
|
144
|
+
</ScidQuestProvider>
|
|
145
|
+
</QuestionnaireAIProvider>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
71
148
|
|
|
72
149
|
createRoot(document.getElementById("root")!).render(
|
|
73
150
|
<React.StrictMode>
|
|
@@ -76,29 +153,436 @@ createRoot(document.getElementById("root")!).render(
|
|
|
76
153
|
);
|
|
77
154
|
```
|
|
78
155
|
|
|
79
|
-
Typical end-user workflow inside
|
|
156
|
+
Typical end-user workflow inside the default UI:
|
|
157
|
+
|
|
158
|
+
1. Upload a PDF file (default max 30 MB).
|
|
159
|
+
2. Wait for text extraction; complete template questions.
|
|
160
|
+
3. Request AI suggestions where needed; jump to PDF evidence when offered.
|
|
161
|
+
4. Run batch verification from the summary bar where available.
|
|
162
|
+
5. Export or import JSON progress.
|
|
163
|
+
|
|
164
|
+
[(back to top)](#table-of-contents)
|
|
165
|
+
|
|
166
|
+
## Provider stack
|
|
167
|
+
|
|
168
|
+
| Order (outer → inner) | Purpose |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| `QuestionnaireAIProvider` | Persists suggestion/verification **history** used by `AIAssistantButton` and related UI (localStorage key `questionnaire-ai-history`). |
|
|
171
|
+
| `ScidQuestProvider` | Supplies `LLMService` via an internal `ScidQuestAdapter` (Redux store included for internal widgets). |
|
|
172
|
+
| `ResearchQuestionnaireWorkspaceProvider` | Injected **automatically** by `ResearchQuestionnaireApp` around `questionnaireSlot` only; exposes PDF/answers/template to `useResearchQuestionnaireWorkspace`. |
|
|
173
|
+
|
|
174
|
+
If you only use `useSuggestionGenerator` and never mount `AIAssistantButton` / `ResearchQuestionnaireFieldAiWrapper`, you can omit `QuestionnaireAIProvider`. For the full questionnaire UI or embed wrappers, include it.
|
|
175
|
+
|
|
176
|
+
[(back to top)](#table-of-contents)
|
|
177
|
+
|
|
178
|
+
## Library API reference
|
|
179
|
+
|
|
180
|
+
Everything below is imported from `@orkg/scidquest` unless noted.
|
|
181
|
+
|
|
182
|
+
### ScidQuestProvider and useScidQuestContext
|
|
183
|
+
|
|
184
|
+
**Purpose:** Connects your `LLMService` to hooks and components that call `adapter.generateSuggestions`, `adapter.verifyAnswer`, or batch verification.
|
|
185
|
+
|
|
186
|
+
**Props (`ScidQuestProvider`):**
|
|
187
|
+
|
|
188
|
+
| Prop | Type | Description |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| `llmService` | `LLMService` | Required. Your implementation of text generation and configuration checks. |
|
|
191
|
+
| `children` | `ReactNode` | App subtree that needs ScidQuest AI APIs. |
|
|
192
|
+
|
|
193
|
+
**`useScidQuestContext()`** returns `{ adapter: ScidQuestAdapter }`. Throws if used outside `ScidQuestProvider`.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { ScidQuestProvider, useScidQuestContext } from "@orkg/scidquest";
|
|
197
|
+
|
|
198
|
+
function Child() {
|
|
199
|
+
const { adapter } = useScidQuestContext();
|
|
200
|
+
return <button type="button" onClick={() => void adapter.isConfigured()}>Check</button>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
<ScidQuestProvider llmService={llmService}>
|
|
204
|
+
<Child />
|
|
205
|
+
</ScidQuestProvider>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
[(back to top)](#table-of-contents)
|
|
209
|
+
|
|
210
|
+
### LLMService, ScidQuestAdapter, and createScidQuestAdapter
|
|
211
|
+
|
|
212
|
+
**`LLMService`** is the contract you implement: the library never holds API keys. Implement `generateText(prompt, options?)` returning `{ text, reasoning?, usage? }` and `isConfigured()`.
|
|
213
|
+
|
|
214
|
+
**`createScidQuestAdapter(llmService)`** builds a **`ScidQuestAdapter`**: `generateSuggestions`, `verifyAnswer`, optional `verifyAnswersBatch`, and `isConfigured`. Advanced integrations can implement `ScidQuestAdapter` directly instead of using `createScidQuestAdapter`, then you would need a custom way to inject it (the shipped `ScidQuestProvider` always uses `createScidQuestAdapter`).
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { createScidQuestAdapter, type LLMService } from "@orkg/scidquest";
|
|
218
|
+
|
|
219
|
+
const adapter = createScidQuestAdapter(myLlmService);
|
|
220
|
+
await adapter.verifyAnswer({
|
|
221
|
+
questionId: "q1",
|
|
222
|
+
questionText: "Contribution?",
|
|
223
|
+
currentAnswer: "They propose a new metric.",
|
|
224
|
+
pdfContent: extractedPlainText,
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
[(back to top)](#table-of-contents)
|
|
229
|
+
|
|
230
|
+
### useSuggestionGenerator
|
|
231
|
+
|
|
232
|
+
**Purpose:** Call the adapter’s suggestion pipeline from arbitrary UI (no PDF viewer required). Requires `ScidQuestProvider`.
|
|
233
|
+
|
|
234
|
+
**Parameters:**
|
|
235
|
+
|
|
236
|
+
| Option | Type | Description |
|
|
237
|
+
| --- | --- | --- |
|
|
238
|
+
| `questionText` | `string` | Wording shown to the model. |
|
|
239
|
+
| `questionType` | `string` | Template field type (e.g. `text`, `select`). |
|
|
240
|
+
| `questionOptions` | `string[]?` | For choice-style questions. |
|
|
241
|
+
| `pdfContent` | `string?` | Extracted document text; required for a successful run. |
|
|
242
|
+
| `contextHistory` | history entries | Prior suggestions for diversity (see type exports). |
|
|
243
|
+
| `previousFeedback` | array | Thumbs/comments from prior runs (shape matches suggestion feedback entries: ids, rating, optional comment, timestamp). |
|
|
244
|
+
| `excludedSuggestionIds` | `Set<string>` | IDs to strip from `contextHistory`. |
|
|
245
|
+
|
|
246
|
+
**Returns:** `{ suggestions, loading, error, rawError, generateSuggestions, clearSuggestions }`.
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import { useSuggestionGenerator } from "@orkg/scidquest";
|
|
250
|
+
|
|
251
|
+
function Panel({ pdfText }: { pdfText: string }) {
|
|
252
|
+
const { suggestions, loading, error, generateSuggestions, clearSuggestions } =
|
|
253
|
+
useSuggestionGenerator({
|
|
254
|
+
questionText: "What is the research objective?",
|
|
255
|
+
questionType: "text",
|
|
256
|
+
pdfContent: pdfText,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<>
|
|
261
|
+
<button type="button" disabled={loading} onClick={() => void generateSuggestions()}>
|
|
262
|
+
Suggest
|
|
263
|
+
</button>
|
|
264
|
+
<button type="button" onClick={clearSuggestions}>Clear</button>
|
|
265
|
+
{error && <p role="alert">{error}</p>}
|
|
266
|
+
<ul>
|
|
267
|
+
{suggestions.map((s) => (
|
|
268
|
+
<li key={s.id}>{typeof s.text === "string" ? s.text : s.text.join(", ")}</li>
|
|
269
|
+
))}
|
|
270
|
+
</ul>
|
|
271
|
+
</>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
[(back to top)](#table-of-contents)
|
|
277
|
+
|
|
278
|
+
### ResearchQuestionnaireApp
|
|
279
|
+
|
|
280
|
+
**Purpose:** Orchestrates PDF upload, `PdfViewer`, plain-text extraction, optional structured document extraction, split or single-column layout, and either the built-in `TemplateQuestionnaire` or a custom **`questionnaireSlot`**.
|
|
281
|
+
|
|
282
|
+
**Props (`ResearchQuestionnaireAppProps`):**
|
|
283
|
+
|
|
284
|
+
| Prop | Type | Default | Description |
|
|
285
|
+
| --- | --- | --- | --- |
|
|
286
|
+
| `templateSpec` | `QuestionnaireTemplate` | (required) | Sections and question definitions. |
|
|
287
|
+
| `pdfTextExtractor` | `{ extractFullText(url: string): Promise<string> }` | built-in | Override for bundlers or custom PDF pipelines. |
|
|
288
|
+
| `structuredPdfExtractor` | `{ extractStructuredDocument(text: string): Promise<StructuredDocument> }` | built-in | Builds structured sections from extracted text. |
|
|
289
|
+
| `maxPdfSizeBytes` | `number` | 30 MB | Passed to `PDFUpload`. |
|
|
290
|
+
| `layout` | `'split' \| 'single'` | `'split'` | Desktop split panel vs stacked flow; narrow viewports behave like single. |
|
|
291
|
+
| `showPdfViewer` | `boolean` | `true` | Hide the PDF column but keep extraction if a URL exists. |
|
|
292
|
+
| `onAnswersChange` | `(answers) => void` | — | Fires whenever `answers` updates. |
|
|
293
|
+
| `initialAnswers` | `Record<string, unknown>` | `{}` | Seed state when **uncontrolled**. |
|
|
294
|
+
| `answers` / `setAnswers` | controlled pair | — | When both set, the host owns answer state. |
|
|
295
|
+
| `questionnaireSlot` | `(ctx: ResearchQuestionnaireWorkspaceValue) => ReactNode` | — | **Embed mode:** replace built-in questionnaire; `ctx` includes `pdfContent`, `answers`, navigation, highlights. |
|
|
296
|
+
| `sx` | MUI `sx` | — | Root `Box` styling. |
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
<ResearchQuestionnaireApp
|
|
300
|
+
templateSpec={templateSpec}
|
|
301
|
+
answers={answers}
|
|
302
|
+
setAnswers={setAnswers}
|
|
303
|
+
layout="split"
|
|
304
|
+
showPdfViewer
|
|
305
|
+
onAnswersChange={(next) => console.log("answers", next)}
|
|
306
|
+
/>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Embed mode** (custom form, same PDF pipeline):
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
<ResearchQuestionnaireApp
|
|
313
|
+
templateSpec={templateSpec}
|
|
314
|
+
answers={answers}
|
|
315
|
+
setAnswers={setAnswers}
|
|
316
|
+
questionnaireSlot={(ctx) => (
|
|
317
|
+
<MyForm
|
|
318
|
+
answers={ctx.answers}
|
|
319
|
+
setAnswers={ctx.setAnswers}
|
|
320
|
+
pdfText={ctx.pdfContent}
|
|
321
|
+
onJumpToPage={ctx.onNavigateToPage}
|
|
322
|
+
/>
|
|
323
|
+
)}
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
See [docs/EMBEDDING.md](./docs/EMBEDDING.md) for embedding caveats (bundlers, `QuestionnaireAIProvider`).
|
|
328
|
+
|
|
329
|
+
[(back to top)](#table-of-contents)
|
|
330
|
+
|
|
331
|
+
### TemplateQuestionnaire
|
|
332
|
+
|
|
333
|
+
**Purpose:** Full questionnaire UI: section accordions, validation, export/import JSON, localStorage autosave, AI verification batch from the summary bar, and integration with PDF navigation and highlights when props are passed.
|
|
334
|
+
|
|
335
|
+
**Requires:** `ScidQuestProvider` (uses `adapter` for verification).
|
|
336
|
+
|
|
337
|
+
**Main props (`TemplateQuestionnaireProps`):**
|
|
338
|
+
|
|
339
|
+
| Prop | Type | Description |
|
|
340
|
+
| --- | --- | --- |
|
|
341
|
+
| `templateSpec` | `QuestionnaireTemplate \| null` | When `null`, shows a short loading placeholder. |
|
|
342
|
+
| `answers` / `setAnswers` | `Record<string, unknown>` + updater | Controlled answer map (question ids as keys; repeat sections use arrays under section ids per template). |
|
|
343
|
+
| `pdfContent` | `string?` | Plain text passed to AI flows. |
|
|
344
|
+
| `structuredDocument` | `StructuredDocument \| null?` | Improves evidence handling when present. |
|
|
345
|
+
| `onNavigateToPage` | `(page: number) => void` | Scroll PDF to page. |
|
|
346
|
+
| `onHighlightsChange` | highlight map | Receives highlight rects keyed by page. |
|
|
347
|
+
| `pdfUrl` / `pageWidth` | viewer sync | Used for evidence UI alignment. |
|
|
348
|
+
| `pdfExtractionError` / `onRetryExtraction` | error UX | Shown in the top bar when extraction fails. |
|
|
349
|
+
| `aiConfig.hidden` | `boolean` | Hides AI configuration entry points in the summary bar when set. |
|
|
350
|
+
| `sx` | MUI `sx` | Wrapper styling. |
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
<TemplateQuestionnaire
|
|
354
|
+
templateSpec={templateSpec}
|
|
355
|
+
answers={answers}
|
|
356
|
+
setAnswers={setAnswers}
|
|
357
|
+
pdfContent={extractedText}
|
|
358
|
+
structuredDocument={structured}
|
|
359
|
+
onNavigateToPage={goToPage}
|
|
360
|
+
onHighlightsChange={setHighlights}
|
|
361
|
+
pdfUrl={blobUrl}
|
|
362
|
+
pageWidth={pageWidth}
|
|
363
|
+
/>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
[(back to top)](#table-of-contents)
|
|
367
|
+
|
|
368
|
+
### PDFUpload
|
|
369
|
+
|
|
370
|
+
**Purpose:** Drag-and-drop or file-picker PDF upload with type and size validation and optional clear.
|
|
371
|
+
|
|
372
|
+
**Props (`PDFUploadProps`):**
|
|
373
|
+
|
|
374
|
+
| Prop | Type | Default | Description |
|
|
375
|
+
| --- | --- | --- | --- |
|
|
376
|
+
| `onFileSelected` | `(file: File) => void` | required | Called with a validated PDF `File`. |
|
|
377
|
+
| `onFileRemoved` | `() => void` | — | Called when the user clears the selection. |
|
|
378
|
+
| `maxSizeBytes` | `number` | 30 MB | Rejects larger files. |
|
|
379
|
+
| `accept` | `string` | `application/pdf` | Input `accept` attribute. |
|
|
380
|
+
| `disabled` | `boolean` | `false` | Disables interaction. |
|
|
381
|
+
| `sx` | MUI `sx` | — | Root `Box` styling. |
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
const [file, setFile] = useState<File | null>(null);
|
|
385
|
+
|
|
386
|
+
<PDFUpload
|
|
387
|
+
maxSizeBytes={20 * 1024 * 1024}
|
|
388
|
+
onFileSelected={(f) => setFile(f)}
|
|
389
|
+
onFileRemoved={() => setFile(null)}
|
|
390
|
+
/>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
[(back to top)](#table-of-contents)
|
|
394
|
+
|
|
395
|
+
### PdfViewer
|
|
396
|
+
|
|
397
|
+
**Purpose:** Renders PDF pages with `react-pdf`, zoom, page controls, optional **full-document text extraction** via `pdfTextExtractor`, and optional highlight overlays.
|
|
398
|
+
|
|
399
|
+
**Props (`PdfViewerProps`):**
|
|
400
|
+
|
|
401
|
+
| Prop | Type | Description |
|
|
402
|
+
| --- | --- | --- |
|
|
403
|
+
| `refContainer` | `RefObject<HTMLDivElement>` | Scroll container for pages (must be the element that scrolls). |
|
|
404
|
+
| `pdfUrl` | `string \| null \| undefined` | Blob URL or remote PDF URL. |
|
|
405
|
+
| `pageWidth` | `number \| null` | Pixel width of each page. |
|
|
406
|
+
| `registerCommands` | `(cmds: { goToPage(n) }) => void` | Optional imperative navigation hook-up. |
|
|
407
|
+
| `onTextExtracted` | `(text: string) => void` | Called when `pdfTextExtractor` finishes. |
|
|
408
|
+
| `onExtractionError` | `(err: Error) => void` | Extraction failure path. |
|
|
409
|
+
| `highlights` | `Record<number, Array<{ left, top, width, height }>>` | Yellow overlays per page number. |
|
|
410
|
+
| `pdfTextExtractor` | `{ extractFullText(url: string): Promise<string> } \| null` | When null/omitted, no automatic extraction. |
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
414
|
+
const [url, setUrl] = useState<string | null>(null);
|
|
415
|
+
|
|
416
|
+
<Box ref={scrollRef} sx={{ height: 480, overflow: "auto" }}>
|
|
417
|
+
<PdfViewer
|
|
418
|
+
refContainer={scrollRef}
|
|
419
|
+
pdfUrl={url}
|
|
420
|
+
pageWidth={720}
|
|
421
|
+
pdfTextExtractor={myExtractor}
|
|
422
|
+
onTextExtracted={setPlainText}
|
|
423
|
+
onExtractionError={console.error}
|
|
424
|
+
highlights={highlightsByPage}
|
|
425
|
+
/>
|
|
426
|
+
</Box>
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
The viewer configures `pdfjs` worker from unpkg; override at app level if your CSP requires a self-hosted worker.
|
|
430
|
+
|
|
431
|
+
[(back to top)](#table-of-contents)
|
|
432
|
+
|
|
433
|
+
### ResearchQuestionnaireWorkspaceProvider and useResearchQuestionnaireWorkspace
|
|
434
|
+
|
|
435
|
+
**Purpose:** Exposes the **workspace value** (`ResearchQuestionnaireWorkspaceValue`) to descendants: template, answers, extracted PDF text, structured document, PDF URL, `pageWidth`, navigation and highlight callbacks, and extraction error/retry.
|
|
436
|
+
|
|
437
|
+
`ResearchQuestionnaireApp` wraps **`questionnaireSlot`** output with `ResearchQuestionnaireWorkspaceProvider`. When you render `TemplateQuestionnaire` alone (without the app), no workspace context is present unless you add the provider yourself.
|
|
438
|
+
|
|
439
|
+
**`useResearchQuestionnaireWorkspace()`** returns the value **or `null`** when used outside the provider (the field AI wrapper treats `null` as “no workspace” and renders children only).
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
import {
|
|
443
|
+
ResearchQuestionnaireWorkspaceProvider,
|
|
444
|
+
useResearchQuestionnaireWorkspace,
|
|
445
|
+
} from "@orkg/scidquest";
|
|
446
|
+
|
|
447
|
+
function Inner() {
|
|
448
|
+
const ws = useResearchQuestionnaireWorkspace();
|
|
449
|
+
if (!ws) return null;
|
|
450
|
+
return <span>{ws.pdfContent?.slice(0, 80)}…</span>;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
<ResearchQuestionnaireWorkspaceProvider value={workspaceValue}>
|
|
454
|
+
<Inner />
|
|
455
|
+
</ResearchQuestionnaireWorkspaceProvider>
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
[(back to top)](#table-of-contents)
|
|
459
|
+
|
|
460
|
+
### ResearchQuestionnaireFieldAiWrapper
|
|
461
|
+
|
|
462
|
+
**Purpose:** Wraps a **single host-rendered control** with the same AI affordances as built-in fields: suggestion generation, verification entry via `AIAssistantButton`, and `SuggestionBox` wired to workspace navigation/highlights.
|
|
463
|
+
|
|
464
|
+
**Requires:** Inside `ResearchQuestionnaireWorkspaceProvider` (automatic in embed mode under `ResearchQuestionnaireApp`) and `ScidQuestProvider`. For full `AIAssistantButton` behavior (history dialog, etc.), also use **`QuestionnaireAIProvider`**.
|
|
465
|
+
|
|
466
|
+
**Props (`ResearchQuestionnaireFieldAiWrapperProps`):**
|
|
467
|
+
|
|
468
|
+
| Prop | Type | Description |
|
|
469
|
+
| --- | --- | --- |
|
|
470
|
+
| `children` | `ReactNode` | Your input component. |
|
|
471
|
+
| `questionId` | `string` | Must match an id in `templateSpec` for sibling/context helpers. |
|
|
472
|
+
| `questionText` | `string` | Label or prompt sent to the model. |
|
|
473
|
+
| `questionType` | `ResearchFieldAiQuestionType` | `text \| select \| multi_select \| repeat_text \| group`. |
|
|
474
|
+
| `questionOptions` | `string[]?` | For select-style fields. |
|
|
475
|
+
| `currentAnswer` | `string` | String form of the value for verification. |
|
|
476
|
+
| `onApplySuggestion` | `(text: string) => void` | Apply chosen suggestion text into your state. |
|
|
477
|
+
| `disableAi` | `boolean?` | Force-disable AI chrome. |
|
|
478
|
+
| `parentContext` | `ParentContext?` | Optional nested/group context (see app `types/context`). |
|
|
479
|
+
| `onVerificationComplete` | `(result: AIVerificationResult) => void` | Per-field verification callback. |
|
|
480
|
+
|
|
481
|
+
If the template marks the question with `disable_ai_assistant: true`, the wrapper renders **`children` only**.
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
<ResearchQuestionnaireFieldAiWrapper
|
|
485
|
+
questionId="contribution"
|
|
486
|
+
questionText="Main contribution"
|
|
487
|
+
questionType="text"
|
|
488
|
+
currentAnswer={String(value ?? "")}
|
|
489
|
+
onApplySuggestion={(t) => setValue(t)}
|
|
490
|
+
>
|
|
491
|
+
<TextField fullWidth value={value} onChange={(e) => setValue(e.target.value)} />
|
|
492
|
+
</ResearchQuestionnaireFieldAiWrapper>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
[(back to top)](#table-of-contents)
|
|
496
|
+
|
|
497
|
+
### AIAssistantButton and SuggestionBox
|
|
498
|
+
|
|
499
|
+
**Lower-level building blocks** used inside `ResearchQuestionnaireFieldAiWrapper`. Prefer the wrapper unless you need a fully custom layout.
|
|
500
|
+
|
|
501
|
+
**`AIAssistantButton`** (forwardRef, exposes `AIAssistantButtonRef` with `triggerSuggestion` / `triggerVerification`):
|
|
502
|
+
|
|
503
|
+
- Expects `ScidQuestProvider`, and **`QuestionnaireAIProvider`** for history features.
|
|
504
|
+
- Props include `questionId`, `questionText`, `questionType`, `questionOptions`, `currentAnswer`, `pdfContent`, `structuredDocument`, `allAnswers`, `siblingQuestionIds`, `questionDefinitions`, callbacks `onSuggestionsGenerated`, `onError`, `onVerificationComplete`, optional `parentContext`, `disabled`, etc.
|
|
505
|
+
|
|
506
|
+
**`SuggestionBox`** displays ranked suggestions, loading/error states, evidence navigation, collapse, regenerate, and feedback hooks (`onFeedback`, `onApply`, `onNavigateToPage`, `onHighlightsChange`, …).
|
|
507
|
+
|
|
508
|
+
```tsx
|
|
509
|
+
import { AIAssistantButton, SuggestionBox, QuestionnaireAIProvider } from "@orkg/scidquest";
|
|
510
|
+
|
|
511
|
+
// Typical usage is through ResearchQuestionnaireFieldAiWrapper; direct composition
|
|
512
|
+
// requires you to manage suggestion list state and refs yourself (see source).
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
[(back to top)](#table-of-contents)
|
|
516
|
+
|
|
517
|
+
### QuestionnaireAIProvider and QuestionnaireAIContext
|
|
518
|
+
|
|
519
|
+
**Purpose:** In-memory and **localStorage**-backed history for questionnaire AI events (`QuestionnaireAIState` / `QuestionnaireAIHistory`).
|
|
520
|
+
|
|
521
|
+
**`QuestionnaireAIProvider`:** wrap near the root of anything that mounts `AIAssistantButton` (or the field wrapper that uses it).
|
|
522
|
+
|
|
523
|
+
**`QuestionnaireAIContext`:** the React context object. The package does **not** export the `useQuestionnaireAI` hook; use `useContext(QuestionnaireAIContext)` if you need programmatic access to `addToHistory`, `getHistoryByQuestion`, `clearHistory`, etc., and guard for `undefined`.
|
|
524
|
+
|
|
525
|
+
```tsx
|
|
526
|
+
import { useContext } from "react";
|
|
527
|
+
import { QuestionnaireAIContext, QuestionnaireAIProvider } from "@orkg/scidquest";
|
|
528
|
+
|
|
529
|
+
function HistoryLength() {
|
|
530
|
+
const api = useContext(QuestionnaireAIContext);
|
|
531
|
+
if (!api) return null;
|
|
532
|
+
return <span>{api.state.history.length} events</span>;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
<QuestionnaireAIProvider>
|
|
536
|
+
<HistoryLength />
|
|
537
|
+
</QuestionnaireAIProvider>
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
[(back to top)](#table-of-contents)
|
|
541
|
+
|
|
542
|
+
### buildQuestionDefinitions and siblingQuestionIdsFor
|
|
543
|
+
|
|
544
|
+
**Purpose:** Template helpers for advanced AI context.
|
|
545
|
+
|
|
546
|
+
- **`buildQuestionDefinitions(template)`** — flat map `questionId → Question` including nested `subquestions` / `item_fields`.
|
|
547
|
+
- **`siblingQuestionIdsFor(template, questionId)`** — ids in the same section top-level group or same nested group; used to gather related answers for prompting.
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
import { buildQuestionDefinitions, siblingQuestionIdsFor } from "@orkg/scidquest";
|
|
551
|
+
|
|
552
|
+
const defs = buildQuestionDefinitions(templateSpec);
|
|
553
|
+
const siblings = siblingQuestionIdsFor(templateSpec, "q1");
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
[(back to top)](#table-of-contents)
|
|
557
|
+
|
|
558
|
+
### Types
|
|
559
|
+
|
|
560
|
+
The package re-exports TypeScript types for templates, LLM wiring, suggestions, and verification. Commonly used names:
|
|
80
561
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
562
|
+
| Name | Role |
|
|
563
|
+
| --- | --- |
|
|
564
|
+
| `QuestionnaireTemplate`, `Section`, `Question`, `ValidationRule`, `Mapping`, `Link` | JSON template model for forms and AI metadata. |
|
|
565
|
+
| `LLMService`, `LLMGenerateTextOptions`, `LLMGenerateTextResponse` | Bring-your-own-model contract. |
|
|
566
|
+
| `ScidQuestAdapter` | Higher-level adapter implemented by `createScidQuestAdapter`. |
|
|
567
|
+
| `GenerateSuggestionsRequest`, `GenerateSuggestionsResponse`, `Suggestion`, `FeedbackData` | Suggestion API surface. |
|
|
568
|
+
| `VerifyAnswerRequest`, `AIVerificationResult` | Verification API surface. |
|
|
569
|
+
| `ResearchQuestionnaireWorkspaceValue`, `ResearchQuestionnaireSlotRenderProps` | Alias pair for embed slot props / workspace context. |
|
|
570
|
+
| `PDFUploadProps`, `PdfViewerProps`, `TemplateQuestionnaireProps`, `ResearchQuestionnaireAppProps` | Component prop types. |
|
|
571
|
+
| `ResearchFieldAiQuestionType`, `ResearchQuestionnaireFieldAiWrapperProps` | Field wrapper typing. |
|
|
572
|
+
|
|
573
|
+
Consult `dist/index.d.ts` after `npm run build` for the authoritative export list.
|
|
86
574
|
|
|
87
575
|
[(back to top)](#table-of-contents)
|
|
88
576
|
|
|
89
577
|
## Configuration
|
|
90
578
|
|
|
91
|
-
|
|
579
|
+
When developing **this** repository locally, copy environment examples if present and configure provider keys for standalone demos:
|
|
92
580
|
|
|
93
581
|
```bash
|
|
94
582
|
cp .env.example .env
|
|
95
583
|
```
|
|
96
584
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- OpenAI
|
|
100
|
-
- Groq
|
|
101
|
-
- Mistral
|
|
585
|
+
Consumer applications supply credentials inside their own **`LLMService`** implementation; the library does not read `.env` in the host app for you.
|
|
102
586
|
|
|
103
587
|
[(back to top)](#table-of-contents)
|
|
104
588
|
|
|
@@ -114,6 +598,13 @@ ScidQuest expects the following peer dependencies in the host project:
|
|
|
114
598
|
|
|
115
599
|
[(back to top)](#table-of-contents)
|
|
116
600
|
|
|
601
|
+
## Further reading
|
|
602
|
+
|
|
603
|
+
- [docs/EMBEDDING.md](./docs/EMBEDDING.md) — embed mode, `QuestionnaireAIProvider`, bundler notes.
|
|
604
|
+
- [src/lib/examples/ExampleUsage.tsx](./src/lib/examples/ExampleUsage.tsx) — mock LLM, caching, and rate-limit patterns.
|
|
605
|
+
|
|
606
|
+
[(back to top)](#table-of-contents)
|
|
607
|
+
|
|
117
608
|
## Development
|
|
118
609
|
|
|
119
610
|
Run the package locally:
|
|
@@ -142,16 +633,18 @@ npm run preview
|
|
|
142
633
|
```text
|
|
143
634
|
ScidQuest/
|
|
144
635
|
├── src/
|
|
145
|
-
│ ├──
|
|
146
|
-
│ ├──
|
|
147
|
-
│ ├──
|
|
148
|
-
│ ├──
|
|
636
|
+
│ ├── lib/ # Published library entry (`src/lib/index.ts`)
|
|
637
|
+
│ ├── pages/ # Page-level views (demo app)
|
|
638
|
+
│ ├── components/ # Shared UI (some re-exported by lib)
|
|
639
|
+
│ ├── services/ # AI/provider integrations
|
|
640
|
+
│ ├── utils/ # PDF, suggestions, validation
|
|
149
641
|
│ ├── templates/ # Questionnaire templates
|
|
150
|
-
│ ├── store/ #
|
|
151
|
-
│ ├── context/ #
|
|
152
|
-
│ ├── App.tsx
|
|
153
|
-
│ ├── Router.tsx
|
|
154
|
-
│ └── main.tsx
|
|
642
|
+
│ ├── store/ # Redux (used inside provider)
|
|
643
|
+
│ ├── context/ # Questionnaire AI context
|
|
644
|
+
│ ├── App.tsx
|
|
645
|
+
│ ├── Router.tsx
|
|
646
|
+
│ └── main.tsx
|
|
647
|
+
├── docs/
|
|
155
648
|
├── index.html
|
|
156
649
|
├── package.json
|
|
157
650
|
├── tsconfig.json
|