Home

2026-01-26 画像と文字の組み合わせ指示機能

画像と文字の組み合わせ指示機能

作成日: 2026-01-26 ステータス: 検討中

要件

全コマンド(plan, research, discuss, fullstack, go, nextjs)で、画像と文字を組み合わせた指示を可能にする。

詳細要件

  • 対象コマンド: 全コマンド
  • 入力形式: 画像 + 文字(両方の組み合わせ)
  • 画像枚数: 2-5枚程度
  • 重要度: すべての画像が同等に重要

現状分析

現在のシステム構成

  • Arguments: 単一の文字列として扱われる
  • ファイル選択: プロジェクト内の既存ファイルのみ(アップロード機能なし)
  • Backend: Go (Gin) で Claude CLI を呼び出し
  • Frontend: Next.js で SSE ストリーミング
  • 画像処理機能: なし

既存の Arguments フロー

  1. Frontend: CommandForm で文字列として入力
  2. Frontend: 選択されたファイルパスと文字列を結合
  3. Backend: Args string として受け取り
  4. Backend: Claude CLI に -p "/<command> <args>" として渡す

実装アプローチ

アプローチ1: Base64エンコード + マルチパート引数構造(推奨)

概要

  • 画像をBase64エンコードしてJSON構造で渡す
  • Argumentsを構造化:{text: string, images: [{name, data, mimeType}]}
  • Claude APIのvision capabilityを活用

メリット

  • Claude APIネイティブの画像サポートを活用
  • フロントエンドで画像プレビュー可能
  • 複数画像を配列で管理しやすい
  • 実装が比較的シンプル
  • 既存アーキテクチャとの統合が容易

デメリット

  • Base64は元のサイズより約33%増加
  • 大きな画像は通信量が増える
  • Backend側でClaude CLIへの渡し方を調整必要

実装箇所

  • Frontend: 画像アップロードUI、Base64エンコード、構造化されたリクエスト
  • Backend: 構造化引数の受け取り、Claude CLIへの変換
  • 型定義の変更(CommandRequest)

技術的詳細

Frontend型定義

interface ImageData {
  name: string;
  data: string; // Base64エンコードされた画像データ
  mimeType: string; // "image/jpeg", "image/png" など
}

interface CommandRequest {
  project: string;
  command: string;
  args: string; // テキスト部分
  images?: ImageData[]; // 新規追加
}

Backend型定義

type CommandRequest struct {
    Project string      `json:"project"`
    Command string      `json:"command"`
    Args    string      `json:"args"`
    Images  []ImageData `json:"images,omitempty"` // 新規追加
}

type ImageData struct {
    Name     string `json:"name"`
    Data     string `json:"data"` // Base64
    MimeType string `json:"mimeType"`
}

アプローチ2: ファイルアップロード + 一時ストレージ

概要

  • 画像を一時的にサーバーにアップロード
  • ファイルパスをArgumentsに含める
  • Claude CLIがファイルパスから画像を読み込み

メリット

  • 大きな画像でもリクエストサイズが小さい
  • 画像の再利用が可能(同じセッション内)
  • バックエンド側でファイル操作が容易

デメリット

  • 一時ファイルの管理が必要(クリーンアップ)
  • アップロードエンドポイントの追加実装
  • セキュリティ考慮が必要(ファイル検証、サイズ制限)
  • 2段階のリクエスト(アップロード→コマンド実行)

実装箇所

  • Frontend: 画像アップロードUI、マルチパートフォーム
  • Backend: アップロードエンドポイント、一時ストレージ管理
  • Backend: クリーンアップロジック

アプローチ3: ハイブリッド(小さい画像はBase64、大きい画像はアップロード)

概要

  • 閾値(例: 500KB)を設定
  • 小さい画像: Base64で直接送信
  • 大きい画像: アップロードしてパスを送信

メリット

  • 両方の長所を活用
  • パフォーマンス最適化

デメリット

  • 実装が複雑
  • テストケースが増える
  • メンテナンスコストが高い

実装箇所

  • 両アプローチの実装箇所すべて
  • サイズ判定ロジック

MVP提案: アプローチ1(Base64 + 構造化引数)

最初のMVPとしてアプローチ1を推奨します。

推奨理由

  1. Claude APIが画像をネイティブサポート
  2. 実装がシンプルで、既存アーキテクチャとの統合が容易
  3. 2-5枚程度の画像なら、Base64のオーバーヘッドは許容範囲
  4. フロントエンドでプレビューが簡単
  5. 後から必要に応じてアプローチ2に拡張可能

MVP実装スコープ

Phase 1: 基本機能

  • 画像アップロードUI(ドラッグ&ドロップ対応)
  • 最大5枚までの画像選択
  • 画像プレビュー表示(サムネイル)
  • Base64エンコード処理
  • 構造化されたCommandRequest送信

Phase 2: Backend対応

  • CommandRequest型の拡張(Images フィールド追加)
  • 構造化引数の受け取り
  • Claude CLIへの画像データ渡し(vision capability活用)
  • エラーハンドリング(サイズ超過、不正な形式)

Phase 3: UX改善

  • 画像削除機能(個別削除)
  • 画像の並び替え(ドラッグ&ドロップ)
  • サイズ制限とエラーハンドリング
  • 画像圧縮(オプション、1MB超の場合)
  • ローディングインジケーター

Claude APIの画像サポート

Claude 3シリーズ(Opus/Sonnet/Haiku)はvision capabilityをサポート:

  • 対応形式: JPEG, PNG, GIF, WebP
  • サイズ制限: 最大5MB/画像(APIの制限)
  • 複数画像を同時に渡すことが可能
  • Base64エンコードでの送信をサポート

制約事項

  • 画像サイズ制限: 5MB/画像
  • 最大枚数: 5枚
  • 対応フォーマット: JPEG, PNG, GIF, WebP
  • 合計サイズ制限: 20MB(リクエスト全体)

実装の詳細設計

Frontend実装

新規コンポーネント: ImageUploader

// src/components/ImageUploader.tsx
interface ImageUploaderProps {
  images: ImageData[];
  onImagesChange: (images: ImageData[]) => void;
  maxImages?: number; // デフォルト: 5
  maxSizePerImage?: number; // デフォルト: 5MB
}

export function ImageUploader({
  images,
  onImagesChange,
  maxImages = 5,
  maxSizePerImage = 5 * 1024 * 1024
}: ImageUploaderProps) {
  // ドラッグ&ドロップ処理
  // ファイル選択処理
  // Base64エンコード
  // プレビュー表示
  // 削除処理
}

CommandForm の拡張

// src/components/CommandForm.tsx
export default function CommandForm() {
  const [images, setImages] = useState<ImageData[]>([]);

  // 既存のステート
  const [projectPath, setProjectPath] = useState("");
  const [command, setCommand] = useState("");
  const [args, setArgs] = useState("");
  const [selectedFile, setSelectedFile] = useState("");

  const handleSubmit = async () => {
    const request: CommandRequest = {
      project: projectPath,
      command: command,
      args: args,
      images: images.length > 0 ? images : undefined,
    };

    // 送信処理
  };
}

Backend実装

handler/command.go の拡張

// internal/handler/command.go

type CommandRequest struct {
    Project string      `json:"project"`
    Command string      `json:"command"`
    Args    string      `json:"args"`
    Images  []ImageData `json:"images,omitempty"`
}

type ImageData struct {
    Name     string `json:"name"`
    Data     string `json:"data"`
    MimeType string `json:"mimeType"`
}

func (h *CommandHandler) HandleStream(c *gin.Context) {
    var req CommandRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // 画像のバリデーション
    if err := validateImages(req.Images); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // サービス層へ渡す
    ctx := c.Request.Context()
    resultChan := h.service.ExecuteCommandStream(ctx, req)

    // ストリーミング処理
}

func validateImages(images []ImageData) error {
    if len(images) > 5 {
        return fmt.Errorf("最大5枚まで")
    }

    for _, img := range images {
        // Base64デコード
        decoded, err := base64.StdEncoding.DecodeString(img.Data)
        if err != nil {
            return fmt.Errorf("無効なBase64: %w", err)
        }

        // サイズチェック(5MB)
        if len(decoded) > 5*1024*1024 {
            return fmt.Errorf("画像サイズは5MB以下: %s", img.Name)
        }

        // MIMEタイプチェック
        allowedTypes := []string{"image/jpeg", "image/png", "image/gif", "image/webp"}
        if !contains(allowedTypes, img.MimeType) {
            return fmt.Errorf("非対応の形式: %s", img.MimeType)
        }
    }

    return nil
}

service/claude.go の拡張

// internal/service/claude.go

func (s *ClaudeService) ExecuteCommandStream(
    ctx context.Context,
    req CommandRequest,
) <-chan CommandResult {
    resultChan := make(chan CommandResult)

    go func() {
        defer close(resultChan)

        // 画像がある場合、一時ファイルとして保存
        var imagePaths []string
        if len(req.Images) > 0 {
            for i, img := range req.Images {
                path, err := saveImageToTemp(img)
                if err != nil {
                    resultChan <- CommandResult{Error: err}
                    return
                }
                imagePaths = append(imagePaths, path)
                defer os.Remove(path) // クリーンアップ
            }
        }

        // Claude CLI コマンド構築
        prompt := fmt.Sprintf("/%s %s", req.Command, req.Args)

        args := []string{
            "-p", prompt,
            "--output-format", "stream-json",
            "--verbose",
            "--permission-mode", "bypassPermissions",
        }

        // 画像パスを追加
        for _, path := range imagePaths {
            args = append(args, "--image", path)
        }

        cmd := exec.CommandContext(ctx, "claude", args...)

        // 実行処理
    }()

    return resultChan
}

func saveImageToTemp(img ImageData) (string, error) {
    decoded, err := base64.StdEncoding.DecodeString(img.Data)
    if err != nil {
        return "", err
    }

    // 拡張子を取得
    ext := filepath.Ext(img.Name)
    if ext == "" {
        ext = mimeTypeToExt(img.MimeType)
    }

    // 一時ファイル作成
    tmpFile, err := os.CreateTemp("", "claude-image-*"+ext)
    if err != nil {
        return "", err
    }
    defer tmpFile.Close()

    if _, err := tmpFile.Write(decoded); err != nil {
        return "", err
    }

    return tmpFile.Name(), nil
}

func mimeTypeToExt(mimeType string) string {
    switch mimeType {
    case "image/jpeg":
        return ".jpg"
    case "image/png":
        return ".png"
    case "image/gif":
        return ".gif"
    case "image/webp":
        return ".webp"
    default:
        return ""
    }
}

テスト計画

Frontend テスト

  • 画像アップロード機能のユニットテスト
  • Base64エンコードのテスト
  • サイズ制限のテスト
  • 複数画像の管理テスト

Backend テスト

  • CommandRequest のバリデーションテスト
  • 画像データのデコードテスト
  • 一時ファイル作成・削除のテスト
  • Claude CLI 呼び出しのテスト

インテグレーションテスト

  • フロントエンドからバックエンドまでのE2Eテスト
  • 実際のClaude APIとの連携テスト

セキュリティ考慮事項

  1. ファイルサイズ制限: フロントエンド・バックエンド両方で検証
  2. ファイル形式検証: MIMEタイプとマジックバイトの両方をチェック
  3. 一時ファイルのクリーンアップ: 処理後は必ず削除
  4. CSRF保護: 既存のセキュリティ機構を維持
  5. レート制限: 大量の画像アップロードを防ぐ

今後の拡張可能性

将来的な機能追加

  • 画像圧縮の自動化
  • より大きな画像のサポート(アプローチ2への移行)
  • 画像の編集機能(クロップ、回転)
  • 画像のキャッシュ機能
  • セッション内での画像再利用

次のステップ

  1. /plan で実装計画を作成
  2. Phase 1(基本機能)から実装開始
  3. 計画確定後、開発/実装/実装待ち/ に移動