assistant2: Add thread history (#21599)

This PR adds support for thread history to the Assistant 2 panel.

We also now generate summaries for the threads.

<img width="986" alt="Screenshot 2024-12-05 at 12 56 53 PM"
src="https://github.com/user-attachments/assets/46cb1309-38a2-4ab9-9fcc-c1275d4b5f2c">

<img width="986" alt="Screenshot 2024-12-05 at 12 56 58 PM"
src="https://github.com/user-attachments/assets/8c91ba57-a6c5-4b88-be05-b22fb615ece5">

Release Notes:

- N/A

---------

Co-authored-by: Piotr <piotr@zed.dev>
This commit is contained in:
Marshall Bowers 2024-12-05 13:22:25 -05:00 committed by GitHub
parent 2d43ad12e6
commit 787c75cbda
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 375 additions and 127 deletions

3
Cargo.lock generated
View file

@ -456,6 +456,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"assistant_tool",
"chrono",
"client",
"collections",
"command_palette_hooks",
@ -478,6 +479,8 @@ dependencies = [
"settings",
"smol",
"theme",
"time",
"time_format",
"ui",
"unindent",
"util",

View file

@ -15,6 +15,7 @@ doctest = false
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
chrono.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
@ -37,6 +38,8 @@ serde_json.workspace = true
settings.workspace = true
smol.workspace = true
theme.workspace = true
time.workspace = true
time_format.workspace = true
ui.workspace = true
unindent.workspace = true
util.workspace = true

View file

@ -3,8 +3,8 @@ use std::sync::Arc;
use assistant_tool::ToolWorkingSet;
use collections::HashMap;
use gpui::{
list, AnyElement, Empty, ListAlignment, ListState, Model, StyleRefinement, Subscription,
TextStyleRefinement, View, WeakView,
list, AnyElement, AppContext, Empty, ListAlignment, ListState, Model, StyleRefinement,
Subscription, TextStyleRefinement, View, WeakView,
};
use language::LanguageRegistry;
use language_model::Role;
@ -70,6 +70,10 @@ impl ActiveThread {
self.messages.is_empty()
}
pub fn summary(&self, cx: &AppContext) -> Option<SharedString> {
self.thread.read(cx).summary()
}
pub fn last_error(&self) -> Option<ThreadError> {
self.last_error.clone()
}
@ -139,6 +143,7 @@ impl ActiveThread {
self.last_error = Some(error.clone());
}
ThreadEvent::StreamedCompletion => {}
ThreadEvent::SummaryChanged => {}
ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
markdown.update(cx, |markdown, cx| {

View file

@ -3,6 +3,7 @@ mod assistant_panel;
mod context_picker;
mod message_editor;
mod thread;
mod thread_history;
mod thread_store;
use command_palette_hooks::CommandPaletteFilter;

View file

@ -11,13 +11,15 @@ use gpui::{
use language::LanguageRegistry;
use language_model::LanguageModelRegistry;
use language_model_selector::LanguageModelSelector;
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, KeyBinding, ListItem, Tab, Tooltip};
use time::UtcOffset;
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, KeyBinding, Tab, Tooltip};
use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace;
use crate::active_thread::ActiveThread;
use crate::message_editor::MessageEditor;
use crate::thread::{Thread, ThreadError, ThreadId};
use crate::thread::{ThreadError, ThreadId};
use crate::thread_history::{PastThread, ThreadHistory};
use crate::thread_store::ThreadStore;
use crate::{NewThread, OpenHistory, ToggleFocus, ToggleModelSelector};
@ -32,13 +34,21 @@ pub fn init(cx: &mut AppContext) {
.detach();
}
enum ActiveView {
Thread,
History,
}
pub struct AssistantPanel {
workspace: WeakView<Workspace>,
language_registry: Arc<LanguageRegistry>,
thread_store: Model<ThreadStore>,
thread: Option<View<ActiveThread>>,
thread: View<ActiveThread>,
message_editor: View<MessageEditor>,
tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset,
active_view: ActiveView,
history: View<ThreadHistory>,
}
impl AssistantPanel {
@ -68,14 +78,31 @@ impl AssistantPanel {
cx: &mut ViewContext<Self>,
) -> Self {
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
let language_registry = workspace.project().read(cx).languages().clone();
let workspace = workspace.weak_handle();
let weak_self = cx.view().downgrade();
Self {
workspace: workspace.weak_handle(),
language_registry: workspace.project().read(cx).languages().clone(),
thread_store,
thread: None,
message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)),
active_view: ActiveView::Thread,
workspace: workspace.clone(),
language_registry: language_registry.clone(),
thread_store: thread_store.clone(),
thread: cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
workspace,
language_registry,
tools.clone(),
cx,
)
}),
message_editor: cx.new_view(|cx| MessageEditor::new(thread.clone(), cx)),
tools,
local_timezone: UtcOffset::from_whole_seconds(
chrono::Local::now().offset().local_minus_utc(),
)
.unwrap(),
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
}
}
@ -84,7 +111,8 @@ impl AssistantPanel {
.thread_store
.update(cx, |this, cx| this.create_thread(cx));
self.thread = Some(cx.new_view(|cx| {
self.active_view = ActiveView::Thread;
self.thread = cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
self.workspace.clone(),
@ -92,12 +120,12 @@ impl AssistantPanel {
self.tools.clone(),
cx,
)
}));
});
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread, cx));
self.message_editor.focus_handle(cx).focus(cx);
}
fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
let Some(thread) = self
.thread_store
.update(cx, |this, cx| this.open_thread(thread_id, cx))
@ -105,7 +133,8 @@ impl AssistantPanel {
return;
};
self.thread = Some(cx.new_view(|cx| {
self.active_view = ActiveView::Thread;
self.thread = cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
self.workspace.clone(),
@ -113,15 +142,22 @@ impl AssistantPanel {
self.tools.clone(),
cx,
)
}));
});
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread, cx));
self.message_editor.focus_handle(cx).focus(cx);
}
pub(crate) fn local_timezone(&self) -> UtcOffset {
self.local_timezone
}
}
impl FocusableView for AssistantPanel {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.message_editor.focus_handle(cx)
match self.active_view {
ActiveView::Thread => self.message_editor.focus_handle(cx),
ActiveView::History => self.history.focus_handle(cx),
}
}
}
@ -180,7 +216,7 @@ impl AssistantPanel {
.bg(cx.theme().colors().tab_bar_background)
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(h_flex().child(Label::new("Thread Title Goes Here")))
.child(h_flex().children(self.thread.read(cx).summary(cx).map(Label::new)))
.child(
h_flex()
.gap(DynamicSpacing::Base08.rems(cx))
@ -291,15 +327,11 @@ impl AssistantPanel {
}
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
let Some(thread) = self.thread.as_ref() else {
return self.render_thread_empty_state(cx).into_any_element();
};
if thread.read(cx).is_empty() {
if self.thread.read(cx).is_empty() {
return self.render_thread_empty_state(cx).into_any_element();
}
thread.clone().into_any()
self.thread.clone().into_any()
}
fn render_thread_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
@ -361,63 +393,41 @@ impl AssistantPanel {
.child(Label::new("/src/components").size(LabelSize::Small)),
),
)
.child(
h_flex()
.w_full()
.justify_center()
.child(Label::new("Recent Threads:").size(LabelSize::Small)),
)
.child(
v_flex().gap_2().children(
recent_threads
.into_iter()
.map(|thread| self.render_past_thread(thread, cx)),
),
)
.child(
h_flex().w_full().justify_center().child(
Button::new("view-all-past-threads", "View All Past Threads")
.style(ButtonStyle::Subtle)
.label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
cx,
))
.on_click(move |_event, cx| {
cx.dispatch_action(OpenHistory.boxed_clone());
}),
),
)
}
fn render_past_thread(
&self,
thread: Model<Thread>,
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
let id = thread.read(cx).id().clone();
ListItem::new(("past-thread", thread.entity_id()))
.start_slot(Icon::new(IconName::MessageBubbles))
.child(Label::new(format!("Thread {id}")))
.end_slot(
h_flex()
.gap_2()
.child(Label::new("1 hour ago").color(Color::Disabled))
.when(!recent_threads.is_empty(), |parent| {
parent
.child(
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small),
),
)
.on_click(cx.listener(move |this, _event, cx| {
this.open_thread(&id, cx);
}))
h_flex()
.w_full()
.justify_center()
.child(Label::new("Recent Threads:").size(LabelSize::Small)),
)
.child(
v_flex().gap_2().children(
recent_threads
.into_iter()
.map(|thread| PastThread::new(thread, cx.view().downgrade())),
),
)
.child(
h_flex().w_full().justify_center().child(
Button::new("view-all-past-threads", "View All Past Threads")
.style(ButtonStyle::Subtle)
.label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
cx,
))
.on_click(move |_event, cx| {
cx.dispatch_action(OpenHistory.boxed_clone());
}),
),
)
})
}
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
let last_error = self.thread.as_ref()?.read(cx).last_error()?;
let last_error = self.thread.read(cx).last_error()?;
Some(
div()
@ -467,11 +477,9 @@ impl AssistantPanel {
.mt_1()
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|this, _, cx| {
if let Some(thread) = this.thread.as_ref() {
thread.update(cx, |this, _cx| {
this.clear_last_error();
});
}
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
cx.open_url(&zed_urls::account_url(cx));
cx.notify();
@ -479,11 +487,9 @@ impl AssistantPanel {
)))
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
if let Some(thread) = this.thread.as_ref() {
thread.update(cx, |this, _cx| {
this.clear_last_error();
});
}
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
cx.notify();
},
@ -518,11 +524,9 @@ impl AssistantPanel {
.child(
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
cx.listener(|this, _, cx| {
if let Some(thread) = this.thread.as_ref() {
thread.update(cx, |this, _cx| {
this.clear_last_error();
});
}
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
cx.open_url(&zed_urls::account_url(cx));
cx.notify();
@ -531,11 +535,9 @@ impl AssistantPanel {
)
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
if let Some(thread) = this.thread.as_ref() {
thread.update(cx, |this, _cx| {
this.clear_last_error();
});
}
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
cx.notify();
},
@ -574,11 +576,9 @@ impl AssistantPanel {
.mt_1()
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
if let Some(thread) = this.thread.as_ref() {
thread.update(cx, |this, _cx| {
this.clear_last_error();
});
}
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
cx.notify();
},
@ -597,17 +597,23 @@ impl Render for AssistantPanel {
.on_action(cx.listener(|this, _: &NewThread, cx| {
this.new_thread(cx);
}))
.on_action(cx.listener(|_this, _: &OpenHistory, _cx| {
println!("Open History");
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
this.active_view = ActiveView::History;
this.history.focus_handle(cx).focus(cx);
cx.notify();
}))
.child(self.render_toolbar(cx))
.child(self.render_active_thread_or_empty_state(cx))
.child(
h_flex()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(self.message_editor.clone()),
)
.children(self.render_last_error(cx))
.map(|parent| match self.active_view {
ActiveView::Thread => parent
.child(self.render_active_thread_or_empty_state(cx))
.child(
h_flex()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(self.message_editor.clone()),
)
.children(self.render_last_error(cx)),
ActiveView::History => parent.child(self.history.clone()),
})
}
}

View file

@ -2,18 +2,19 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_tool::ToolWorkingSet;
use chrono::{DateTime, Utc};
use collections::HashMap;
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _};
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelToolResult, LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
StopReason,
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent, Role, StopReason,
};
use language_models::provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError};
use serde::{Deserialize, Serialize};
use util::post_inc;
use util::{post_inc, TryFutureExt as _};
use uuid::Uuid;
#[derive(Debug, Clone, Copy)]
@ -56,6 +57,9 @@ pub struct Message {
/// A thread of conversation with the LLM.
pub struct Thread {
id: ThreadId,
updated_at: DateTime<Utc>,
summary: Option<SharedString>,
pending_summary: Task<Option<()>>,
messages: Vec<Message>,
next_message_id: MessageId,
completion_count: usize,
@ -70,6 +74,9 @@ impl Thread {
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut ModelContext<Self>) -> Self {
Self {
id: ThreadId::new(),
updated_at: Utc::now(),
summary: None,
pending_summary: Task::ready(None),
messages: Vec::new(),
next_message_id: MessageId(0),
completion_count: 0,
@ -89,6 +96,23 @@ impl Thread {
self.messages.is_empty()
}
pub fn updated_at(&self) -> DateTime<Utc> {
self.updated_at
}
pub fn touch_updated_at(&mut self) {
self.updated_at = Utc::now();
}
pub fn summary(&self) -> Option<SharedString> {
self.summary.clone()
}
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut ModelContext<Self>) {
self.summary = Some(summary.into());
cx.emit(ThreadEvent::SummaryChanged);
}
pub fn message(&self, id: MessageId) -> Option<&Message> {
self.messages.iter().find(|message| message.id == id)
}
@ -121,6 +145,7 @@ impl Thread {
role,
text: text.into(),
});
self.touch_updated_at();
cx.emit(ThreadEvent::MessageAdded(id));
}
@ -191,13 +216,7 @@ impl Thread {
thread.update(&mut cx, |thread, cx| {
match event {
LanguageModelCompletionEvent::StartMessage { .. } => {
let id = thread.next_message_id.post_inc();
thread.messages.push(Message {
id,
role: Role::Assistant,
text: String::new(),
});
cx.emit(ThreadEvent::MessageAdded(id));
thread.insert_message(Role::Assistant, String::new(), cx);
}
LanguageModelCompletionEvent::Stop(reason) => {
stop_reason = reason;
@ -239,6 +258,7 @@ impl Thread {
}
}
thread.touch_updated_at();
cx.emit(ThreadEvent::StreamedCompletion);
cx.notify();
})?;
@ -246,10 +266,14 @@ impl Thread {
smol::future::yield_now().await;
}
thread.update(&mut cx, |thread, _cx| {
thread.update(&mut cx, |thread, cx| {
thread
.pending_completions
.retain(|completion| completion.id != pending_completion_id);
if thread.summary.is_none() && thread.messages.len() >= 2 {
thread.summarize(cx);
}
})?;
anyhow::Ok(stop_reason)
@ -292,6 +316,59 @@ impl Thread {
});
}
pub fn summarize(&mut self, cx: &mut ModelContext<Self>) {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return;
};
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
if !provider.is_authenticated(cx) {
return;
}
let mut request = self.to_completion_request(RequestKind::Chat, cx);
request.messages.push(LanguageModelRequestMessage {
role: Role::User,
content: vec![
"Generate a concise 3-7 word title for this conversation, omitting punctuation. Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`"
.into(),
],
cache: false,
});
self.pending_summary = cx.spawn(|this, mut cx| {
async move {
let stream = model.stream_completion_text(request, &cx);
let mut messages = stream.await?;
let mut new_summary = String::new();
while let Some(message) = messages.stream.next().await {
let text = message?;
let mut lines = text.lines();
new_summary.extend(lines.next());
// Stop if the LLM generated multiple lines.
if lines.next().is_some() {
break;
}
}
this.update(&mut cx, |this, cx| {
if !new_summary.is_empty() {
this.summary = Some(new_summary.into());
}
cx.emit(ThreadEvent::SummaryChanged);
})?;
anyhow::Ok(())
}
.log_err()
});
}
pub fn insert_tool_output(
&mut self,
assistant_message_id: MessageId,
@ -365,6 +442,7 @@ pub enum ThreadEvent {
StreamedCompletion,
StreamedAssistantText(MessageId, String),
MessageAdded(MessageId),
SummaryChanged,
UsePendingTools,
ToolFinished {
#[allow(unused)]

View file

@ -0,0 +1,144 @@
use gpui::{
uniform_list, AppContext, FocusHandle, FocusableView, Model, UniformListScrollHandle, WeakView,
};
use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, IconButtonShape, ListItem};
use crate::thread::Thread;
use crate::thread_store::ThreadStore;
use crate::AssistantPanel;
pub struct ThreadHistory {
focus_handle: FocusHandle,
assistant_panel: WeakView<AssistantPanel>,
thread_store: Model<ThreadStore>,
scroll_handle: UniformListScrollHandle,
}
impl ThreadHistory {
pub(crate) fn new(
assistant_panel: WeakView<AssistantPanel>,
thread_store: Model<ThreadStore>,
cx: &mut ViewContext<Self>,
) -> Self {
Self {
focus_handle: cx.focus_handle(),
assistant_panel,
thread_store,
scroll_handle: UniformListScrollHandle::default(),
}
}
}
impl FocusableView for ThreadHistory {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ThreadHistory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
v_flex()
.id("thread-history-container")
.track_focus(&self.focus_handle)
.overflow_y_scroll()
.size_full()
.p_1()
.map(|history| {
if threads.is_empty() {
history
.justify_center()
.child(
h_flex().w_full().justify_center().child(
Label::new("You don't have any past threads yet.")
.size(LabelSize::Small),
),
)
} else {
history.child(
uniform_list(
cx.view().clone(),
"thread-history",
threads.len(),
move |history, range, _cx| {
threads[range]
.iter()
.map(|thread| {
PastThread::new(
thread.clone(),
history.assistant_panel.clone(),
)
})
.collect()
},
)
.track_scroll(self.scroll_handle.clone())
.flex_grow(),
)
}
})
}
}
#[derive(IntoElement)]
pub struct PastThread {
thread: Model<Thread>,
assistant_panel: WeakView<AssistantPanel>,
}
impl PastThread {
pub fn new(thread: Model<Thread>, assistant_panel: WeakView<AssistantPanel>) -> Self {
Self {
thread,
assistant_panel,
}
}
}
impl RenderOnce for PastThread {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let (id, summary) = {
const DEFAULT_SUMMARY: SharedString = SharedString::new_static("New Thread");
let thread = self.thread.read(cx);
(
thread.id().clone(),
thread.summary().unwrap_or(DEFAULT_SUMMARY),
)
};
let thread_timestamp = time_format::format_localized_timestamp(
OffsetDateTime::from_unix_timestamp(self.thread.read(cx).updated_at().timestamp())
.unwrap(),
OffsetDateTime::now_utc(),
self.assistant_panel
.update(cx, |this, _cx| this.local_timezone())
.unwrap_or(UtcOffset::UTC),
time_format::TimestampFormat::EnhancedAbsolute,
);
ListItem::new(("past-thread", self.thread.entity_id()))
.start_slot(Icon::new(IconName::MessageBubbles))
.child(Label::new(summary))
.end_slot(
h_flex()
.gap_2()
.child(Label::new(thread_timestamp).color(Color::Disabled))
.child(
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small),
),
)
.on_click({
let assistant_panel = self.assistant_panel.clone();
move |_event, cx| {
assistant_panel
.update(cx, |this, cx| {
this.open_thread(&id, cx);
})
.ok();
}
})
}
}

View file

@ -52,13 +52,19 @@ impl ThreadStore {
})
}
pub fn recent_threads(&self, limit: usize, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
self.threads
pub fn threads(&self, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
let mut threads = self
.threads
.iter()
.filter(|thread| !thread.read(cx).is_empty())
.take(limit)
.cloned()
.collect()
.collect::<Vec<_>>();
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.read(cx).updated_at()));
threads
}
pub fn recent_threads(&self, limit: usize, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
self.threads(cx).into_iter().take(limit).collect()
}
pub fn create_thread(&mut self, cx: &mut ModelContext<Self>) -> Model<Thread> {
@ -148,6 +154,7 @@ impl ThreadStore {
self.threads.push(cx.new_model(|cx| {
let mut thread = Thread::new(self.tools.clone(), cx);
thread.set_summary("Introduction to quantum computing", cx);
thread.insert_user_message("Hello! Can you help me understand quantum computing?", cx);
thread.insert_message(Role::Assistant, "Of course! I'd be happy to help you understand quantum computing. Quantum computing is a fascinating field that uses the principles of quantum mechanics to process information. Unlike classical computers that use bits (0s and 1s), quantum computers use quantum bits or 'qubits'. These qubits can exist in multiple states simultaneously, a property called superposition. This allows quantum computers to perform certain calculations much faster than classical computers. What specific aspect of quantum computing would you like to know more about?", cx);
thread.insert_user_message("That's interesting! Can you explain how quantum entanglement is used in quantum computing?", cx);
@ -157,6 +164,7 @@ impl ThreadStore {
self.threads.push(cx.new_model(|cx| {
let mut thread = Thread::new(self.tools.clone(), cx);
thread.set_summary("Rust web development and async programming", cx);
thread.insert_user_message("Can you show me an example of Rust code for a simple web server?", cx);
thread.insert_message(Role::Assistant, "Certainly! Here's an example of a simple web server in Rust using the `actix-web` framework: