@orkg/scidquest
v1.1.7
Published
SciD-QuESt: From Scientific Documents to Knowledge - Questionnaire-Based Extraction and Structuring of Knowledge in the Open Research Knowledge Graph with LLMs and Human Validation
Readme
SciD-QuESt: From Scientific Documents to Knowledge

An AI-assisted React library for structured research-paper analysis workflows.
ScidQuest provides a reusable UI and workflow foundation for extracting structured information from research papers (PDF) with template-driven forms, model-assisted suggestions, and answer verification.
Table of Contents
- SciD-QuESt: From Scientific Documents to Knowledge
- Table of Contents
- About
- Key Features
- Installation
- Styles
- Quick start
- Provider stack
- Library API reference
- ScidQuestProvider and useScidQuestContext
- LLMService, ScidQuestAdapter, and createScidQuestAdapter
- useSuggestionGenerator
- ResearchQuestionnaireApp
- TemplateQuestionnaire
- PDFUpload
- PdfViewer
- ResearchQuestionnaireWorkspaceProvider and useResearchQuestionnaireWorkspace
- ResearchQuestionnaireFieldAiWrapper
- AIAssistantButton and SuggestionBox
- QuestionnaireAIProvider and QuestionnaireAIContext
- buildQuestionDefinitions and siblingQuestionIdsFor
- Types
- Configuration
- Peer Dependencies
- Further reading
- Development
- Project Structure
- License
About
ScidQuest is distributed as an npm package: @orkg/scidquest.
It is intended for teams building research tooling that requires:
- PDF-first review workflows
- structured questionnaire-based extraction
- AI-assisted response generation and verification
- portable frontend integration with modern React stacks
Key Features
- PDF upload and viewing with zoom and page navigation
- Split-panel analysis UI for side-by-side document and questionnaire usage
- Template-driven forms for consistent data extraction
- AI suggestions for draft answers from document context
- AI verification to validate user answers against source content
- Local persistence for in-progress analysis sessions
- Embeddable workflows with custom forms while keeping PDF extraction and per-field AI
Installation
Install the package with npm:
npm install @orkg/scidquestStyles
Import the published stylesheet in your application entrypoint (or layout):
import "@orkg/scidquest/dist/contribute-standalone.css";Quick start
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.
import React, { useMemo, useState } from "react";
import { createRoot } from "react-dom/client";
import "@orkg/scidquest/dist/contribute-standalone.css";
import {
ScidQuestProvider,
QuestionnaireAIProvider,
ResearchQuestionnaireApp,
type LLMService,
type QuestionnaireTemplate,
} from "@orkg/scidquest";
const templateSpec: QuestionnaireTemplate = {
version: "1",
template: "demo",
template_id: "demo",
sections: [
{
id: "sec1",
title: "Example",
questions: [
{
id: "q1",
label: "Main contribution",
type: "text",
required: true,
},
],
},
],
};
class DemoLLM implements LLMService {
async generateText(prompt: string, options?: { systemContext?: string }) {
// Replace with your provider (OpenAI, Azure, local, etc.).
return { text: '{"suggestions":[{"rank":1,"text":"…","confidence":0.9,"evidence":[]}]}' };
}
isConfigured() {
return true;
}
}
function App() {
const llmService = useMemo(() => new DemoLLM(), []);
const [answers, setAnswers] = useState<Record<string, unknown>>({});
return (
<QuestionnaireAIProvider>
<ScidQuestProvider llmService={llmService}>
<ResearchQuestionnaireApp
templateSpec={templateSpec}
answers={answers}
setAnswers={setAnswers}
layout="split"
/>
</ScidQuestProvider>
</QuestionnaireAIProvider>
);
}
createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);Typical end-user workflow inside the default UI:
- Upload a PDF file (default max 30 MB).
- Wait for text extraction; complete template questions.
- Request AI suggestions where needed; jump to PDF evidence when offered.
- Run batch verification from the summary bar where available.
- Export or import JSON progress.
Provider stack
| Order (outer → inner) | Purpose |
| --- | --- |
| QuestionnaireAIProvider | Persists suggestion/verification history used by AIAssistantButton and related UI (localStorage key questionnaire-ai-history). |
| ScidQuestProvider | Supplies LLMService via an internal ScidQuestAdapter (Redux store included for internal widgets). |
| ResearchQuestionnaireWorkspaceProvider | Injected automatically by ResearchQuestionnaireApp around questionnaireSlot only; exposes PDF/answers/template to useResearchQuestionnaireWorkspace. |
If you only use useSuggestionGenerator and never mount AIAssistantButton / ResearchQuestionnaireFieldAiWrapper, you can omit QuestionnaireAIProvider. For the full questionnaire UI or embed wrappers, include it.
Library API reference
Everything below is imported from @orkg/scidquest unless noted.
ScidQuestProvider and useScidQuestContext
Purpose: Connects your LLMService to hooks and components that call adapter.generateSuggestions, adapter.verifyAnswer, or batch verification.
Props (ScidQuestProvider):
| Prop | Type | Description |
| --- | --- | --- |
| llmService | LLMService | Required. Your implementation of text generation and configuration checks. |
| children | ReactNode | App subtree that needs ScidQuest AI APIs. |
useScidQuestContext() returns { adapter: ScidQuestAdapter }. Throws if used outside ScidQuestProvider.
import { ScidQuestProvider, useScidQuestContext } from "@orkg/scidquest";
function Child() {
const { adapter } = useScidQuestContext();
return <button type="button" onClick={() => void adapter.isConfigured()}>Check</button>;
}
<ScidQuestProvider llmService={llmService}>
<Child />
</ScidQuestProvider>LLMService, ScidQuestAdapter, and createScidQuestAdapter
LLMService is the contract you implement: the library never holds API keys. Implement generateText(prompt, options?) returning { text, reasoning?, usage? } and isConfigured().
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).
import { createScidQuestAdapter, type LLMService } from "@orkg/scidquest";
const adapter = createScidQuestAdapter(myLlmService);
await adapter.verifyAnswer({
questionId: "q1",
questionText: "Contribution?",
currentAnswer: "They propose a new metric.",
pdfContent: extractedPlainText,
});useSuggestionGenerator
Purpose: Call the adapter’s suggestion pipeline from arbitrary UI (no PDF viewer required). Requires ScidQuestProvider.
Parameters:
| Option | Type | Description |
| --- | --- | --- |
| questionText | string | Wording shown to the model. |
| questionType | string | Template field type (e.g. text, select). |
| questionOptions | string[]? | For choice-style questions. |
| pdfContent | string? | Extracted document text; required for a successful run. |
| contextHistory | history entries | Prior suggestions for diversity (see type exports). |
| previousFeedback | array | Thumbs/comments from prior runs (shape matches suggestion feedback entries: ids, rating, optional comment, timestamp). |
| excludedSuggestionIds | Set<string> | IDs to strip from contextHistory. |
Returns: { suggestions, loading, error, rawError, generateSuggestions, clearSuggestions }.
import { useSuggestionGenerator } from "@orkg/scidquest";
function Panel({ pdfText }: { pdfText: string }) {
const { suggestions, loading, error, generateSuggestions, clearSuggestions } =
useSuggestionGenerator({
questionText: "What is the research objective?",
questionType: "text",
pdfContent: pdfText,
});
return (
<>
<button type="button" disabled={loading} onClick={() => void generateSuggestions()}>
Suggest
</button>
<button type="button" onClick={clearSuggestions}>Clear</button>
{error && <p role="alert">{error}</p>}
<ul>
{suggestions.map((s) => (
<li key={s.id}>{typeof s.text === "string" ? s.text : s.text.join(", ")}</li>
))}
</ul>
</>
);
}ResearchQuestionnaireApp
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.
Props (ResearchQuestionnaireAppProps):
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| templateSpec | QuestionnaireTemplate | (required) | Sections and question definitions. |
| pdfTextExtractor | { extractFullText(url: string): Promise<string> } | built-in | Override for bundlers or custom PDF pipelines. |
| structuredPdfExtractor | { extractStructuredDocument(text: string): Promise<StructuredDocument> } | built-in | Builds structured sections from extracted text. |
| maxPdfSizeBytes | number | 30 MB | Passed to PDFUpload. |
| layout | 'split' \| 'single' | 'split' | Desktop split panel vs stacked flow; narrow viewports behave like single. |
| showPdfViewer | boolean | true | Hide the PDF column but keep extraction if a URL exists. |
| onAnswersChange | (answers) => void | — | Fires whenever answers updates. |
| initialAnswers | Record<string, unknown> | {} | Seed state when uncontrolled. |
| answers / setAnswers | controlled pair | — | When both set, the host owns answer state. |
| questionnaireSlot | (ctx: ResearchQuestionnaireWorkspaceValue) => ReactNode | — | Embed mode: replace built-in questionnaire; ctx includes pdfContent, answers, navigation, highlights. |
| sx | MUI sx | — | Root Box styling. |
<ResearchQuestionnaireApp
templateSpec={templateSpec}
answers={answers}
setAnswers={setAnswers}
layout="split"
showPdfViewer
onAnswersChange={() => {}}
/>Embed mode (custom form, same PDF pipeline):
<ResearchQuestionnaireApp
templateSpec={templateSpec}
answers={answers}
setAnswers={setAnswers}
questionnaireSlot={(ctx) => (
<MyForm
answers={ctx.answers}
setAnswers={ctx.setAnswers}
pdfText={ctx.pdfContent}
onJumpToPage={ctx.onNavigateToPage}
/>
)}
/>See docs/EMBEDDING.md for embedding caveats (bundlers, QuestionnaireAIProvider).
TemplateQuestionnaire
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.
Requires: ScidQuestProvider (uses adapter for verification).
Main props (TemplateQuestionnaireProps):
| Prop | Type | Description |
| --- | --- | --- |
| templateSpec | QuestionnaireTemplate \| null | When null, shows a short loading placeholder. |
| answers / setAnswers | Record<string, unknown> + updater | Controlled answer map (question ids as keys; repeat sections use arrays under section ids per template). |
| pdfContent | string? | Plain text passed to AI flows. |
| structuredDocument | StructuredDocument \| null? | Improves evidence handling when present. |
| onNavigateToPage | (page: number) => void | Scroll PDF to page. |
| onHighlightsChange | highlight map | Receives highlight rects keyed by page. |
| pdfUrl / pageWidth | viewer sync | Used for evidence UI alignment. |
| pdfExtractionError / onRetryExtraction | error UX | Shown in the top bar when extraction fails. |
| aiConfig.hidden | boolean | Hides AI configuration entry points in the summary bar when set. |
| sx | MUI sx | Wrapper styling. |
<TemplateQuestionnaire
templateSpec={templateSpec}
answers={answers}
setAnswers={setAnswers}
pdfContent={extractedText}
structuredDocument={structured}
onNavigateToPage={goToPage}
onHighlightsChange={setHighlights}
pdfUrl={blobUrl}
pageWidth={pageWidth}
/>PDFUpload
Purpose: Drag-and-drop or file-picker PDF upload with type and size validation and optional clear.
Props (PDFUploadProps):
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| onFileSelected | (file: File) => void | required | Called with a validated PDF File. |
| onFileRemoved | () => void | — | Called when the user clears the selection. |
| maxSizeBytes | number | 30 MB | Rejects larger files. |
| accept | string | application/pdf | Input accept attribute. |
| disabled | boolean | false | Disables interaction. |
| sx | MUI sx | — | Root Box styling. |
const [file, setFile] = useState<File | null>(null);
<PDFUpload
maxSizeBytes={20 * 1024 * 1024}
onFileSelected={(f) => setFile(f)}
onFileRemoved={() => setFile(null)}
/>PdfViewer
Purpose: Renders PDF pages with react-pdf, zoom, page controls, optional full-document text extraction via pdfTextExtractor, and optional highlight overlays.
Props (PdfViewerProps):
| Prop | Type | Description |
| --- | --- | --- |
| refContainer | RefObject<HTMLDivElement> | Scroll container for pages (must be the element that scrolls). |
| pdfUrl | string \| null \| undefined | Blob URL or remote PDF URL. |
| pageWidth | number \| null | Pixel width of each page. |
| registerCommands | (cmds: { goToPage(n) }) => void | Optional imperative navigation hook-up. |
| onTextExtracted | (text: string) => void | Called when pdfTextExtractor finishes. |
| onExtractionError | (err: Error) => void | Extraction failure path. |
| highlights | Record<number, Array<{ left, top, width, height }>> | Yellow overlays per page number. |
| pdfTextExtractor | { extractFullText(url: string): Promise<string> } \| null | When null/omitted, no automatic extraction. |
const scrollRef = useRef<HTMLDivElement>(null);
const [url, setUrl] = useState<string | null>(null);
<Box ref={scrollRef} sx={{ height: 480, overflow: "auto" }}>
<PdfViewer
refContainer={scrollRef}
pdfUrl={url}
pageWidth={720}
pdfTextExtractor={myExtractor}
onTextExtracted={setPlainText}
onExtractionError={console.error}
highlights={highlightsByPage}
/>
</Box>The viewer configures pdfjs worker from unpkg; override at app level if your CSP requires a self-hosted worker.
ResearchQuestionnaireWorkspaceProvider and useResearchQuestionnaireWorkspace
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.
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.
useResearchQuestionnaireWorkspace() returns the value or null when used outside the provider (the field AI wrapper treats null as “no workspace” and renders children only).
import {
ResearchQuestionnaireWorkspaceProvider,
useResearchQuestionnaireWorkspace,
} from "@orkg/scidquest";
function Inner() {
const ws = useResearchQuestionnaireWorkspace();
if (!ws) return null;
return <span>{ws.pdfContent?.slice(0, 80)}…</span>;
}
<ResearchQuestionnaireWorkspaceProvider value={workspaceValue}>
<Inner />
</ResearchQuestionnaireWorkspaceProvider>ResearchQuestionnaireFieldAiWrapper
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.
Requires: Inside ResearchQuestionnaireWorkspaceProvider (automatic in embed mode under ResearchQuestionnaireApp) and ScidQuestProvider. For full AIAssistantButton behavior (history dialog, etc.), also use QuestionnaireAIProvider.
Props (ResearchQuestionnaireFieldAiWrapperProps):
| Prop | Type | Description |
| --- | --- | --- |
| children | ReactNode | Your input component. |
| questionId | string | Must match an id in templateSpec for sibling/context helpers. |
| questionText | string | Label or prompt sent to the model. |
| questionType | ResearchFieldAiQuestionType | text \| select \| multi_select \| repeat_text \| group. |
| questionOptions | string[]? | For select-style fields. |
| currentAnswer | string | String form of the value for verification. |
| onApplySuggestion | (text: string) => void | Apply chosen suggestion text into your state. |
| disableAi | boolean? | Force-disable AI chrome. |
| parentContext | ParentContext? | Optional nested/group context (see app types/context). |
| onVerificationComplete | (result: AIVerificationResult) => void | Per-field verification callback. |
If the template marks the question with disable_ai_assistant: true, the wrapper renders children only.
<ResearchQuestionnaireFieldAiWrapper
questionId="contribution"
questionText="Main contribution"
questionType="text"
currentAnswer={String(value ?? "")}
onApplySuggestion={(t) => setValue(t)}
>
<TextField fullWidth value={value} onChange={(e) => setValue(e.target.value)} />
</ResearchQuestionnaireFieldAiWrapper>AIAssistantButton and SuggestionBox
Lower-level building blocks used inside ResearchQuestionnaireFieldAiWrapper. Prefer the wrapper unless you need a fully custom layout.
AIAssistantButton (forwardRef, exposes AIAssistantButtonRef with triggerSuggestion / triggerVerification):
- Expects
ScidQuestProvider, andQuestionnaireAIProviderfor history features. - Props include
questionId,questionText,questionType,questionOptions,currentAnswer,pdfContent,structuredDocument,allAnswers,siblingQuestionIds,questionDefinitions, callbacksonSuggestionsGenerated,onError,onVerificationComplete, optionalparentContext,disabled, etc.
SuggestionBox displays ranked suggestions, loading/error states, evidence navigation, collapse, regenerate, and feedback hooks (onFeedback, onApply, onNavigateToPage, onHighlightsChange, …).
import { AIAssistantButton, SuggestionBox, QuestionnaireAIProvider } from "@orkg/scidquest";
// Typical usage is through ResearchQuestionnaireFieldAiWrapper; direct composition
// requires you to manage suggestion list state and refs yourself (see source).QuestionnaireAIProvider and QuestionnaireAIContext
Purpose: In-memory and localStorage-backed history for questionnaire AI events (QuestionnaireAIState / QuestionnaireAIHistory).
QuestionnaireAIProvider: wrap near the root of anything that mounts AIAssistantButton (or the field wrapper that uses it).
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.
import { useContext } from "react";
import { QuestionnaireAIContext, QuestionnaireAIProvider } from "@orkg/scidquest";
function HistoryLength() {
const api = useContext(QuestionnaireAIContext);
if (!api) return null;
return <span>{api.state.history.length} events</span>;
}
<QuestionnaireAIProvider>
<HistoryLength />
</QuestionnaireAIProvider>buildQuestionDefinitions and siblingQuestionIdsFor
Purpose: Template helpers for advanced AI context.
buildQuestionDefinitions(template)— flat mapquestionId → Questionincluding nestedsubquestions/item_fields.siblingQuestionIdsFor(template, questionId)— ids in the same section top-level group or same nested group; used to gather related answers for prompting.
import { buildQuestionDefinitions, siblingQuestionIdsFor } from "@orkg/scidquest";
const defs = buildQuestionDefinitions(templateSpec);
const siblings = siblingQuestionIdsFor(templateSpec, "q1");Types
The package re-exports TypeScript types for templates, LLM wiring, suggestions, and verification. Commonly used names:
| Name | Role |
| --- | --- |
| QuestionnaireTemplate, Section, Question, ValidationRule, Mapping, Link | JSON template model for forms and AI metadata. |
| LLMService, LLMGenerateTextOptions, LLMGenerateTextResponse | Bring-your-own-model contract. |
| ScidQuestAdapter | Higher-level adapter implemented by createScidQuestAdapter. |
| GenerateSuggestionsRequest, GenerateSuggestionsResponse, Suggestion, FeedbackData | Suggestion API surface. |
| VerifyAnswerRequest, AIVerificationResult | Verification API surface. |
| ResearchQuestionnaireWorkspaceValue, ResearchQuestionnaireSlotRenderProps | Alias pair for embed slot props / workspace context. |
| PDFUploadProps, PdfViewerProps, TemplateQuestionnaireProps, ResearchQuestionnaireAppProps | Component prop types. |
| ResearchFieldAiQuestionType, ResearchQuestionnaireFieldAiWrapperProps | Field wrapper typing. |
Consult dist/index.d.ts after npm run build for the authoritative export list.
Configuration
When developing this repository locally, copy environment examples if present and configure provider keys for standalone demos:
cp .env.example .envConsumer applications supply credentials inside their own LLMService implementation; the library does not read .env in the host app for you.
Peer Dependencies
ScidQuest expects the following peer dependencies in the host project:
react^18.3.1react-dom^18.3.1@mui/material^6.4.11@emotion/react^11.14.0@emotion/styled^11.14.0
Further reading
- docs/EMBEDDING.md — embed mode,
QuestionnaireAIProvider, bundler notes. - src/lib/examples/ExampleUsage.tsx — mock LLM, caching, and rate-limit patterns.
Development
Run the package locally:
npm install
npm run devBuild distributable artifacts:
npm run buildPreview the production build:
npm run previewProject Structure
ScidQuest/
├── src/
│ ├── lib/ # Published library entry (`src/lib/index.ts`)
│ ├── pages/ # Page-level views (demo app)
│ ├── components/ # Shared UI (some re-exported by lib)
│ ├── services/ # AI/provider integrations
│ ├── utils/ # PDF, suggestions, validation
│ ├── templates/ # Questionnaire templates
│ ├── store/ # Redux (used inside provider)
│ ├── context/ # Questionnaire AI context
│ ├── App.tsx
│ ├── Router.tsx
│ └── main.tsx
├── docs/
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.tsLicense
This project is released under the MIT License.
