@dataimago/interview 0.2.0-alpha.0 → 0.2.0-alpha.2

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/index.cjs CHANGED
@@ -23,7 +23,10 @@ __export(index_exports, {
23
23
  AnswerSidebar: () => AnswerSidebar,
24
24
  ProgressBar: () => ProgressBar,
25
25
  QuestionCard: () => QuestionCard,
26
- ReviewSummary: () => ReviewSummary
26
+ ReviewSummary: () => ReviewSummary,
27
+ isAnswered: () => isAnswered,
28
+ summarizeLong: () => summarizeLong,
29
+ summarizeShort: () => summarizeShort
27
30
  });
28
31
  module.exports = __toCommonJS(index_exports);
29
32
 
@@ -69,6 +72,28 @@ function ProgressBar({ current, total, estimatedMinutesRemaining }) {
69
72
  // src/QuestionCard.tsx
70
73
  var import_react = require("react");
71
74
  var import_jsx_runtime2 = require("react/jsx-runtime");
75
+ function defaultValueFor(question) {
76
+ switch (question.inputType) {
77
+ case "multi-select":
78
+ case "list":
79
+ return [];
80
+ case "composite":
81
+ return {};
82
+ case "repeater":
83
+ case "file-upload":
84
+ return [];
85
+ default:
86
+ return "";
87
+ }
88
+ }
89
+ function canAdvanceWith(question, value) {
90
+ if (!question.required) return true;
91
+ if (value === void 0 || value === null) return false;
92
+ if (typeof value === "string") return value.trim().length > 0;
93
+ if (Array.isArray(value)) return value.length > 0;
94
+ if (typeof value === "object") return Object.keys(value).length > 0;
95
+ return true;
96
+ }
72
97
  function QuestionCard({
73
98
  question,
74
99
  existingAnswer,
@@ -79,113 +104,28 @@ function QuestionCard({
79
104
  isLast
80
105
  }) {
81
106
  const [localValue, setLocalValue] = (0, import_react.useState)(
82
- existingAnswer ?? (question.inputType === "multi-select" || question.inputType === "list" ? [] : "")
107
+ existingAnswer ?? defaultValueFor(question)
83
108
  );
84
109
  (0, import_react.useEffect)(() => {
85
- const current = existingAnswer ?? (question.inputType === "multi-select" || question.inputType === "list" ? [] : "");
86
- setLocalValue(current);
110
+ setLocalValue(existingAnswer ?? defaultValueFor(question));
87
111
  }, [question.id, existingAnswer, question.inputType]);
88
112
  const handleSubmit = (e) => {
89
113
  e.preventDefault();
90
114
  onAnswerSubmit(question.id, localValue);
91
115
  onNext();
92
116
  };
93
- const canAdvance = question.required ? Array.isArray(localValue) ? localValue.length > 0 : localValue.trim().length > 0 : true;
117
+ const canAdvance = canAdvanceWith(question, localValue);
94
118
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, className: "animate-slide-up", children: [
95
119
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-3xl font-medium text-stone-900 sm:text-4xl", children: question.prompt }),
96
120
  question.subprompt && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-4 text-lg text-stone-700", children: question.subprompt }),
97
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-8", children: [
98
- question.inputType === "short-text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
99
- "input",
100
- {
101
- type: "text",
102
- value: localValue,
103
- onChange: (e) => setLocalValue(e.target.value),
104
- autoFocus: true,
105
- className: "w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none",
106
- placeholder: "Type your answer\u2026"
107
- }
108
- ),
109
- question.inputType === "long-text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
110
- "textarea",
111
- {
112
- value: localValue,
113
- onChange: (e) => setLocalValue(e.target.value),
114
- autoFocus: true,
115
- rows: 5,
116
- className: "w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none",
117
- placeholder: "Type your answer\u2026"
118
- }
119
- ),
120
- question.inputType === "select" && question.options && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-3", role: "radiogroup", "aria-labelledby": `q-${question.id}`, children: question.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
121
- "label",
122
- {
123
- className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors
124
- ${localValue === opt.value ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
125
- children: [
126
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
127
- "input",
128
- {
129
- type: "radio",
130
- name: question.id,
131
- value: opt.value,
132
- checked: localValue === opt.value,
133
- onChange: (e) => setLocalValue(e.target.value),
134
- className: "mt-1 h-4 w-4 text-forest-700"
135
- }
136
- ),
137
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
138
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium text-stone-900", children: opt.label }),
139
- opt.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1 text-sm text-stone-700", children: opt.description })
140
- ] })
141
- ]
142
- },
143
- opt.value
144
- )) }),
145
- question.inputType === "multi-select" && question.options && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-2", role: "group", "aria-labelledby": `q-${question.id}`, children: question.options.map((opt) => {
146
- const values = localValue || [];
147
- const checked = values.includes(opt.value);
148
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
149
- "label",
150
- {
151
- className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors
152
- ${checked ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
153
- children: [
154
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
155
- "input",
156
- {
157
- type: "checkbox",
158
- value: opt.value,
159
- checked,
160
- onChange: (e) => {
161
- if (e.target.checked) {
162
- setLocalValue([...values, opt.value]);
163
- } else {
164
- setLocalValue(values.filter((v) => v !== opt.value));
165
- }
166
- },
167
- className: "mt-1 h-4 w-4 text-forest-700"
168
- }
169
- ),
170
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
171
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium text-stone-900", children: opt.label }),
172
- opt.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1 text-sm text-stone-700", children: opt.description })
173
- ] })
174
- ]
175
- },
176
- opt.value
177
- );
178
- }) }),
179
- question.inputType === "list" && question.listRange && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
180
- ListInput,
181
- {
182
- values: localValue || [],
183
- onChange: setLocalValue,
184
- min: question.listRange.min,
185
- max: question.listRange.max
186
- }
187
- )
188
- ] }),
121
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-8", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
122
+ InputForType,
123
+ {
124
+ question,
125
+ value: localValue,
126
+ onChange: setLocalValue
127
+ }
128
+ ) }),
189
129
  question.examples && question.examples.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { className: "mt-8", children: [
190
130
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("summary", { className: "cursor-pointer text-sm font-medium text-forest-700 hover:text-forest-800", children: "Show examples from different fields" }),
191
131
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "mt-4 space-y-3 border-l-2 border-copper-200 pl-5", children: question.examples.map((ex, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "text-sm italic text-stone-700", children: [
@@ -217,13 +157,174 @@ function QuestionCard({
217
157
  ] })
218
158
  ] });
219
159
  }
160
+ function InputForType({ question, value, onChange }) {
161
+ switch (question.inputType) {
162
+ case "short-text":
163
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ShortTextInput, { value, onChange });
164
+ case "long-text":
165
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LongTextInput, { value, onChange });
166
+ case "single-select":
167
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SingleSelectInput, { question, value, onChange });
168
+ case "multi-select":
169
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MultiSelectInput, { question, value: value ?? [], onChange });
170
+ case "list":
171
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ListInput, { value: value ?? [], onChange, listRange: question.listRange });
172
+ case "scale":
173
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ScaleInput, { question, value, onChange });
174
+ case "date":
175
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DateInput, { value, onChange });
176
+ case "composite":
177
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CompositeInput, { question, value: value ?? {}, onChange });
178
+ case "repeater":
179
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RepeaterInput, { question, value: value ?? [], onChange });
180
+ case "file-upload":
181
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FileUploadInput, { question, value: value ?? [], onChange });
182
+ default: {
183
+ const _exhaustive = question.inputType;
184
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(UnknownTypeFallback, { inputType: _exhaustive });
185
+ }
186
+ }
187
+ }
188
+ function ShortTextInput({ value, onChange }) {
189
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
190
+ "input",
191
+ {
192
+ type: "text",
193
+ value: value ?? "",
194
+ onChange: (e) => onChange(e.target.value),
195
+ autoFocus: true,
196
+ className: "w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none",
197
+ placeholder: "Type your answer\u2026"
198
+ }
199
+ );
200
+ }
201
+ function LongTextInput({ value, onChange }) {
202
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
203
+ "textarea",
204
+ {
205
+ value: value ?? "",
206
+ onChange: (e) => onChange(e.target.value),
207
+ autoFocus: true,
208
+ rows: 5,
209
+ className: "w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none",
210
+ placeholder: "Type your answer\u2026"
211
+ }
212
+ );
213
+ }
214
+ function SingleSelectInput({
215
+ question,
216
+ value,
217
+ onChange
218
+ }) {
219
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-3", role: "radiogroup", "aria-labelledby": `q-${question.id}`, children: (question.options ?? []).map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
220
+ "label",
221
+ {
222
+ className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors ${value === opt.value ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
223
+ children: [
224
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
225
+ "input",
226
+ {
227
+ type: "radio",
228
+ name: question.id,
229
+ value: opt.value,
230
+ checked: value === opt.value,
231
+ onChange: (e) => onChange(e.target.value),
232
+ className: "mt-1 h-4 w-4 text-forest-700"
233
+ }
234
+ ),
235
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
236
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium text-stone-900", children: opt.label }),
237
+ opt.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1 text-sm text-stone-700", children: opt.description })
238
+ ] })
239
+ ]
240
+ },
241
+ opt.value
242
+ )) });
243
+ }
244
+ function MultiSelectInput({
245
+ question,
246
+ value,
247
+ onChange
248
+ }) {
249
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-2", role: "group", "aria-labelledby": `q-${question.id}`, children: (question.options ?? []).map((opt) => {
250
+ const checked = value.includes(opt.value);
251
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
252
+ "label",
253
+ {
254
+ className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors ${checked ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
255
+ children: [
256
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
257
+ "input",
258
+ {
259
+ type: "checkbox",
260
+ value: opt.value,
261
+ checked,
262
+ onChange: (e) => {
263
+ if (e.target.checked) onChange([...value, opt.value]);
264
+ else onChange(value.filter((v) => v !== opt.value));
265
+ },
266
+ className: "mt-1 h-4 w-4 text-forest-700"
267
+ }
268
+ ),
269
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium text-stone-900", children: opt.label }),
271
+ opt.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1 text-sm text-stone-700", children: opt.description })
272
+ ] })
273
+ ]
274
+ },
275
+ opt.value
276
+ );
277
+ }) });
278
+ }
279
+ function ScaleInput({
280
+ question,
281
+ value,
282
+ onChange
283
+ }) {
284
+ const options = question.options ?? [];
285
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-3", role: "radiogroup", "aria-labelledby": `q-${question.id}`, children: options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
286
+ "label",
287
+ {
288
+ className: `flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-colors ${value === opt.value ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
289
+ children: [
290
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
291
+ "input",
292
+ {
293
+ type: "radio",
294
+ name: question.id,
295
+ value: opt.value,
296
+ checked: value === opt.value,
297
+ onChange: (e) => onChange(e.target.value),
298
+ className: "h-4 w-4 text-forest-700"
299
+ }
300
+ ),
301
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-mono text-sm text-stone-500", children: opt.value }),
302
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-stone-900", children: opt.label })
303
+ ]
304
+ },
305
+ opt.value
306
+ )) });
307
+ }
308
+ function DateInput({ value, onChange }) {
309
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
310
+ "input",
311
+ {
312
+ type: "date",
313
+ value: value ?? "",
314
+ onChange: (e) => onChange(e.target.value),
315
+ autoFocus: true,
316
+ className: "rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none"
317
+ }
318
+ );
319
+ }
220
320
  function ListInput({
221
- values,
321
+ value,
222
322
  onChange,
223
- min,
224
- max
323
+ listRange
225
324
  }) {
226
- const items = values.length > 0 ? values : [""];
325
+ const items = value.length > 0 ? value : [""];
326
+ const min = listRange?.min ?? 0;
327
+ const max = listRange?.max ?? Infinity;
227
328
  const setItem = (i, v) => {
228
329
  const next = [...items];
229
330
  next[i] = v;
@@ -250,7 +351,7 @@ function ListInput({
250
351
  value: item,
251
352
  onChange: (e) => setItem(i, e.target.value),
252
353
  className: "flex-grow rounded-lg border-2 border-stone-200 bg-stone-50 px-3 py-2 text-stone-900 focus:border-forest-700 focus:outline-none",
253
- placeholder: `Term ${i + 1}`
354
+ placeholder: `Item ${i + 1}`
254
355
  }
255
356
  ),
256
357
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -260,7 +361,7 @@ function ListInput({
260
361
  onClick: () => removeItem(i),
261
362
  disabled: items.length <= 1,
262
363
  className: "btn-ghost text-stone-500 disabled:opacity-30",
263
- "aria-label": `Remove term ${i + 1}`,
364
+ "aria-label": `Remove item ${i + 1}`,
264
365
  children: "\xD7"
265
366
  }
266
367
  )
@@ -273,19 +374,312 @@ function ListInput({
273
374
  onClick: addItem,
274
375
  disabled: items.length >= max,
275
376
  className: "text-sm font-medium text-forest-700 hover:text-forest-800 disabled:opacity-40",
276
- children: "+ Add term"
377
+ children: "+ Add item"
277
378
  }
278
379
  ),
279
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-mono text-xs text-stone-500", children: [
380
+ listRange && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-mono text-xs text-stone-500", children: [
280
381
  validCount,
281
382
  " / ",
282
383
  min,
283
384
  "\u2013",
284
- max
385
+ max === Infinity ? "\u221E" : max
285
386
  ] })
286
387
  ] })
287
388
  ] });
288
389
  }
390
+ function CompositeInput({
391
+ question,
392
+ value,
393
+ onChange
394
+ }) {
395
+ const subQuestions = question.subQuestions ?? [];
396
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-6 rounded-lg border-2 border-stone-200 bg-stone-50 p-4", children: subQuestions.map((sub) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
397
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-sm font-medium text-stone-900", children: sub.prompt }),
398
+ sub.subprompt && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1 text-xs text-stone-700", children: sub.subprompt }),
399
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
400
+ InputForType,
401
+ {
402
+ question: sub,
403
+ value: value[sub.id],
404
+ onChange: (next) => onChange({ ...value, [sub.id]: next })
405
+ }
406
+ ) })
407
+ ] }, sub.id)) });
408
+ }
409
+ function RepeaterInput({
410
+ question,
411
+ value,
412
+ onChange
413
+ }) {
414
+ const rows = value.length > 0 ? value : [{}];
415
+ const itemSchema = question.itemSchema ?? [];
416
+ const min = question.listRange?.min ?? 1;
417
+ const max = question.listRange?.max ?? Infinity;
418
+ const updateRow = (i, patch) => {
419
+ const next = [...rows];
420
+ next[i] = { ...next[i], ...patch };
421
+ onChange(next);
422
+ };
423
+ const addRow = () => {
424
+ if (rows.length < max) onChange([...rows, {}]);
425
+ };
426
+ const removeRow = (i) => {
427
+ if (rows.length <= 1) return;
428
+ onChange(rows.filter((_, idx) => idx !== i));
429
+ };
430
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
431
+ rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-lg border-2 border-stone-200 bg-stone-50 p-4", children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-3 flex items-center justify-between", children: [
433
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-mono text-xs uppercase tracking-wider text-stone-500", children: [
434
+ "Item ",
435
+ i + 1
436
+ ] }),
437
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
438
+ "button",
439
+ {
440
+ type: "button",
441
+ onClick: () => removeRow(i),
442
+ disabled: rows.length <= min,
443
+ className: "btn-ghost text-xs text-stone-500 disabled:opacity-30",
444
+ "aria-label": `Remove item ${i + 1}`,
445
+ children: "Remove"
446
+ }
447
+ )
448
+ ] }),
449
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-4", children: itemSchema.map((sub) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
450
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-sm font-medium text-stone-900", children: sub.prompt }),
451
+ sub.subprompt && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1 text-xs text-stone-700", children: sub.subprompt }),
452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
453
+ InputForType,
454
+ {
455
+ question: sub,
456
+ value: row[sub.id],
457
+ onChange: (next) => updateRow(i, { [sub.id]: next })
458
+ }
459
+ ) })
460
+ ] }, sub.id)) })
461
+ ] }, i)),
462
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
463
+ "button",
464
+ {
465
+ type: "button",
466
+ onClick: addRow,
467
+ disabled: rows.length >= max,
468
+ className: "text-sm font-medium text-forest-700 hover:text-forest-800 disabled:opacity-40",
469
+ children: "+ Add another"
470
+ }
471
+ )
472
+ ] });
473
+ }
474
+ function FileUploadInput({
475
+ question,
476
+ value,
477
+ onChange
478
+ }) {
479
+ const itemSchema = question.itemSchema ?? [];
480
+ const onFiles = (files) => {
481
+ if (!files || files.length === 0) return;
482
+ const entries = Array.from(files).map((f) => ({
483
+ file: f,
484
+ metadata: {}
485
+ }));
486
+ onChange([...value, ...entries]);
487
+ };
488
+ const updateMetadata = (i, patch) => {
489
+ const next = [...value];
490
+ next[i] = { ...next[i], metadata: { ...next[i].metadata, ...patch } };
491
+ onChange(next);
492
+ };
493
+ const removeEntry = (i) => onChange(value.filter((_, idx) => idx !== i));
494
+ const onDrop = (e) => {
495
+ e.preventDefault();
496
+ onFiles(e.dataTransfer.files);
497
+ };
498
+ const onDragOver = (e) => e.preventDefault();
499
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
501
+ "div",
502
+ {
503
+ onDrop,
504
+ onDragOver,
505
+ className: "rounded-lg border-2 border-dashed border-stone-300 bg-stone-50 p-6 text-center",
506
+ children: [
507
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-sm text-stone-700", children: [
508
+ "Drag PDFs or other files here, or",
509
+ " ",
510
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "cursor-pointer font-medium text-forest-700 hover:text-forest-800", children: [
511
+ "browse",
512
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
513
+ "input",
514
+ {
515
+ type: "file",
516
+ multiple: true,
517
+ className: "sr-only",
518
+ onChange: (e) => onFiles(e.target.files)
519
+ }
520
+ )
521
+ ] })
522
+ ] }),
523
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-1 text-xs text-stone-500", children: "You'll add a title, author, and description for each file below." })
524
+ ]
525
+ }
526
+ ),
527
+ value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "space-y-4", children: value.map((entry, i) => {
528
+ const name = entry.file.name;
529
+ const size = "size" in entry.file ? entry.file.size : void 0;
530
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "rounded-lg border-2 border-stone-200 bg-stone-50 p-4", children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between", children: [
532
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
533
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "font-medium text-stone-900", children: name }),
534
+ typeof size === "number" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "font-mono text-xs text-stone-500", children: [
535
+ (size / 1024).toFixed(1),
536
+ " KB"
537
+ ] })
538
+ ] }),
539
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
540
+ "button",
541
+ {
542
+ type: "button",
543
+ onClick: () => removeEntry(i),
544
+ className: "btn-ghost text-xs text-stone-500",
545
+ "aria-label": `Remove ${name}`,
546
+ children: "Remove"
547
+ }
548
+ )
549
+ ] }),
550
+ itemSchema.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 space-y-3", children: itemSchema.map((sub) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
551
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-xs font-medium text-stone-900", children: sub.prompt }),
552
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
553
+ InputForType,
554
+ {
555
+ question: sub,
556
+ value: entry.metadata[sub.id],
557
+ onChange: (next) => updateMetadata(i, { [sub.id]: next })
558
+ }
559
+ ) })
560
+ ] }, sub.id)) })
561
+ ] }, i);
562
+ }) })
563
+ ] });
564
+ }
565
+ function UnknownTypeFallback({ inputType }) {
566
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-lg border-2 border-stone-300 bg-stone-50 p-4 text-sm text-stone-500", children: [
567
+ "Unsupported question input type:",
568
+ " ",
569
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "font-mono", children: String(inputType) })
570
+ ] });
571
+ }
572
+
573
+ // src/summarize.ts
574
+ function isAnswered(value) {
575
+ if (value === void 0 || value === null) return false;
576
+ if (typeof value === "string") return value.trim().length > 0;
577
+ if (Array.isArray(value)) return value.length > 0;
578
+ if (typeof value === "object") return Object.keys(value).length > 0;
579
+ return true;
580
+ }
581
+ function summarizeShort(question, value, maxLen = 80) {
582
+ if (!isAnswered(value)) return "";
583
+ switch (question.inputType) {
584
+ case "short-text":
585
+ case "long-text":
586
+ case "date":
587
+ case "scale":
588
+ return truncate(String(value), maxLen);
589
+ case "single-select": {
590
+ const v = String(value);
591
+ const label = question.options?.find((o) => o.value === v)?.label;
592
+ return truncate(label ?? v, maxLen);
593
+ }
594
+ case "multi-select":
595
+ case "list": {
596
+ const arr = value;
597
+ const labels = arr.map((v) => {
598
+ const found = question.options?.find((o) => o.value === v)?.label;
599
+ return found ?? v;
600
+ });
601
+ return truncate(labels.join(", "), maxLen);
602
+ }
603
+ case "composite": {
604
+ const obj = value;
605
+ const subs = question.subQuestions ?? [];
606
+ for (const sub of subs) {
607
+ const subVal = obj[sub.id];
608
+ if (isAnswered(subVal)) {
609
+ return truncate(summarizeShort(sub, subVal, maxLen), maxLen);
610
+ }
611
+ }
612
+ return "";
613
+ }
614
+ case "repeater": {
615
+ const rows = value;
616
+ return `${rows.length} item${rows.length === 1 ? "" : "s"}`;
617
+ }
618
+ case "file-upload": {
619
+ const files = value;
620
+ return `${files.length} file${files.length === 1 ? "" : "s"}`;
621
+ }
622
+ default:
623
+ return "";
624
+ }
625
+ }
626
+ function summarizeLong(question, value) {
627
+ if (!isAnswered(value)) return "";
628
+ switch (question.inputType) {
629
+ case "short-text":
630
+ case "long-text":
631
+ case "date":
632
+ case "scale":
633
+ return String(value);
634
+ case "single-select": {
635
+ const v = String(value);
636
+ const label = question.options?.find((o) => o.value === v)?.label;
637
+ return label ?? v;
638
+ }
639
+ case "multi-select":
640
+ case "list": {
641
+ const arr = value;
642
+ const labels = arr.map((v) => question.options?.find((o) => o.value === v)?.label ?? v);
643
+ return labels.join("\n\u2022 ").replace(/^/, "\u2022 ");
644
+ }
645
+ case "composite": {
646
+ const obj = value;
647
+ const subs = question.subQuestions ?? [];
648
+ const lines = [];
649
+ for (const sub of subs) {
650
+ const subVal = obj[sub.id];
651
+ if (isAnswered(subVal)) {
652
+ lines.push(`${sub.prompt}: ${summarizeShort(sub, subVal, 200)}`);
653
+ }
654
+ }
655
+ return lines.join("\n");
656
+ }
657
+ case "repeater": {
658
+ const rows = value;
659
+ const itemSchema = question.itemSchema ?? [];
660
+ const summary = rows.map((row, i) => {
661
+ const parts = [`Item ${i + 1}:`];
662
+ for (const sub of itemSchema) {
663
+ const subVal = row[sub.id];
664
+ if (isAnswered(subVal)) {
665
+ parts.push(` ${sub.prompt}: ${summarizeShort(sub, subVal, 100)}`);
666
+ }
667
+ }
668
+ return parts.join("\n");
669
+ });
670
+ return summary.join("\n");
671
+ }
672
+ case "file-upload": {
673
+ const files = value;
674
+ return files.map((f, i) => `${i + 1}. ${f.file.name}`).join("\n");
675
+ }
676
+ default:
677
+ return "";
678
+ }
679
+ }
680
+ function truncate(s, maxLen) {
681
+ return s.length > maxLen ? s.slice(0, maxLen - 1) + "\u2026" : s;
682
+ }
289
683
 
290
684
  // src/AnswerSidebar.tsx
291
685
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -299,21 +693,20 @@ function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }) {
299
693
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "mb-3 font-mono text-xs uppercase tracking-wider text-stone-500", children: "Your answers" }),
300
694
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className: "space-y-3", children: questions.map((q, i) => {
301
695
  const answer = answers[q.id];
302
- const answered = answer !== void 0 && answer !== "" && !(Array.isArray(answer) && answer.length === 0);
696
+ const answered = isAnswered(answer);
303
697
  const isCurrent = i === currentIndex;
698
+ const preview = answered ? summarizeShort(q, answer) : "";
304
699
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
305
700
  "button",
306
701
  {
307
702
  type: "button",
308
703
  onClick: () => onSelectQuestion(i),
309
- className: `w-full rounded-lg border p-3 text-left transition-colors
310
- ${isCurrent ? "border-forest-700 bg-forest-50" : answered ? "border-stone-200 bg-stone-50 hover:border-stone-400" : "border-stone-200/50 bg-stone-50/50 hover:border-stone-300"}`,
704
+ className: `w-full rounded-lg border p-3 text-left transition-colors ${isCurrent ? "border-forest-700 bg-forest-50" : answered ? "border-stone-200 bg-stone-50 hover:border-stone-400" : "border-stone-200/50 bg-stone-50/50 hover:border-stone-300"}`,
311
705
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start gap-2", children: [
312
706
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
313
707
  "span",
314
708
  {
315
- className: `mt-0.5 font-mono text-xs
316
- ${isCurrent ? "text-forest-700" : answered ? "text-stone-500" : "text-stone-400"}`,
709
+ className: `mt-0.5 font-mono text-xs ${isCurrent ? "text-forest-700" : answered ? "text-stone-500" : "text-stone-400"}`,
317
710
  children: [
318
711
  String(i + 1).padStart(2, "0"),
319
712
  "."
@@ -324,12 +717,11 @@ function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }) {
324
717
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
325
718
  "div",
326
719
  {
327
- className: `text-xs font-medium
328
- ${answered ? "text-stone-900" : "text-stone-500"}`,
720
+ className: `text-xs font-medium ${answered ? "text-stone-900" : "text-stone-500"}`,
329
721
  children: q.prompt.length > 50 ? q.prompt.slice(0, 50) + "\u2026" : q.prompt
330
722
  }
331
723
  ),
332
- answered && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-1 text-xs italic text-stone-500 line-clamp-1", children: Array.isArray(answer) ? answer.join(", ") : answer })
724
+ answered && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-1 text-xs italic text-stone-500 line-clamp-1", children: preview })
333
725
  ] })
334
726
  ] })
335
727
  }
@@ -348,13 +740,13 @@ function ReviewSummary({ questions, answers, onGenerate, onEdit, generating }) {
348
740
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mt-4 text-lg text-stone-700", children: "These become the seed content of your project. You can edit anything now, or continue and edit later through the platform." }),
349
741
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dl", { className: "mt-10 space-y-6 border-t border-stone-200 pt-6", children: questions.map((q, i) => {
350
742
  const answer = answers[q.id];
351
- const display = Array.isArray(answer) ? answer.length > 0 ? answer.join(", ") : "(not answered)" : answer || "(not answered)";
352
- const isEmpty = !answer || typeof answer === "string" && answer.trim() === "" || Array.isArray(answer) && answer.length === 0;
743
+ const answered = isAnswered(answer);
744
+ const display = answered ? summarizeLong(q, answer) : "";
353
745
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-4 border-b border-stone-200 pb-6", children: [
354
746
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "font-mono text-sm text-stone-400 pt-1", children: String(i + 1).padStart(2, "0") }),
355
747
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-grow", children: [
356
748
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { className: "text-sm font-medium text-stone-900", children: q.prompt }),
357
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { className: "mt-2 text-stone-900 whitespace-pre-wrap", children: isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "italic text-stone-400", children: "Not answered" }) : display })
749
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { className: "mt-2 text-stone-900 whitespace-pre-wrap", children: answered ? display : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "italic text-stone-400", children: "Not answered" }) })
358
750
  ] }),
359
751
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
360
752
  "button",
@@ -392,6 +784,9 @@ function ReviewSummary({ questions, answers, onGenerate, onEdit, generating }) {
392
784
  AnswerSidebar,
393
785
  ProgressBar,
394
786
  QuestionCard,
395
- ReviewSummary
787
+ ReviewSummary,
788
+ isAnswered,
789
+ summarizeLong,
790
+ summarizeShort
396
791
  });
397
792
  //# sourceMappingURL=index.cjs.map