#
Giới thiệu về Gradio Blocks

Trong các phần trước, chúng ta đã tìm hiểu và tạo các bản demo bằng cách sử dụng lớp `Interface`. Trong phần này, chúng tôi sẽ giới thiệu API cấp thấp mới được phát triển của mình có tên là `gradio.Blocks`.

Bây giờ, sự khác biệt giữa `Interface` và `Blocks`là gì?

- ⚡ `Interface`: một API cấp cao cho phép bạn tạo một bản demo học máy đầy đủ chỉ đơn giản bằng cách cung cấp danh sách các đầu vào và đầu ra.

- 🧱 `Blocks`: một API cấp thấp cho phép bạn có toàn quyền kiểm soát các luồng dữ liệu và bố cục của ứng dụng của mình. Bạn có thể xây dựng các ứng dụng nhiều bước, rất phức tạp bằng cách sử dụng `Blocks` (như trong “các khối xây dựng”).

###
Tại sao lại là Blocks 🧱?

Như chúng ta đã thấy trong các phần trước, lớp `Interface` cho phép bạn dễ dàng tạo các bản demo học máy chính thức chỉ với một vài dòng mã. API `Interface` cực kỳ dễ sử dụng nhưng thiếu tính linh hoạt mà API `Blocks` cung cấp. Ví dụ: bạn có thể muốn:

- Nhóm các bản demo có liên quan lại với nhau dưới dạng nhiều tab trong một ứng dụng web
- Thay đổi bố cục của bản demo của bạn, ví dụ: để chỉ định vị trí của các đầu vào và đầu ra
- Có giao diện nhiều bước, trong đó đầu ra của một mô hình trở thành đầu vào cho mô hình tiếp theo hoặc có các luồng dữ liệu linh hoạt hơn nói chung
- Thay đổi thuộc tính của một thành phần (ví dụ: các lựa chọn trong danh sách thả xuống) hoặc khả năng hiển thị của nó dựa trên đầu vào của người dùng

Chúng ta sẽ khám phá tất cả các khái niệm này trong các phần tiếp theo.

###
Tạo một bản demo đơn giản bằng cách sử dụng Blocks

Sau khi bạn đã cài đặt Gradio, hãy chạy mã bên dưới dưới dạng tập lệnh Python, notebook Jupyter hoặc sổ ghi chép Colab.

    import gradio as gr

    def flip_text(x):
        return x[::-1]

    demo = gr.Blocks()

    with demo:
        gr.Markdown(
            """
        # Flip Text!
        Start typing below to see the output.
        """
        )
        input = gr.Textbox(placeholder="Flip this text")
        output = gr.Textbox()

        input.change(fn=flip_text, inputs=input, outputs=output)

    demo.launch()

Ví dụ đơn giản ở trên giới thiệu 4 khái niệm làm nền tảng cho Blocks:

1. Blocks cho phép bạn xây dựng các ứng dụng web kết hợp markdown, HTML, các nút và các thành phần tương tác đơn giản bằng cách khởi tạo các đối tượng bằng Python bên trong ngữ cảnh `with gradio.Blocks`.

🙋Nếu bạn không quen với câu lệnh `with` trong Python, chúng tôi khuyên bạn nên xem [hướng dẫn](https://realpython.com/python-with-statement/) tuyệt vời từ Real Python. Quay lại đây sau khi đọc xong 🤗

Thứ tự mà bạn khởi tạo các thành phần quan trọng khi mỗi phần tử được hiển thị vào ứng dụng web theo thứ tự nó được tạo. (Các bố cục phức tạp hơn được thảo luận bên dưới)

2. Bạn có thể xác định các hàm Python thông thường ở bất kỳ đâu trong mã của mình và chạy chúng với đầu vào của người dùng bằng cách sử dụng `Blocks`. Trong ví dụ của mình, chúng ta có một hàm đơn giản “lật” văn bản đầu vào, nhưng bạn có thể viết bất kỳ hàm Python nào, từ một phép tính đơn giản đến xử lý các dự đoán từ một mô hình học máy.

3. Bạn có thể gán các sự kiện cho bất kỳ thành phần `Blocks` nào. Điều này sẽ chạy hàm của bạn khi thành phần được nhấp, thay đổi, v.v. Khi bạn gán một sự kiện, bạn truyền vào ba tham số: `fn`: hàm cần được gọi,`inputs`: (danh sách) thành phần đầu vào (s), và `outputs`: (danh sách) các thành phần đầu ra cần được gọi.

   Trong ví dụ trên, chúng tôi chạy hàm `flip_text()` khi giá trị trong `Textbox` có tên đầu vào `input` thay đổi. Sự kiện đọc giá trị trong `input`, truyền nó làm tham số tên cho `flip_text()`, sau đó trả về một giá trị được gán cho `Textbox` thứ hai của chúng ta có tên là `output`.

   Để xem danh sách các sự kiện mà mỗi thành phần hỗ trợ, hãy xem tài liệu Gradio.

4. Các khối tự động tìm ra liệu một thành phần có nên tương tác (chấp nhận đầu vào của người dùng) hay không, dựa trên các trình kích hoạt sự kiện mà bạn xác định. Trong ví dụ của chúng ta, hộp văn bản đầu tiên là tương tác, vì giá trị của nó được sử dụng bởi hàm `flip_text()`. Hộp văn bản thứ hai không tương tác, vì giá trị của nó không bao giờ được sử dụng làm đầu vào. Trong một số trường hợp, bạn có thể muốn ghi đè điều này, bạn có thể thực hiện bằng cách truyền một boolean đến tham số `interactive` của thành phần (ví dụ:`gr.Textbox(placeholder="Flip this text", interactive=True)`).

###
Tùy chỉnh bố cục của bản demo của bạn

Làm cách nào chúng ta có thể sử dụng `Blocks` để tùy chỉnh bố cục bản demo của mình? Theo mặc định, `Blocks` hiển thị các thành phần mà bạn tạo theo chiều dọc trong một cột. Bạn có thể thay đổi điều đó bằng cách tạo các cột bổ sung `with gradio.Column():` hoặc các hàng `with gradio.Row():` và tạo các thành phần trong các ngữ cảnh đó.

Đây là những gì bạn nên ghi nhớ: bất kỳ thành phần nào được tạo trong một `Column` (đây cũng là mặc định) sẽ được bố trí theo chiều dọc. Bất kỳ thành phần nào được tạo trong một `Row` sẽ được bố trí theo chiều ngang, tương tự như mô hình flexbox trong phát triển web.

Cuối cùng, bạn cũng có thể tạo các tab cho bản demo của mình bằng cách sử dụng trình quản lý ngữ cảnh `with gradio.Tabs()`. Trong ngữ cảnh này, bạn có thể tạo nhiều tab bằng cách chỉ định `with gradio.TabItem(name_of_tab):` children. Bất kỳ thành phần nào được tạo bên trong ngữ cảnh `with gradio.TabItem(name_of_tab):` sẽ xuất hiện trong tab đó.

Bây giờ, hãy thêm một hàm `flip_image()` vào bản demo của chúng ta và thêm một tab mới để lật hình ảnh. Dưới đây là một ví dụ với 2 tab và cũng sử dụng Row:

    import numpy as np
    import gradio as gr

    demo = gr.Blocks()

    def flip_text(x):
        return x[::-1]

    def flip_image(x):
        return np.fliplr(x)

    with demo:
        gr.Markdown("Flip text or image files using this demo.")
        with gr.Tabs():
            with gr.TabItem("Flip Text"):
                with gr.Row():
                    text_input = gr.Textbox()
                    text_output = gr.Textbox()
                text_button = gr.Button("Flip")
            with gr.TabItem("Flip Image"):
                with gr.Row():
                    image_input = gr.Image()
                    image_output = gr.Image()
                image_button = gr.Button("Flip")

        text_button.click(flip_text, inputs=text_input, outputs=text_output)
        image_button.click(flip_image, inputs=image_input, outputs=image_output)

    demo.launch()

Bạn sẽ nhận thấy rằng trong ví dụ này, chúng ta cũng đã tạo ra một thành phần `Button` trong mỗi tab và chỉ định một sự kiện nhấp chuột cho mỗi nút, đó là những gì thực sự chạy hàm.

###
Khám phá các sự kiện và trạng thái

Cũng giống như bạn có thể kiểm soát bố cục, `Blocks` cung cấp cho bạn khả năng kiểm soát chi tiết đối với những sự kiện nào kích hoạt hàm gọi. Mỗi thành phần và nhiều bố cục có các sự kiện cụ thể mà chúng hỗ trợ.

Ví dụ: thành phần `Textbox` có 2 sự kiện: `change()` (khi giá trị bên trong hộp văn bản thay đổi) và `submit()` (khi người dùng nhấn phím enter trong khi tập trung vào hộp văn bản). Các thành phần phức tạp hơn có thể có nhiều sự kiện hơn nữa: ví dụ: thành phần `Audio` cũng có các sự kiện riêng biệt khi tệp âm thanh được phát, xóa, tạm dừng, v.v. Xem tài liệu về các sự kiện mà mỗi thành phần hỗ trợ.

Bạn có thể đính kèm trình kích hoạt sự kiện cho không, một hoặc nhiều sự kiện này. Bạn tạo một trình kích hoạt sự kiện bằng cách gọi tên của sự kiện trên cá thể thành phần dưới dạng một hàm - ví dụ: `textbox.change(...)` hoặc `btn.click(...)`. Hàm nhận ba tham số, như đã thảo luận ở trên:

- `fn`: hàm để chạy
- `input`: một (danh sách) (các) thành phần có giá trị sẽ được cung cấp làm tham số đầu vào cho hàm. Giá trị của mỗi thành phần được ánh xạ tới tham số hàm tương ứng, theo thứ tự. Tham số này có thể là None nếu hàm không nhận bất kỳ tham số nào.
- `outputs`: một (danh sách) (các) thành phần có giá trị cần được cập nhật dựa trên các giá trị được trả về bởi hàm. Mỗi giá trị trả về đặt giá trị của thành phần tương ứng, theo thứ tự. Tham số này có thể là None nếu hàm không trả về bất kỳ thứ gì.

Bạn thậm chí có thể đặt thành phần đầu vào và đầu ra là thành phần giống nhau, như chúng ta làm trong ví dụ này sử dụng mô hình GPT để hoàn thành văn bản:

    import gradio as gr

    api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B")

    def complete_with_gpt(text):
        # Sử dụng 50 kí tự cuối của văn bản làm ngữ cảnh
        return text[:-50] + api(text[-50:])

    with gr.Blocks() as demo:
        textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4)
        btn = gr.Button("Generate")

        btn.click(complete_with_gpt, textbox, textbox)

    demo.launch()

###
Tạo bản demo đa bước

Trong một số trường hợp, bạn có thể muốn có bản demo đa bước, trong đó bạn sử dụng lại đầu ra của một hàm làm đầu vào cho hàm tiếp theo. Điều này thực sự dễ thực hiện với `Blocks`, vì bạn có thể sử dụng một thành phần cho đầu vào của một trình kích hoạt sự kiện nhưng lại là đầu ra của một thành phần khác. Hãy xem thành phần văn bản trong ví dụ bên dưới, giá trị của nó là kết quả đầu ra của mô hình nhận dạng giọng nói, nhưng cũng được truyền vào mô hình phân tích tình cảm:

    from transformers import pipeline

    import gradio as gr

    asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
    classifier = pipeline("text-classification")

    def speech_to_text(speech):
        text = asr(speech)["text"]
        return text

    def text_to_sentiment(text):
        return classifier(text)[0]["label"]

    demo = gr.Blocks()

    with demo:
        audio_file = gr.Audio(type="filepath")
        text = gr.Textbox()
        label = gr.Label()

        b1 = gr.Button("Recognize Speech")
        b2 = gr.Button("Classify Sentiment")

        b1.click(speech_to_text, inputs=audio_file, outputs=text)
        b2.click(text_to_sentiment, inputs=text, outputs=label)

    demo.launch()

###
Cập nhật Thuộc tính Thành phần

Cho đến nay, chúng ta đã thấy cách tạo các sự kiện để cập nhật giá trị của một thành phần khác. Nhưng điều gì sẽ xảy ra nếu bạn muốn thay đổi các thuộc tính khác của một thành phần, như khả năng hiển thị của hộp văn bản hoặc các lựa chọn trong nhóm nút radio? Bạn có thể thực hiện việc này bằng cách trả về phương thức `update()` của lớp thành phần thay vì giá trị trả về thông thường từ hàm của bạn.

Điều này được minh họa dễ dàng nhất bằng một ví dụ:

    import gradio as gr

    def change_textbox(choice):
        if choice == "short":
            return gr.Textbox.update(lines=2, visible=True)
        elif choice == "long":
            return gr.Textbox.update(lines=8, visible=True)
        else:
            return gr.Textbox.update(visible=False)

    with gr.Blocks() as block:
        radio = gr.Radio(
            ["short", "long", "none"], label="What kind of essay would you like to write?"
        )
        text = gr.Textbox(lines=2, interactive=True)

        radio.change(fn=change_textbox, inputs=radio, outputs=text)
        block.launch()

Chúng ta vừa khám phá tất cả các khái niệm cốt lõi của `Blocks`! Cũng giống như với `Interfaces`, bạn có thể tạo các bản demo thú vị có thể được chia sẻ bằng cách sử dụng `share=True` trong phương thức `launch()` hoặc triển khai trên Hugging Face Spaces.