iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
生成式 AI

Multi-Agent 實戰:開發多代理智慧小幫手系列 第 28

【Day 28】 小專案 - 前端聊天介面

  • 分享至 

  • xImage
  •  

截止到這篇,我們已經完成了後端的部分。
接下來,要讓我們的 Agent 真的能在前端上與使用者互動
這篇會示範如何在 Android 前端中,建立一個簡單的 聊天介面
使用者輸入訊息並按下「發送」後,畫面上就會出現一則回覆(目前先模擬 Agent 的回應)。

前端會使用 Java 編寫 Android 程式。

建立聊天介面

我們先從最基本的聊天畫面開始。
每一則訊息都會以「對話氣泡」的方式呈現,並區分 UserAgent

設計對話框背景顏色

res/drawable 資料夾中新增以下兩個檔案,分別設定使用者與 Agent 的對話框樣式。

  • agent_message_bg.xml(Agent 對話框)
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    
        <solid android:color="#2196F3"/> <!-- 藍色 -->
        <corners android:radius="16dp"/>
        <padding
            android:left="12dp"
            android:top="8dp"
            android:right="12dp"
            android:bottom="8dp" />
    </shape>
    
  • user_message_bg.xml(User 對話框)
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    
        <solid android:color="#4CAF50"/> <!-- 綠色 -->
        <corners android:radius="16dp"/>
        <padding
            android:left="12dp"
            android:top="8dp"
            android:right="12dp"
            android:bottom="8dp" />
    </shape>
    

單則訊息 (item_message.xml)

這是每一條訊息在 RecyclerView 裡的樣式。
它包含一個 Agent 的氣泡與一個 User 的氣泡(實際顯示時會擇一顯示)。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp">

    <!-- Agent 訊息 -->
    <TextView
        android:id="@+id/itemMessage_bot_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/agent_message_bg"
        android:padding="8dp"
        android:textColor="#FFFFFF"
        android:layout_alignParentStart="true"
        android:visibility="gone" />

    <!-- User 訊息 -->
    <TextView
        android:id="@+id/itemMessage_user_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/user_message_bg"
        android:padding="8dp"
        android:textColor="#FFFFFF"
        android:layout_alignParentEnd="true"
        android:visibility="gone" />
</RelativeLayout>

主畫面 (activity_main.xml)

主畫面包含:

  • 一個 RecyclerView(顯示聊天內容)
  • 一個輸入框與「發送」按鈕(輸入與送出訊息)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp">

    <!-- RecyclerView 用於顯示聊天訊息 -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/main_chat_rv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scrollbars="vertical" />

    <!-- 輸入欄位 + 發送按鈕 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.1"
        android:orientation="horizontal"
        android:paddingTop="4dp">

        <EditText
            android:id="@+id/main_message_et"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:hint="輸入訊息..." />

        <Button
            android:id="@+id/main_send_btn"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0.3"
            android:text="發送" />
    </LinearLayout>
</LinearLayout>

功能邏輯

MainActivity.java

這裡是前端邏輯的核心,負責控制整個聊天流程。

public class MainActivity extends AppCompatActivity {

    private RecyclerView chatRv;
    private EditText messageEt;
    private Button sendbtn;
    private MessageAdapter adapter;
    private ArrayList<Message> messageList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 綁定 UI 元件
        chatRv = findViewById(R.id.main_chat_rv);
        messageEt = findViewById(R.id.main_message_et);
        sendbtn = findViewById(R.id.main_send_btn);

        // 初始化訊息清單與 Adapter
        messageList = new ArrayList<>();
        adapter = new MessageAdapter(messageList);

        // 設定 RecyclerView
        chatRv.setLayoutManager(new LinearLayoutManager(this));
        chatRv.setAdapter(adapter);

        // 設定按鈕點擊事件
        sendbtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String msg = messageEt.getText().toString(); // 取得輸入文字
                if (!msg.isEmpty()) { // 確保文字不是空的
                    // 加入 user 訊息
                    messageList.add(new Message(msg, true));
                    adapter.notifyItemInserted(messageList.size() - 1);
                    chatRv.scrollToPosition(messageList.size() - 1);
                    messageEt.setText(""); // 清空輸入框

                    // 模擬 Agent 回覆
                    String botReply = "你說的是: " + msg;
                    messageList.add(new Message(botReply, false));
                    adapter.notifyItemInserted(messageList.size() - 1);
                    chatRv.scrollToPosition(messageList.size() - 1);
                }
            }
        });
    }
}

MessageAdapter.java

這個 Adapter 會負責控制訊息的顯示方向與樣式。

public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.ViewHolder> {
    private List<Message> messageList; // 用來儲存所有訊息的清單

    // 建構子
    public MessageAdapter(List<Message> messageList) {
        this.messageList = messageList;
    }

    // 用來儲存單一訊息項目的 UI 元件
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView userTv;  // 顯示 user 訊息的 TextView
        public TextView agentTv;  // 顯示 Agent 訊息的 TextView

        public ViewHolder(View view) {
            super(view);
            // 綁定 UI 元件
            userTv = view.findViewById(R.id.itemMessage_user_tv);
            agentTv = view.findViewById(R.id.itemMessage_bot_tv);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 將 item_message.xml 佈局轉換成可顯示的 View
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_message, parent, false);
        return new ViewHolder(view); // 回傳 ViewHolder
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // 綁定每一筆資料到畫面上
        Message msg = messageList.get(position); // 取得當前訊息物件

        if (msg.isUser()) {
            // 若是 user 的訊息時,顯示 user 文字,隱藏 Agent 文字
            holder.userTv.setText(msg.getContent());
            holder.userTv.setVisibility(View.VISIBLE);
            holder.agentTv.setVisibility(View.GONE);
        } else {
            // 若是 Agent 訊息時,顯示 Agent 文字,隱藏 user 文字
            holder.agentTv.setText(msg.getContent());
            holder.agentTv.setVisibility(View.VISIBLE);
            holder.userTv.setVisibility(View.GONE);
        }
    }

    @Override
    public int getItemCount() {
        // 回傳訊息總數,用來告訴 RecyclerView 要顯示幾項
        return messageList.size();
    }

Message.java

這個是用來儲存每一則訊息的內容與屬性。

public class Message {
    private String content;   // 訊息文字內容
    private boolean isUser;   // 用來判斷這則訊息是 user 還是 Agent (true = user, false = Agent)

    // 建構子,用來建立 Message 物件
    public Message(String content, boolean isUser) {
        this.content = content;  
        this.isUser = isUser;
    }

    // 取得訊息內容
    public String getContent() {
        return content;
    }

    // 回傳是否為 user 訊息
    public boolean isUser() {
        return isUser;
    }
}

畫面展示

  • APP畫面
    https://ithelp.ithome.com.tw/upload/images/20251012/20168456tYyvnqmXDT.png
  • 模擬結果
    使用者輸入文字後,Agent 會回覆同一句話(模擬回覆)。
    https://ithelp.ithome.com.tw/upload/images/20251012/20168456QoKBJ7YGvi.png

上一篇
【Day 27】 小專案 - 將 Master Agent 改寫成可供前端呼叫的 API
系列文
Multi-Agent 實戰:開發多代理智慧小幫手28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言