plotlink-ows 1.0.32 → 1.0.33

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.
@@ -42,8 +42,8 @@ For login, the passphrase is hashed with HMAC-SHA256 and compared against the st
42
42
  | \`/api/publish/preflight\` | GET | Check wallet balance, Filebase config |
43
43
  | \`/api/publish/file\` | POST | Publish story on-chain (SSE stream of progress events) |
44
44
  | \`/api/publish/retry-index\` | POST | Retry indexing for a published file |
45
- | \`/api/publish/upload-cover\` | POST | Upload cover image — FormData \`file\` field, **WebP or JPEG only**, max 500KB → returns \`{ cid }\` |
46
- | \`/api/publish/upload-plot-image\` | POST | Upload plot illustration — FormData \`file\` field, **WebP or JPEG only**, max 500KB → returns \`{ cid, url }\` |
45
+ | \`/api/publish/upload-cover\` | POST | Upload cover image — FormData \`file\` field, **WebP or JPEG only**, max 1MB → returns \`{ cid }\` |
46
+ | \`/api/publish/upload-plot-image\` | POST | Upload plot illustration — FormData \`file\` field, **WebP or JPEG only**, max 1MB → returns \`{ cid, url }\` |
47
47
  | \`/api/publish/update-storyline\` | POST | Update storyline metadata (coverCid, genre, language, isNsfw) |
48
48
 
49
49
  **Publish flow:** Upload to IPFS → estimate gas → sign with OWS wallet → broadcast → confirm → index on plotlink.xyz (8s delay + 10 retries × 30s). Genesis files call \`createStoryline\`, plot files (\`plot-*.md\`) call \`chainPlot\`. Content limit: 10K chars.
@@ -212,9 +212,9 @@ publish.post("/upload-cover", async (c) => {
212
212
  return c.json({ error: "No image file provided" }, 400);
213
213
  }
214
214
 
215
- // Validate file size (500KB max)
216
- if (file.size > 500 * 1024) {
217
- return c.json({ error: "Image exceeds 500KB limit" }, 400);
215
+ // Validate file size (1MB max)
216
+ if (file.size > 1024 * 1024) {
217
+ return c.json({ error: "Image exceeds 1MB limit" }, 400);
218
218
  }
219
219
 
220
220
  // Validate file type — only WebP and JPEG accepted by the plotlink server
@@ -247,9 +247,9 @@ publish.post("/upload-plot-image", async (c) => {
247
247
  return c.json({ error: "No image file provided" }, 400);
248
248
  }
249
249
 
250
- // Validate file size (500KB max)
251
- if (file.size > 500 * 1024) {
252
- return c.json({ error: "Image exceeds 500KB limit" }, 400);
250
+ // Validate file size (1MB max)
251
+ if (file.size > 1024 * 1024) {
252
+ return c.json({ error: "Image exceeds 1MB limit" }, 400);
253
253
  }
254
254
 
255
255
  // Validate file type — only WebP and JPEG accepted by the plotlink server
@@ -187,8 +187,8 @@ export function PreviewPanel({ storyName, fileName, authFetch, onPublish, publis
187
187
  const handleCoverSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
188
188
  const file = e.target.files?.[0];
189
189
  if (!file) return;
190
- if (file.size > 500 * 1024) {
191
- setEditError("Image exceeds 500KB limit");
190
+ if (file.size > 1024 * 1024) {
191
+ setEditError("Image exceeds 1MB limit");
192
192
  return;
193
193
  }
194
194
  if (!file.type.startsWith("image/")) {
@@ -202,8 +202,8 @@ export function PreviewPanel({ storyName, fileName, authFetch, onPublish, publis
202
202
 
203
203
  // Handle illustration image upload from File object
204
204
  const uploadIllustration = useCallback(async (file: File) => {
205
- if (file.size > 500 * 1024) {
206
- setIllustrationError("Image exceeds 500KB limit");
205
+ if (file.size > 1024 * 1024) {
206
+ setIllustrationError("Image exceeds 1MB limit");
207
207
  return;
208
208
  }
209
209
  const allowedTypes = ["image/webp", "image/jpeg"];
@@ -500,70 +500,6 @@ export function PreviewPanel({ storyName, fileName, authFetch, onPublish, publis
500
500
  {saving ? "Saving..." : "Save"}
501
501
  </button>
502
502
  </div>
503
- {/* Inline illustration upload for plot files */}
504
- {isPlot && (
505
- <div className="px-3 py-1.5 border-t border-border">
506
- <label className="flex items-center gap-1.5 text-xs text-muted cursor-pointer">
507
- <input
508
- type="checkbox"
509
- checked={showIllustrations}
510
- onChange={(e) => setShowIllustrations(e.target.checked)}
511
- className="rounded border-border"
512
- />
513
- Add illustrations in the plot
514
- </label>
515
- {showIllustrations && (
516
- <div className="mt-2 flex flex-col gap-2">
517
- <div
518
- className="border-2 border-dashed border-border rounded p-3 flex flex-col items-center gap-1.5 cursor-pointer hover:border-accent transition-colors"
519
- onClick={() => illustrationInputRef.current?.click()}
520
- onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }}
521
- onDrop={(e) => {
522
- e.preventDefault();
523
- e.stopPropagation();
524
- const file = e.dataTransfer.files?.[0];
525
- if (file) uploadIllustration(file);
526
- }}
527
- >
528
- <input
529
- ref={illustrationInputRef}
530
- type="file"
531
- accept="image/webp,image/jpeg"
532
- onChange={handleIllustrationInput}
533
- className="hidden"
534
- />
535
- <span className="text-xs text-muted">
536
- {illustrationUploading ? "Uploading..." : "Drop image here or click to browse"}
537
- </span>
538
- <span className="text-xs text-muted">WebP/JPEG, max 500KB</span>
539
- </div>
540
- {illustrationError && (
541
- <span className="text-error text-xs">{illustrationError}</span>
542
- )}
543
- {uploadedImages.map((img, i) => (
544
- <div key={img.cid} className="border border-border rounded p-2 flex flex-col gap-1 bg-surface">
545
- <span className="text-xs text-green-700">Image uploaded! Copy the markdown below and paste it where you want the illustration to appear in your plot:</span>
546
- <div className="flex items-center gap-1.5">
547
- <code className="flex-1 text-xs bg-background px-2 py-1 rounded font-mono break-all">
548
- ![Scene description]({img.url})
549
- </code>
550
- <button
551
- onClick={() => {
552
- navigator.clipboard.writeText(`![Scene description](${img.url})`);
553
- setCopiedIndex(i);
554
- setTimeout(() => setCopiedIndex(null), 2000);
555
- }}
556
- className="px-2 py-1 text-xs border border-border rounded hover:bg-surface shrink-0"
557
- >
558
- {copiedIndex === i ? "Copied!" : "Copy"}
559
- </button>
560
- </div>
561
- </div>
562
- ))}
563
- </div>
564
- )}
565
- </div>
566
- )}
567
503
  </div>
568
504
  )}
569
505
 
@@ -717,7 +653,7 @@ export function PreviewPanel({ storyName, fileName, authFetch, onPublish, publis
717
653
  onChange={handleCoverSelect}
718
654
  className="text-xs"
719
655
  />
720
- <span className="text-xs text-muted">WebP/JPEG, max 500KB, 600x900px recommended</span>
656
+ <span className="text-xs text-muted">WebP/JPEG, max 1MB, 600x900px recommended</span>
721
657
  </div>
722
658
  </div>
723
659
  </div>
@@ -769,6 +705,70 @@ export function PreviewPanel({ storyName, fileName, authFetch, onPublish, publis
769
705
  </div>
770
706
  ) : (
771
707
  <div className="flex flex-col gap-2">
708
+ {/* Inline illustration upload for plot files (Preview tab only) */}
709
+ {isPlot && activeTab === "preview" && (
710
+ <div>
711
+ <label className="flex items-center gap-1.5 text-xs text-muted cursor-pointer">
712
+ <input
713
+ type="checkbox"
714
+ checked={showIllustrations}
715
+ onChange={(e) => setShowIllustrations(e.target.checked)}
716
+ className="rounded border-border"
717
+ />
718
+ Add illustrations in the plot
719
+ </label>
720
+ {showIllustrations && (
721
+ <div className="mt-2 flex flex-col gap-2">
722
+ <div
723
+ className="border-2 border-dashed border-border rounded p-3 flex flex-col items-center gap-1.5 cursor-pointer hover:border-accent transition-colors"
724
+ onClick={() => illustrationInputRef.current?.click()}
725
+ onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }}
726
+ onDrop={(e) => {
727
+ e.preventDefault();
728
+ e.stopPropagation();
729
+ const file = e.dataTransfer.files?.[0];
730
+ if (file) uploadIllustration(file);
731
+ }}
732
+ >
733
+ <input
734
+ ref={illustrationInputRef}
735
+ type="file"
736
+ accept="image/webp,image/jpeg"
737
+ onChange={handleIllustrationInput}
738
+ className="hidden"
739
+ />
740
+ <span className="text-xs text-muted">
741
+ {illustrationUploading ? "Uploading..." : "Drop image here or click to browse"}
742
+ </span>
743
+ <span className="text-xs text-muted">WebP/JPEG, max 1MB</span>
744
+ </div>
745
+ {illustrationError && (
746
+ <span className="text-error text-xs">{illustrationError}</span>
747
+ )}
748
+ {uploadedImages.map((img, i) => (
749
+ <div key={img.cid} className="border border-border rounded p-2 flex flex-col gap-1 bg-surface">
750
+ <span className="text-xs text-green-700">Image uploaded! Copy the markdown below and paste it where you want the illustration to appear in your plot:</span>
751
+ <div className="flex items-center gap-1.5">
752
+ <code className="flex-1 text-xs bg-background px-2 py-1 rounded font-mono break-all">
753
+ ![Scene description]({img.url})
754
+ </code>
755
+ <button
756
+ onClick={() => {
757
+ navigator.clipboard.writeText(`![Scene description](${img.url})`);
758
+ setCopiedIndex(i);
759
+ setTimeout(() => setCopiedIndex(null), 2000);
760
+ }}
761
+ className="px-2 py-1 text-xs border border-border rounded hover:bg-surface shrink-0"
762
+ >
763
+ {copiedIndex === i ? "Copied!" : "Copy"}
764
+ </button>
765
+ </div>
766
+ </div>
767
+ ))}
768
+ </div>
769
+ )}
770
+ </div>
771
+ )}
772
772
  <div className="flex items-center gap-2">
773
773
  {(isGenesis) && (
774
774
  <>