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()
115 .span(msg.content.clone())
116 .into_element()
117 } else {
118 MarkdownViewer::new(msg.content.clone()).into_element()
119 }),
120 )
121 .into()
122 }),
123 )),
124 );
125
126 let input_area = rect()
127 .width(Size::fill())
128 .height(Size::px(60.))
129 .padding(12.)
130 .child(
131 rect()
132 .horizontal()
133 .expanded()
134 .cross_align(Alignment::Center)
135 .spacing(8.)
136 .content(Content::Flex)
137 .child(
138 Input::new(input_value)
139 .background((65, 65, 65))
140 .focus_background((75, 75, 75))
141 .border_fill(Color::TRANSPARENT)
142 .color((200, 200, 200))
143 .placeholder("Type your message...")
144 .width(Size::flex(1.)),
145 )
146 .child(
147 Button::new()
148 .background((65, 65, 65))
149 .hover_background((75, 75, 75))
150 .border_fill(Color::TRANSPARENT)
151 .color((200, 200, 200))
152 .on_press(send_message)
153 .child("Send"),
154 ),
155 );
156
157 rect()
158 .expanded()
159 .content(Content::Flex)
160 .background((30, 30, 30))
161 .child(chat_area)
162 .child(input_area)
163}