4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Blazor C# チュートリアル - Claude APIで画像入力

Last updated at Posted at 2024-04-01

はじめに

前回「C#(Blazor)でClaude APIを使う方法について」の続きです。
本記事では画像入力の機能について説明したいと思います。

完成イメージ

このような感じで画像入力ができるように機能を追加したいと思います。

image.png

概要説明

ファイルの選択ボタンを押して画像を選択し、その画像を入力に使います。
・BlazorApp1のHome.razorHome.razor.csに変更を加えます。

image.png

以下のリンクUpload Fileを参考にコードを追加します。

image.png

Anthropic ConsoleにWorkbenchという機能があります。
API実行および実行時のコードやデータ構造を確認することができます。
コードはPythonとTypeScriptですが、参考になると思います。

image.png

チュートリアル

それでは機能追加していきましょう。

ファイルを追加するボタンを追加します。
ボタンを押すとファイルを選択できるようになります。
ファイルが追加された後の処理UploadImageにて画像をbyte配列に変換します。

Home.razor
        <div class="p-4">
            <InputFile id="InputFileImage" OnChange="@UploadImage" type="file" accept="image/*" />
        </div>
Home.razor.cs
    bool isLoading = false;
    string imageType = "";
    byte[]? imageBytes = null;

    private async Task UploadImage(InputFileChangeEventArgs e)
    {
        if (isLoading) return;

        isLoading = true;

        // Get the uploaded image(s)
        var files = e.GetMultipleFiles();

        // Read the image data
        var buffer = new byte[files[0].Size];
        await files[0].OpenReadStream().ReadAsync(buffer);

        // Convert the image data to a base64 string
        imageBytes = buffer;
        imageInput = Convert.ToBase64String(buffer);
        imageType = files[0].ContentType;

        isLoading = false;
    }

Claude APIを呼び出すタイミングはSendボタンを押した時にしています。
入力データは「テキスト」or「画像+テキスト」の形式です。

Home.razor.cs
    async Task SendClick()
    {
        if (running) return;
        if (string.IsNullOrWhiteSpace(textInput)) return;

        running = true;
        try
        {
            chatMessages.Add(new() { 
                    Role = Roles.User,                 
                    Content = (imageBytes == null) ? textInput : [new(imageBytes, imageType), textInput ]
                });

            var stream = Anthropic.Messages.CreateStreamAsync(new()
            {
                Model = Models.Claude3Opus,
                MaxTokens = 1024,
                Temperature = temperature,
                System = string.IsNullOrWhiteSpace(systemInput) ? null : systemInput,
                Messages = chatMessages.ToArray()
            });

            var currentMessage = new Message { Role = Roles.Assistant, Content = "" };
            chatMessages.Add(currentMessage);

            textInput = ""; // clear input.
            imageType = "";
            imageBytes = null;

            await JSRuntime.InvokeVoidAsync("resetFileInput", null);

            StateHasChanged();

            await foreach (var messageStreamEvent in stream)
            {
                if (messageStreamEvent is ContentBlockDelta content)
                {
                    currentMessage.Content[0].Text += content.Delta.Text;
                    StateHasChanged();
                }
            }
        }
        finally
        {
            running = false;
        }
    }

チャット表示部の変更

switch文で分岐しています。入力に画像が含まれる場合はitem.Content[0].Textnullなのでご注意ください。

Home.razor
            @foreach (var item in chatMessages)
            {
                @if (item.Role == Roles.User)
                {
                    <div class="mb-2">
                        <span class="font-bold text-blue-500">User:</span>
                        @switch (item.Content[0].Type)
                        {
                            case ContentTypes.Text:
                                @((MarkupString)item.Content[0].Text!.Replace("\n", "<br />"))
                                break;
                            case ContentTypes.Image:
                                @((MarkupString)item.Content[1].Text!.Replace("\n", "<br />"))
                                // 画像のbyte配列からBase64文字列を生成
                                var base64Image = Convert.ToBase64String(item.Content[0].Source.Data.Span);
                                <img src="data:@item.Content[0].Source.MediaType;base64,@base64Image" alt="User Image" />                            
                                break;
                            default:
                                
                                break;
                        }
                    </div>
                }
                else
                {
                    <div class="mb-2">
                        <span class="font-bold text-green-500">Assistant:</span> @((MarkupString)item.Content[0].Text!.Replace("\n", "<br />"))
                    </div>
                }
            }

画像入力後にリセットする処理:resetFileInput

Home.razor
<script>
    var rangeInput = document.querySelector(".range_slider");
    var rangeBubble = document.querySelector(".range_bubble");

    rangeInput.addEventListener("input", () => {
        setBubble(rangeInput, rangeBubble);
    });
    rangeInput.addEventListener("mouseover", () => {
        setBubble(rangeInput, rangeBubble);
    });
    rangeInput.addEventListener("mouseleave", () => {
        rangeBubble.style.display = "none";
    });

    function setBubble(range, bubble) {
        var val = range.value;
        var min = range.min ? range.min : 0;
        var max = range.max ? range.max : 100;
        var newVal = Number(((val - min) * 100) / (max - min));
        bubble.innerHTML = val;
        bubble.style.left = `calc(${newVal}% + (${-5 - newVal * 0.15}px))`;
        rangeBubble.style.display = "inline-block";
    }

    function downloadFromBase64String(fileName, base64String) {
        const a = document.createElement('a');
        a.href = "data:application/octet-stream;base64," + base64String;
        a.download = fileName;
        a.click();
        a.remove();
    }

+    function resetFileInput(element) {
+        if (element) {
+            element.value = null;
+        } else {
+            const fileInputs = document.querySelectorAll('input[type="file"]');
+            fileInputs.forEach(input => input.value = null);
+        }
+    }
</script>

おわり

複数画像には対応していません。画像を使うと通信データ量が大きくなる為、クレジットの減りが早くなるのでご注意ください。
また、画像サイズが大きいとうまく動かない場合があります。
MaxTokens = 1024を変更するか、画像を小さくして対応してください。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?