iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
AI & Data

Rust 加 MLOps,你說有沒有搞頭?系列 第 14

[Day 14] - 鋼鐵草泥馬 🦙 LLM chatbot 🤖 (5/10)|Signal & Action

  • 分享至 

  • xImage
  •  

今日份 Ferris

祝大家中秋節快樂!
/images/emoticon/emoticon61.gif
果然還是要跨過中秋連假才有參加鐵人賽的感覺啊哈哈哈哈

今天要為前端建立基本的架構,而重點就是要讓其具有 Reactivity,也就是當資料變化時,視覺介面會自動更新的特性。
這項特性可以讓開發者更輕鬆地建立動態的網頁,並提供使用者更好的體驗。
Ferris 也在遠端發送 Signal 親自指導:
https://ithelp.ithome.com.tw/upload/images/20230929/20141304IRAX6dleJv.jpg

拆掉模板

🏮 今天完整的程式碼可以拉到最底下 Put it together 區塊或是在 GitHub 找到。

現在的 app.rs 中有很多自動生成的程式碼是我們不需要的,這裡要把它們先刪掉,由上到下分別是:

  1. Router - 把 // content for this welcome page 底下 <Router></Router> 之間的程式碼都刪掉。
  2. HomePage - 把 /// Renders the home page of your application. 下方的 component 整個刪掉。
  3. NotFound - 把 /// 404 - Not Found 下方的 component 也刪掉。

而我們的前端只有三個主要部件,

  • 問答的紀錄
  • 輸入問題的地方
  • 將上面兩者串連的 app 部分

所以這裡只需要在 App component 的下方加上我們需要的部件就好,請把下面的程式碼加到 <Title text="Iron LLaMa"/> 下面:

// basic layout
<MessageHistory/>
<MessageInputField/>

作為前端的兩個主要區塊。

Working with Signals

建立好架構之後,就可以為頁面的主要部件來實作一些邏輯了。
首先要為對話建立一個 signal,程式碼請放在 app.rsApp component 裡面(provide_meta_context(); 下面):

let (conversaton, set_conversaton) = create_signal(Conversation::new());

正如 [Day 12] - 鋼鐵草泥馬 🦙 LLM chatbot 🤖|Leptos 小教室 所介紹的 create_signal 會回傳一個 tuple。
其中第一個元素的型別為 ReadSignal,第二個為 WriteSignal,分別對應到讀寫操作

若我們想要,可以透過 conversaton.get() (或 conversation()) 將訊息內容印在 view! 模板中。
而從這個 view 回傳的頁面,會在有人更新這個 signal 時自動重新渲染,這就是 Leptos 中 Reactivity 的體現。

因為有用到 Conversation 所以記得在檔案上方引入 use crate::model::conversation::Conversation;

Mutating Data with Actions

接著要建立所謂的 action,因為這個 App 基本上都要靠使用者輸入問題並按下送出才會開始一系列的動作:

  1. 把使用者新的 prompt 加入 Conversation 中,讓我們可以在歷史訊息的區塊中看見它
  2. 把這個 prompt 送到伺服器端,以使用 LLM 進行推論,然後再把結果送回來 (此時前端就得等待結果回來)
  3. 收到結果之後 (user: false),要再把它加入 Conversation 中,讓我們可以在歷史訊息的區塊中看見它

而在 leptos 中 action 就是能處理這個流程的工具。
這裡我們把這個 action 取名為 send

    let send = create_action(move |new_message: &String| {
        let user_message = Message {
            text: new_message.clone(),
            user: true,
        };
        
        set_conversation.update(move |c| c.messages.push(user_message));
        async move { todo!("converse") }
    });

其中 create_action 的引數是一個閉包,而這個閉包的輸入值就是要給 Signal 的輸入值,我們在這裡用它建立一個新的訊息 user_message
而後面就可以透過 set_conversation 把新建立的 Message 更新上去,這裡 .update 方法也是以一個閉包做為引數,這個閉包的輸入值則是 Signal 當前的數值。
而最後的 todo! 則留待後面再實作,它的功能基本上就是把對話送到伺服器端,然後等到伺服器端回傳結果後再進行處理。

Put it together

最後,雖然還沒有實作 <MessageHistory/><MessageInputField/> component,但前面的 action 要給輸入區塊 <MessageInputField send/>,這樣在使用者按下送出鈕後,才會啟動一連串的動作。
而 conversation 則要給 <MessageHistory conversation/> ,這樣這個區塊才會在每次 Conversation 更新時自動重新渲染。
所以今天最後 app.rs 會長這樣:

use leptos::*;
use leptos_meta::*;

use crate::model::conversation::{Conversation, Message};

#[component]
pub fn App() -> impl IntoView {
    // Provides context that manages stylesheets, titles, meta tags, etc.
    provide_meta_context();

    let (conversation, set_conversation) = create_signal(Conversation::new());

    let send = create_action(move |new_message: &String| {
        let user_message = Message {
            text: new_message.clone(),
            user: true,
        };

        set_conversation.update(move |c| c.messages.push(user_message));
        async move { todo!("converse") }
    });

    view! {
        // injects a stylesheet into the document <head>
        // id=leptos means cargo-leptos will hot-reload this stylesheet
        <Stylesheet id="leptos" href="/pkg/iron_llama.css"/>

        // sets the document title
        <Title text="Iron LLaMa"/>

        <MessageHistory conversation/>
        <MessageInputField send/>
    }
}

好啦,明天要開始建立後端的邏輯了,明天見囉~
/images/emoticon/emoticon07.gif

寫到這邊才發現今天都沒有迷因,但還是補一張好了
meme
雖然一點意義都沒有哈哈哈
https://ithelp.ithome.com.tw/upload/images/20230929/201413046eQd7Myku8.png


上一篇
[Day 13] - 鋼鐵草泥馬 🦙 LLM chatbot 🤖 (4/10)|對話の資料結構
下一篇
[Day 15] - 鋼鐵草泥馬 🦙 LLM chatbot 🤖 (6/10)|GGML 量化 LLaMa
系列文
Rust 加 MLOps,你說有沒有搞頭?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言