1#![cfg_attr(
2 all(not(debug_assertions), target_os = "windows"),
3 windows_subsystem = "windows"
4)]
5
6use freya::prelude::*;
7use rig::{
8 client::{
9 CompletionClient,
10 ProviderClient,
11 },
12 completion::Prompt,
13 providers::openai,
14};
15use tokio::runtime::Builder;
16
17#[derive(Clone, Debug, PartialEq)]
18enum Role {
19 AI,
20 User,
21}
22
23#[derive(Clone, Debug)]
24struct Message {
25 role: Role,
26 content: String,
27}
28
29fn main() {
30 let rt = Builder::new_multi_thread().enable_all().build().unwrap();
31 let _rt = rt.enter();
32 launch(LaunchConfig::new().with_window(WindowConfig::new(app).with_size(800., 600.)))
33}
34
35fn app() -> impl IntoElement {
36 let mut messages = use_state(|| {
37 vec![Message {
38 role: Role::AI,
39 content: "Hello! I'm a AI chat. Type a message and press Send to see a response."
40 .to_string(),
41 }]
42 });
43 let mut input_value = use_state(|| String::new());
44
45 let send_message = move |_| {
46 let user_message = input_value.read().clone();
47 if user_message.trim().is_empty() {
48 return;
49 }
50
51 messages.write().push(Message {
53 role: Role::User,
54 content: user_message.clone(),
55 });
56
57 *input_value.write() = String::new();
59
60 let user_msg = user_message.clone();
62 spawn(async move {
63 let client = openai::Client::from_env();
64 let agent = client.agent("gpt-5.2").build();
65 match agent.prompt(&user_msg).await {
66 Ok(response) => {
67 messages.write().push(Message {
68 role: Role::AI,
69 content: response,
70 });
71 }
72 Err(e) => {
73 messages.write().push(Message {
74 role: Role::AI,
75 content: format!("Error: {}", e),
76 });
77 }
78 }
79 });
80 };
81
82 let chat_area = rect().width(Size::fill()).height(Size::flex(1.)).child(
83 ScrollView::new().child(rect().width(Size::fill()).padding(16.).children(
84 messages.read().iter().map(|msg| {
85 let is_user = msg.role == Role::User;
86 let bg_color = if is_user {
87 (59, 130, 246)
88 } else {
89 (55, 65, 81)
90 };
91 let align = if is_user {
92 Alignment::End
93 } else {
94 Alignment::Start
95 };
96 let text_align = if is_user {
97 TextAlign::End
98 } else {
99 TextAlign::Start
100 };
101
102 rect()
103 .width(Size::fill())
104 .margin(8.)
105 .cross_align(align)
106 .child(
107 rect()
108 .padding(12.)
109 .background(bg_color)
110 .corner_radius(16.)
111 .color((255, 255, 255))
112 .text_align(text_align)
113 .child(if is_user {
114 SelectableText::new(msg.content.clone()).into_element()
115 } else {
116 MarkdownViewer::new(msg.content.clone()).into_element()
117 }),
118 )
119 .into()
120 }),
121 )),
122 );
123
124 let input_area = rect()
125 .width(Size::fill())
126 .height(Size::px(60.))
127 .padding(12.)
128 .child(
129 rect()
130 .horizontal()
131 .expanded()
132 .cross_align(Alignment::Center)
133 .spacing(8.)
134 .content(Content::Flex)
135 .child(
136 Input::new()
137 .value(input_value)
138 .on_change(move |value| {
139 *input_value.write() = value;
140 })
141 .background((65, 65, 65))
142 .hover_background((75, 75, 75))
143 .border_fill(Color::TRANSPARENT)
144 .color((200, 200, 200))
145 .placeholder("Type your message...")
146 .width(Size::flex(1.)),
147 )
148 .child(
149 Button::new()
150 .background((65, 65, 65))
151 .hover_background((75, 75, 75))
152 .border_fill(Color::TRANSPARENT)
153 .color((200, 200, 200))
154 .on_press(send_message)
155 .child("Send"),
156 ),
157 );
158
159 rect()
160 .expanded()
161 .content(Content::Flex)
162 .background((30, 30, 30))
163 .child(chat_area)
164 .child(input_area)
165}