1use crate::repository::ExportedRepository;
15use crate::runtime::ThreadRuntime;
16use crate::subscribe::{Subscribers, Subscription};
17use crate::types::ThreadMessage;
18use std::cell::{Cell, RefCell};
19use std::future::Future;
20use std::pin::Pin;
21use std::rc::{Rc, Weak};
22use std::sync::Arc;
23
24pub type Spawner = Rc<dyn Fn(Pin<Box<dyn Future<Output = ()>>>)>;
27
28pub type AdapterFuture<T> = Pin<Box<dyn Future<Output = Result<T, String>>>>;
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum ThreadStatus {
34 New,
36 Regular,
38 Archived,
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct ThreadItem {
45 pub id: String,
47 pub remote_id: Option<String>,
49 pub status: ThreadStatus,
51 pub title: Option<String>,
53}
54
55#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct RemoteThread {
58 pub remote_id: String,
60 pub title: Option<String>,
62 pub archived: bool,
64}
65
66pub trait ThreadListAdapter {
69 fn list(&self) -> AdapterFuture<Vec<RemoteThread>> {
71 Box::pin(async { Ok(Vec::new()) })
72 }
73 fn initialize(&self, local_id: &str) -> AdapterFuture<String> {
75 let id = local_id.to_owned();
76 Box::pin(async move { Ok(id) })
77 }
78 fn rename(&self, _remote_id: &str, _title: &str) -> AdapterFuture<()> {
80 Box::pin(async { Ok(()) })
81 }
82 fn archive(&self, _remote_id: &str) -> AdapterFuture<()> {
84 Box::pin(async { Ok(()) })
85 }
86 fn unarchive(&self, _remote_id: &str) -> AdapterFuture<()> {
88 Box::pin(async { Ok(()) })
89 }
90 fn delete(&self, _remote_id: &str) -> AdapterFuture<()> {
92 Box::pin(async { Ok(()) })
93 }
94 fn generate_title(
96 &self,
97 _remote_id: &str,
98 _messages: Vec<Arc<ThreadMessage>>,
99 ) -> AdapterFuture<Option<String>> {
100 Box::pin(async { Ok(None) })
101 }
102 fn load(&self, _remote_id: &str) -> AdapterFuture<ExportedRepository> {
104 Box::pin(async { Ok(ExportedRepository::default()) })
105 }
106}
107
108type Factory = dyn Fn(&str) -> Rc<dyn ThreadRuntime>;
109
110struct Entry {
111 item: ThreadItem,
112 runtime: Option<Rc<dyn ThreadRuntime>>,
113 _subscription: Option<Subscription>,
115 loaded: bool,
116 initializing: bool,
117 titled: bool,
119}
120
121struct State {
122 entries: Vec<Entry>,
123 main: String,
124 error: Option<String>,
125 loading: bool,
126}
127
128struct Inner {
129 me: Weak<Inner>,
130 factory: Box<Factory>,
131 adapter: Option<Rc<dyn ThreadListAdapter>>,
132 spawner: Spawner,
133 state: RefCell<State>,
134 subscribers: Rc<Subscribers>,
135 next_id: Cell<u64>,
136}
137
138#[derive(Clone)]
140pub struct ThreadList {
141 inner: Rc<Inner>,
142}
143
144impl ThreadList {
145 pub fn new(
148 factory: impl Fn(&str) -> Rc<dyn ThreadRuntime> + 'static,
149 spawner: Spawner,
150 adapter: Option<Rc<dyn ThreadListAdapter>>,
151 ) -> Self {
152 let list = Self {
153 inner: Rc::new_cyclic(|me| Inner {
154 me: me.clone(),
155 factory: Box::new(factory),
156 adapter,
157 spawner,
158 state: RefCell::new(State {
159 entries: Vec::new(),
160 main: String::new(),
161 error: None,
162 loading: false,
163 }),
164 subscribers: Rc::default(),
165 next_id: Cell::new(1),
166 }),
167 };
168 let id = list.inner.add_new_entry();
169 list.inner.state.borrow_mut().main = id;
170 if list.inner.adapter.is_some() {
171 list.refresh();
172 }
173 list
174 }
175
176 pub fn subscribe(&self, callback: impl Fn() + 'static) -> Subscription {
178 self.inner.subscribers.subscribe(callback)
179 }
180
181 pub fn items(&self) -> Vec<ThreadItem> {
183 self.inner
184 .state
185 .borrow()
186 .entries
187 .iter()
188 .map(|e| e.item.clone())
189 .collect()
190 }
191 pub fn regular(&self) -> Vec<ThreadItem> {
193 self.with_status(ThreadStatus::Regular)
194 }
195 pub fn archived(&self) -> Vec<ThreadItem> {
197 self.with_status(ThreadStatus::Archived)
198 }
199 fn with_status(&self, status: ThreadStatus) -> Vec<ThreadItem> {
200 self.items()
201 .into_iter()
202 .filter(|i| i.status == status)
203 .collect()
204 }
205 pub fn item(&self, id: &str) -> Option<ThreadItem> {
207 self.items().into_iter().find(|i| i.id == id)
208 }
209 pub fn main_id(&self) -> String {
211 self.inner.state.borrow().main.clone()
212 }
213 pub fn main(&self) -> Rc<dyn ThreadRuntime> {
215 let id = self.main_id();
216 self.inner.runtime(&id).expect("the main thread exists")
217 }
218 pub fn runtime(&self, id: &str) -> Option<Rc<dyn ThreadRuntime>> {
220 self.inner.runtime(id)
221 }
222 pub fn is_loading(&self) -> bool {
224 self.inner.state.borrow().loading
225 }
226 pub fn last_error(&self) -> Option<String> {
228 self.inner.state.borrow().error.clone()
229 }
230
231 pub fn refresh(&self) {
233 let Some(adapter) = self.inner.adapter.clone() else {
234 return;
235 };
236 self.inner.state.borrow_mut().loading = true;
237 self.inner.notify();
238 let weak = self.inner.me.clone();
239 self.inner.spawn(async move {
240 let result = adapter.list().await;
241 let Some(inner) = weak.upgrade() else { return };
242 {
243 let mut state = inner.state.borrow_mut();
244 state.loading = false;
245 match result {
246 Ok(threads) => {
247 let mut added = Vec::new();
248 for thread in threads {
249 if state
250 .entries
251 .iter()
252 .any(|e| e.item.remote_id.as_deref() == Some(&thread.remote_id))
253 {
254 continue;
255 }
256 added.push(thread);
257 }
258 for thread in added {
259 let id = format!("thread-{}", inner.next_id.get());
260 inner.next_id.set(inner.next_id.get() + 1);
261 state.entries.push(Entry {
262 item: ThreadItem {
263 id,
264 remote_id: Some(thread.remote_id),
265 status: if thread.archived {
266 ThreadStatus::Archived
267 } else {
268 ThreadStatus::Regular
269 },
270 title: thread.title,
271 },
272 runtime: None,
273 _subscription: None,
274 loaded: false,
275 initializing: false,
276 titled: false,
277 });
278 }
279 }
280 Err(error) => state.error = Some(error),
281 }
282 }
283 inner.notify();
284 });
285 }
286
287 pub fn switch_to_new(&self) {
289 let existing = {
290 let state = self.inner.state.borrow();
291 state
292 .entries
293 .iter()
294 .find(|e| e.item.status == ThreadStatus::New)
295 .map(|e| e.item.id.clone())
296 };
297 let id = match existing {
298 Some(id) => id,
299 None => self.inner.add_new_entry(),
300 };
301 self.switch_to(&id);
302 }
303
304 pub fn switch_to(&self, id: &str) {
306 let known = self
307 .inner
308 .state
309 .borrow()
310 .entries
311 .iter()
312 .any(|e| e.item.id == id);
313 if !known {
314 return;
315 }
316 self.inner.state.borrow_mut().main = id.to_owned();
317 self.inner.load_history(id);
318 self.inner.notify();
319 }
320
321 pub fn rename(&self, id: &str, title: &str) {
323 let title = title.trim().to_owned();
324 let (previous, remote) = {
325 let mut state = self.inner.state.borrow_mut();
326 let Some(entry) = state.entries.iter_mut().find(|e| e.item.id == id) else {
327 return;
328 };
329 let previous = std::mem::replace(
330 &mut entry.item.title,
331 (!title.is_empty()).then(|| title.clone()),
332 );
333 (previous, entry.item.remote_id.clone())
334 };
335 self.inner.notify();
336 if let (Some(adapter), Some(remote)) = (self.inner.adapter.clone(), remote) {
337 let id = id.to_owned();
338 self.inner
339 .persist(adapter.rename(&remote, &title), move |inner| {
340 inner.with_entry(&id, |e| e.item.title = previous);
341 });
342 }
343 }
344
345 pub fn archive(&self, id: &str) {
347 self.set_archived(id, true);
348 }
349 pub fn unarchive(&self, id: &str) {
351 self.set_archived(id, false);
352 }
353 fn set_archived(&self, id: &str, archived: bool) {
354 let (previous, remote) = {
355 let mut state = self.inner.state.borrow_mut();
356 let Some(entry) = state.entries.iter_mut().find(|e| e.item.id == id) else {
357 return;
358 };
359 let wanted = if archived {
360 ThreadStatus::Archived
361 } else {
362 ThreadStatus::Regular
363 };
364 if entry.item.status != ThreadStatus::Regular
365 && entry.item.status != ThreadStatus::Archived
366 {
367 return;
368 }
369 let previous = std::mem::replace(&mut entry.item.status, wanted);
370 (previous, entry.item.remote_id.clone())
371 };
372 if archived && self.main_id() == id {
374 self.switch_to_new();
375 }
376 self.inner.notify();
377 if let (Some(adapter), Some(remote)) = (self.inner.adapter.clone(), remote) {
378 let id = id.to_owned();
379 let future = if archived {
380 adapter.archive(&remote)
381 } else {
382 adapter.unarchive(&remote)
383 };
384 self.inner.persist(future, move |inner| {
385 inner.with_entry(&id, |e| e.item.status = previous);
386 });
387 }
388 }
389
390 pub fn delete(&self, id: &str) {
392 let removed = {
393 let mut state = self.inner.state.borrow_mut();
394 let Some(index) = state.entries.iter().position(|e| e.item.id == id) else {
395 return;
396 };
397 (index, state.entries.remove(index))
398 };
399 if self.main_id() == id {
400 self.switch_to_new();
401 } else {
402 self.inner.notify();
403 }
404 if let (Some(adapter), Some(remote)) =
405 (self.inner.adapter.clone(), removed.1.item.remote_id.clone())
406 {
407 let (index, mut entry) = removed;
408 entry.runtime = None;
410 entry._subscription = None;
411 entry.loaded = false;
412 self.inner.persist(adapter.delete(&remote), move |inner| {
413 let mut state = inner.state.borrow_mut();
414 let index = index.min(state.entries.len());
415 state.entries.insert(index, entry);
416 });
417 }
418 }
419}
420
421impl Inner {
422 fn notify(&self) {
423 self.subscribers.notify();
424 }
425
426 fn spawn(&self, future: impl Future<Output = ()> + 'static) {
427 (self.spawner)(Box::pin(future));
428 }
429
430 fn with_entry(&self, id: &str, change: impl FnOnce(&mut Entry)) {
431 if let Some(entry) = self
432 .state
433 .borrow_mut()
434 .entries
435 .iter_mut()
436 .find(|e| e.item.id == id)
437 {
438 change(entry);
439 }
440 }
441
442 fn add_new_entry(&self) -> String {
443 let id = format!("thread-{}", self.next_id.get());
444 self.next_id.set(self.next_id.get() + 1);
445 self.state.borrow_mut().entries.insert(
446 0,
447 Entry {
448 item: ThreadItem {
449 id: id.clone(),
450 remote_id: None,
451 status: ThreadStatus::New,
452 title: None,
453 },
454 runtime: None,
455 _subscription: None,
456 loaded: true,
457 initializing: false,
458 titled: false,
459 },
460 );
461 id
462 }
463
464 fn persist(&self, task: AdapterFuture<()>, undo: impl FnOnce(&Inner) + 'static) {
466 let weak = self.me.clone();
467 self.spawn(async move {
468 let result = task.await;
469 let Some(inner) = weak.upgrade() else { return };
470 match result {
471 Ok(()) => inner.state.borrow_mut().error = None,
472 Err(error) => {
473 undo(&inner);
474 inner.state.borrow_mut().error = Some(error);
475 }
476 }
477 inner.notify();
478 });
479 }
480
481 fn runtime(&self, id: &str) -> Option<Rc<dyn ThreadRuntime>> {
483 if let Some(runtime) = self
484 .state
485 .borrow()
486 .entries
487 .iter()
488 .find(|e| e.item.id == id)
489 .map(|e| e.runtime.clone())?
490 {
491 return Some(runtime);
492 }
493 let runtime = (self.factory)(id);
494 let weak = self.me.clone();
495 let thread = id.to_owned();
496 let subscription = runtime.subscribe_dyn(Box::new(move || {
497 if let Some(inner) = weak.upgrade() {
498 inner.thread_changed(&thread);
499 }
500 }));
501 let mut state = self.state.borrow_mut();
502 let entry = state.entries.iter_mut().find(|e| e.item.id == id)?;
503 entry.runtime = Some(runtime.clone());
504 entry._subscription = Some(subscription);
505 Some(runtime)
506 }
507
508 fn load_history(&self, id: &str) {
510 let Some(adapter) = self.adapter.clone() else {
511 let _ = self.runtime(id);
512 return;
513 };
514 let remote = {
515 let mut state = self.state.borrow_mut();
516 let Some(entry) = state.entries.iter_mut().find(|e| e.item.id == id) else {
517 return;
518 };
519 match (&entry.item.remote_id, entry.loaded) {
520 (Some(remote), false) => {
521 entry.loaded = true;
522 Some(remote.clone())
523 }
524 _ => None,
525 }
526 };
527 let Some(runtime) = self.runtime(id) else {
528 return;
529 };
530 let Some(remote) = remote else { return };
531 let (weak, id) = (self.me.clone(), id.to_owned());
532 self.spawn(async move {
533 let result = adapter.load(&remote).await;
534 let Some(inner) = weak.upgrade() else { return };
535 match result {
536 Ok(history) => {
537 if runtime.messages().is_empty() && runtime.import(history).is_err() {
539 inner.state.borrow_mut().error = Some("could not load the thread".into());
540 }
541 }
542 Err(error) => {
543 inner.with_entry(&id, |e| e.loaded = false);
544 inner.state.borrow_mut().error = Some(error);
545 }
546 }
547 inner.notify();
548 });
549 }
550
551 fn thread_changed(&self, id: &str) {
554 let Some(runtime) = self
555 .state
556 .borrow()
557 .entries
558 .iter()
559 .find(|e| e.item.id == id)
560 .and_then(|e| e.runtime.clone())
561 else {
562 return;
563 };
564 let messages = runtime.messages();
565 let running = runtime.is_running();
566 let (register, title_for) = {
567 let mut state = self.state.borrow_mut();
568 let Some(entry) = state.entries.iter_mut().find(|e| e.item.id == id) else {
569 return;
570 };
571 let mut register = false;
572 if entry.item.status == ThreadStatus::New && !messages.is_empty() {
573 entry.item.status = ThreadStatus::Regular;
574 register = !entry.initializing && entry.item.remote_id.is_none();
575 entry.initializing |= register;
576 }
577 let answered = !running
578 && messages
579 .iter()
580 .any(|m| m.as_assistant().is_some_and(|a| a.status.is_terminal()));
581 let title_for =
582 (answered && entry.item.title.is_none() && !entry.titled && !entry.initializing)
583 .then(|| entry.item.remote_id.clone())
584 .flatten();
585 if title_for.is_some() {
586 entry.titled = true;
587 }
588 (register, title_for)
589 };
590 if register {
591 self.initialize(id);
592 }
593 if let Some(remote) = title_for {
594 self.generate_title(id, &remote, messages.iter().cloned().collect());
595 }
596 self.notify();
597 }
598
599 fn initialize(&self, id: &str) {
600 let Some(adapter) = self.adapter.clone() else {
601 self.with_entry(id, |e| e.initializing = false);
602 return;
603 };
604 let (weak, id) = (self.me.clone(), id.to_owned());
605 self.spawn(async move {
606 let result = adapter.initialize(&id).await;
607 let Some(inner) = weak.upgrade() else { return };
608 match result {
609 Ok(remote) => inner.with_entry(&id, |e| {
610 e.item.remote_id = Some(remote);
611 e.initializing = false;
612 }),
613 Err(error) => {
614 inner.with_entry(&id, |e| e.initializing = false);
615 inner.state.borrow_mut().error = Some(error);
616 }
617 }
618 inner.thread_changed(&id);
620 inner.notify();
621 });
622 }
623
624 fn generate_title(&self, id: &str, remote: &str, messages: Vec<Arc<ThreadMessage>>) {
625 let Some(adapter) = self.adapter.clone() else {
626 return;
627 };
628 let (weak, id, remote) = (self.me.clone(), id.to_owned(), remote.to_owned());
629 self.spawn(async move {
630 let result = adapter.generate_title(&remote, messages).await;
631 let Some(inner) = weak.upgrade() else { return };
632 match result {
633 Ok(Some(title)) => {
634 inner.with_entry(&id, |e| {
635 if e.item.title.is_none() {
636 e.item.title = Some(title.clone());
637 }
638 });
639 let persist = adapter.rename(&remote, &title);
640 inner.persist(persist, |_| {});
641 }
642 Ok(None) => {}
643 Err(error) => inner.state.borrow_mut().error = Some(error),
644 }
645 inner.notify();
646 });
647 }
648}