needware-cli 1.7.1 → 1.7.3

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.
@@ -358,7 +358,7 @@ Since the codebase is a template, you should not assume they have set up anythin
358
358
 
359
359
  - Then list what features you'll implement in this first version. It's a first version so the user will be able to iterate on it. Don't do too much, but make it look good.
360
360
 
361
- - List possible colors, gradients, animations, fonts and styles you'll use if relevant. Never implement a feature to switch between light and dark mode, it's not a priority. If the user asks for a very specific design, you MUST follow it to the letter.
361
+ - List possible colors, gradients, animations, fonts and styles you'll use if relevant. Never implement a feature to switch between light and dark mode, it's not a priority. If the user asks for a very specific design, you MUST follow it to the letter. IMPORTANT: Do NOT use purple as the primary color theme unless explicitly requested by the user.
362
362
 
363
363
  - When implementing:
364
364
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "needware-cli",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "一个功能强大的 Node.js 命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -491,19 +491,18 @@ export function ChatInterface() {
491
491
  }
492
492
  ```
493
493
 
494
- ### Image Generation Specialized Template
494
+ ### Image Input Template (Virtual Try-On Example) 👗
495
495
 
496
- **CRITICAL: Use `gemini-3-pro-image-preview` model for image generation tasks.**
496
+ **Use Case:** When AI needs to process images and generate a new composite image. This example demonstrates a virtual try-on feature that combines a person photo with clothing.
497
497
 
498
- Use this template when users need to generate images from text descriptions.
498
+ **CRITICAL: Use `google/gemini-3-pro-image-preview` model with `modalities: ["text", "image"]` for multi-image generation tasks.**
499
499
 
500
- **File Location:** `supabase/functions/generate-image/index.ts`
500
+ **File Location:** `supabase/functions/virtual-try-on/index.ts`
501
501
 
502
502
  ```typescript
503
- // supabase/functions/generate-image/index.ts
503
+ // supabase/functions/virtual-try-on/index.ts
504
504
  import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
505
505
 
506
- // CORS headers for cross-origin requests
507
506
  const corsHeaders = {
508
507
  "Access-Control-Allow-Origin": "*",
509
508
  "Access-Control-Allow-Headers":
@@ -512,100 +511,192 @@ const corsHeaders = {
512
511
  "Access-Control-Max-Age": "86400",
513
512
  };
514
513
 
515
- // Request interface
516
- interface ImageGenerationRequest {
517
- prompt: string;
518
- options?: {
519
- size?: string;
520
- quality?: string;
521
- style?: string;
522
- };
514
+ interface VirtualTryOnRequest {
515
+ personImage: string;
516
+ clothingImage: string;
523
517
  }
524
518
 
525
519
  const handler = async (req: Request): Promise<Response> => {
526
- // Handle CORS preflight requests
527
520
  if (req.method === "OPTIONS") {
528
- return new Response(null, {
521
+ return new Response(null, {
529
522
  status: 200,
530
- headers: corsHeaders
523
+ headers: corsHeaders
531
524
  });
532
525
  }
533
526
 
534
527
  try {
535
- const { prompt, options }: ImageGenerationRequest = await req.json();
528
+ const { personImage, clothingImage }: VirtualTryOnRequest = await req.json();
536
529
 
537
- // Validate input
538
- if (!prompt) {
530
+ if (!personImage) {
539
531
  return new Response(
540
- JSON.stringify({ error: "No prompt provided" }),
541
- {
542
- status: 400,
543
- headers: { "Content-Type": "application/json", ...corsHeaders }
544
- }
532
+ JSON.stringify({ error: "Please upload a person photo" }),
533
+ { status: 400, headers: { "Content-Type": "application/json", ...corsHeaders } }
534
+ );
535
+ }
536
+
537
+ if (!clothingImage) {
538
+ return new Response(
539
+ JSON.stringify({ error: "Please select a clothing image" }),
540
+ { status: 400, headers: { "Content-Type": "application/json", ...corsHeaders } }
545
541
  );
546
542
  }
547
543
 
548
- console.log("Generating image with prompt:", prompt);
544
+ console.log("Processing virtual try-on request with two images");
549
545
 
550
- // Call AI Gateway API with image generation model
546
+ const prompt = `You are a virtual try-on AI. I'm giving you two images:
547
+ 1. First image: A photo of a person
548
+ 2. Second image: A clothing item
549
+
550
+ Your task: Generate a new image showing the person from image 1 wearing the clothing from image 2.
551
+
552
+ Requirements:
553
+ - Keep the person's face, body, pose, skin tone, and hair exactly the same
554
+ - Replace their current clothing with the clothing from image 2
555
+ - The clothing should fit naturally on the person's body
556
+ - Maintain realistic lighting and shadows
557
+ - Keep the same background as the original person photo
558
+ - Output only the final edited image, no text explanation needed`;
559
+
560
+ // Call AI Gateway API with multi-image input
551
561
  const response = await fetch("https://www.needware.dev/v1/chat/completions", {
552
562
  method: "POST",
553
563
  headers: {
554
564
  "Content-Type": "application/json",
555
565
  },
556
566
  body: JSON.stringify({
557
- model: "gemini-3-pro-image-preview", // 🎨 Use image generation model
567
+ model: "google/gemini-3-pro-image-preview", // 🎨 Image generation model
568
+ modalities: ["text", "image"], // Enable image output
558
569
  messages: [
559
- {
560
- role: "system",
561
- content: "You are a professional image generation assistant. Generate high-quality images based on user descriptions."
562
- },
563
570
  {
564
571
  role: "user",
565
- content: prompt
572
+ content: [
573
+ {
574
+ type: "text",
575
+ text: prompt
576
+ },
577
+ {
578
+ type: "image_url",
579
+ image_url: personImage // First image: person photo
580
+ },
581
+ {
582
+ type: "image_url",
583
+ image_url: clothingImage // Second image: clothing
584
+ }
585
+ ]
566
586
  }
567
587
  ],
568
- temperature: 0.8,
569
- max_tokens: 4000,
588
+ temperature: 0.7,
589
+ max_tokens: 8192,
570
590
  }),
571
591
  });
572
592
 
573
593
  if (!response.ok) {
574
594
  const errorText = await response.text();
575
595
  console.error("AI service error:", response.status, errorText);
576
- throw new Error(`Image generation failed: ${errorText}`);
596
+
597
+ if (response.status === 429) {
598
+ return new Response(
599
+ JSON.stringify({ error: "Request rate too high, please try again later" }),
600
+ { status: 429, headers: { "Content-Type": "application/json", ...corsHeaders } }
601
+ );
602
+ }
603
+
604
+ if (response.status === 402) {
605
+ return new Response(
606
+ JSON.stringify({ error: "AI service quota exhausted" }),
607
+ { status: 402, headers: { "Content-Type": "application/json", ...corsHeaders } }
608
+ );
609
+ }
610
+
611
+ let errorMessage = "AI service temporarily unavailable";
612
+ try {
613
+ const errorJson = JSON.parse(errorText);
614
+ errorMessage = errorJson.error?.message || errorJson.message || errorMessage;
615
+ } catch {
616
+ if (errorText) errorMessage = errorText.slice(0, 200);
617
+ }
618
+
619
+ return new Response(
620
+ JSON.stringify({ error: errorMessage }),
621
+ { status: 500, headers: { "Content-Type": "application/json", ...corsHeaders } }
622
+ );
577
623
  }
578
624
 
579
625
  const data = await response.json();
580
- const result = data.choices?.[0]?.message?.content;
626
+ console.log("AI response:", JSON.stringify(data, null, 2));
627
+
628
+ // ⚠️ IMPORTANT: Parse image from multiple possible response formats
629
+ let generatedImage: string | null = null;
630
+ const message = data.choices?.[0]?.message;
631
+
632
+ // Format 1: content_parts (Gemini native format)
633
+ if (message?.content_parts && Array.isArray(message.content_parts)) {
634
+ for (const part of message.content_parts) {
635
+ if (part.inline_data?.data) {
636
+ const mimeType = part.inline_data.mime_type || "image/png";
637
+ generatedImage = `data:${mimeType};base64,${part.inline_data.data}`;
638
+ break;
639
+ }
640
+ }
641
+ }
581
642
 
582
- if (!result) {
583
- throw new Error("No image generated");
643
+ // Format 2: content array with various structures
644
+ if (!generatedImage && Array.isArray(message?.content)) {
645
+ for (const item of message.content) {
646
+ if (item.type === "image_url" && item.image_url?.url) {
647
+ generatedImage = item.image_url.url;
648
+ break;
649
+ }
650
+ if (item.type === "image" && item.data) {
651
+ generatedImage = `data:image/png;base64,${item.data}`;
652
+ break;
653
+ }
654
+ if (item.inline_data?.data) {
655
+ const mimeType = item.inline_data.mime_type || "image/png";
656
+ generatedImage = `data:${mimeType};base64,${item.inline_data.data}`;
657
+ break;
658
+ }
659
+ }
584
660
  }
585
661
 
586
- console.log("Image generation completed");
662
+ // Format 3: Direct base64 string in content
663
+ if (!generatedImage && typeof message?.content === "string" && message.content) {
664
+ if (message.content.startsWith("data:image")) {
665
+ generatedImage = message.content;
666
+ } else if (message.content.length > 1000 && /^[A-Za-z0-9+/=\s]+$/.test(message.content)) {
667
+ // Looks like raw base64 data
668
+ generatedImage = `data:image/png;base64,${message.content.replace(/\s/g, '')}`;
669
+ }
670
+ }
671
+
672
+ if (!generatedImage) {
673
+ const reasoning = message?.reasoning;
674
+ console.error("No image in response. Full response:", JSON.stringify(data));
675
+
676
+ return new Response(
677
+ JSON.stringify({
678
+ error: "AI failed to generate try-on image, please retry or use a different photo",
679
+ debug: reasoning ? reasoning.slice(0, 100) : "Unable to extract image"
680
+ }),
681
+ { status: 500, headers: { "Content-Type": "application/json", ...corsHeaders } }
682
+ );
683
+ }
684
+
685
+ console.log("Virtual try-on completed successfully");
587
686
 
588
687
  return new Response(
589
- JSON.stringify({
688
+ JSON.stringify({
590
689
  success: true,
591
- imageUrl: result, // Image URL or base64 data
592
- prompt: prompt,
593
- model: "gemini-3-pro-image-preview"
690
+ image: generatedImage
594
691
  }),
595
- {
596
- status: 200,
597
- headers: { "Content-Type": "application/json", ...corsHeaders }
598
- }
692
+ { status: 200, headers: { "Content-Type": "application/json", ...corsHeaders } }
599
693
  );
600
694
 
601
695
  } catch (error: any) {
602
- console.error("Image generation error:", error);
696
+ console.error("Virtual try-on error:", error);
603
697
  return new Response(
604
- JSON.stringify({ error: error.message || "Unknown error" }),
605
- {
606
- status: 500,
607
- headers: { "Content-Type": "application/json", ...corsHeaders }
608
- }
698
+ JSON.stringify({ error: error.message || "Virtual try-on failed, please retry" }),
699
+ { status: 500, headers: { "Content-Type": "application/json", ...corsHeaders } }
609
700
  );
610
701
  }
611
702
  };
@@ -620,46 +711,50 @@ import { useState } from 'react';
620
711
  import { supabase } from '@/lib/supabase';
621
712
  import { toast } from 'sonner';
622
713
 
623
- export function ImageGenerator() {
624
- const [isGenerating, setIsGenerating] = useState(false);
625
- const [generatedImage, setGeneratedImage] = useState<string | null>(null);
714
+ export function VirtualTryOn() {
715
+ const [isProcessing, setIsProcessing] = useState(false);
716
+ const [resultImage, setResultImage] = useState<string | null>(null);
626
717
 
627
- const handleGenerate = async (prompt: string) => {
628
- setIsGenerating(true);
718
+ const handleTryOn = async (personImage: string, clothingImage: string) => {
719
+ setIsProcessing(true);
629
720
 
630
721
  try {
631
- const { data, error } = await supabase.functions.invoke('generate-image', {
722
+ const { data, error } = await supabase.functions.invoke('virtual-try-on', {
632
723
  body: {
633
- prompt,
634
- options: {
635
- size: '1024x1024',
636
- quality: 'high'
637
- }
724
+ personImage, // Base64 or URL of person photo
725
+ clothingImage // Base64 or URL of clothing image
638
726
  }
639
727
  });
640
728
 
641
729
  if (error) throw error;
642
730
  if (data?.error) throw new Error(data.error);
643
731
 
644
- setGeneratedImage(data.imageUrl);
645
- toast.success("图片生成成功!");
732
+ setResultImage(data.image);
733
+ toast.success("Virtual try-on completed!");
646
734
  } catch (error) {
647
- console.error("Image generation error:", error);
648
- toast.error(error instanceof Error ? error.message : "图片生成失败");
735
+ console.error("Virtual try-on error:", error);
736
+ toast.error(error instanceof Error ? error.message : "Try-on failed, please retry");
649
737
  } finally {
650
- setIsGenerating(false);
738
+ setIsProcessing(false);
651
739
  }
652
740
  };
653
741
 
654
742
  return (
655
743
  <div>
656
- {generatedImage && <img src={generatedImage} alt="Generated" />}
657
- {/* UI components */}
744
+ {resultImage && <img src={resultImage} alt="Try-on result" />}
745
+ {/* Upload components for person and clothing images */}
658
746
  </div>
659
747
  );
660
748
  }
661
749
  ```
662
750
 
751
+ **Key Points for Multi-Image Generation:**
752
+
753
+ 1. **modalities parameter**: Set `modalities: ["text", "image"]` to enable image output
754
+ 2. **Multiple image_url**: Include multiple `image_url` objects in the content array
755
+ 3. **Response parsing**: Handle multiple response formats (content_parts, content array, direct base64)
756
+ 4. **Higher max_tokens**: Use `max_tokens: 8192` for image generation tasks
757
+
663
758
  ### Image Analysis Specialized Template
664
759
 
665
760
  Use this template when users need image analysis functionality.