1pub mod assistant_panel;
2mod assistant_settings;
3mod codegen;
4mod prompts;
5mod streaming_diff;
6
7use ai::providers::open_ai::Role;
8use anyhow::Result;
9pub use assistant_panel::AssistantPanel;
10use assistant_settings::OpenAiModel;
11use chrono::{DateTime, Local};
12use collections::HashMap;
13use fs::Fs;
14use futures::StreamExt;
15use gpui::{actions, AppContext, SharedString};
16use regex::Regex;
17use serde::{Deserialize, Serialize};
18use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
19use util::paths::CONVERSATIONS_DIR;
20
21actions!(
22 assistant,
23 [
24 NewConversation,
25 Assist,
26 Split,
27 CycleMessageRole,
28 QuoteSelection,
29 ToggleFocus,
30 ResetKey,
31 InlineAssist,
32 ToggleIncludeConversation,
33 ToggleRetrieveContext,
34 ]
35);
36
37#[derive(
38 Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
39)]
40struct MessageId(usize);
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
43struct MessageMetadata {
44 role: Role,
45 sent_at: DateTime<Local>,
46 status: MessageStatus,
47}
48
49#[derive(Clone, Debug, Serialize, Deserialize)]
50enum MessageStatus {
51 Pending,
52 Done,
53 Error(SharedString),
54}
55
56#[derive(Serialize, Deserialize)]
57struct SavedMessage {
58 id: MessageId,
59 start: usize,
60}
61
62#[derive(Serialize, Deserialize)]
63struct SavedConversation {
64 id: Option<String>,
65 zed: String,
66 version: String,
67 text: String,
68 messages: Vec<SavedMessage>,
69 message_metadata: HashMap<MessageId, MessageMetadata>,
70 summary: String,
71 api_url: Option<String>,
72 model: OpenAiModel,
73}
74
75impl SavedConversation {
76 const VERSION: &'static str = "0.1.0";
77}
78
79struct SavedConversationMetadata {
80 title: String,
81 path: PathBuf,
82 mtime: chrono::DateTime<chrono::Local>,
83}
84
85impl SavedConversationMetadata {
86 pub async fn list(fs: Arc<dyn Fs>) -> Result<Vec<Self>> {
87 fs.create_dir(&CONVERSATIONS_DIR).await?;
88
89 let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
90 let mut conversations = Vec::<SavedConversationMetadata>::new();
91 while let Some(path) = paths.next().await {
92 let path = path?;
93 if path.extension() != Some(OsStr::new("json")) {
94 continue;
95 }
96
97 let pattern = r" - \d+.zed.json$";
98 let re = Regex::new(pattern).unwrap();
99
100 let metadata = fs.metadata(&path).await?;
101 if let Some((file_name, metadata)) = path
102 .file_name()
103 .and_then(|name| name.to_str())
104 .zip(metadata)
105 {
106 let title = re.replace(file_name, "");
107 conversations.push(Self {
108 title: title.into_owned(),
109 path,
110 mtime: metadata.mtime.into(),
111 });
112 }
113 }
114 conversations.sort_unstable_by_key(|conversation| Reverse(conversation.mtime));
115
116 Ok(conversations)
117 }
118}
119
120pub fn init(cx: &mut AppContext) {
121 assistant_panel::init(cx);
122}
123
124#[cfg(test)]
125#[ctor::ctor]
126fn init_logger() {
127 if std::env::var("RUST_LOG").is_ok() {
128 env_logger::init();
129 }
130}