Bind sessions to their selected model
This commit is contained in:
@@ -196,12 +196,11 @@ stored per model; the custom system prompt is shared across profiles. Other
|
||||
sections control A2UI, permission defaults, endpoint settings, Git diff display,
|
||||
Dev Brain, extensions, checkpoint storage, and diagnostics.
|
||||
|
||||
A session remains model-independent until its first tool call. That call locks
|
||||
the session to its model so persisted tool syntax is never mixed. Continuing a
|
||||
locked session while another model is active asks before switching back. Legacy
|
||||
sessions recover the exact model from their checkpoint when available; a legacy
|
||||
tool session whose exact model can no longer be identified remains viewable but
|
||||
cannot be continued.
|
||||
A new draft uses the currently selected model when its first turn is persisted.
|
||||
From then on the session keeps that exact model, so opening it immediately selects
|
||||
the same model and that model's thinking choices. Legacy sessions recover the
|
||||
exact model from their checkpoint when available; a legacy session whose
|
||||
exact model can no longer be identified remains viewable but cannot be continued.
|
||||
|
||||
### Main views, sidebar, and branches
|
||||
|
||||
@@ -232,7 +231,9 @@ restarts the local listener.
|
||||
accepts `none`, `high`, or `max`; GLM 5.3 Flash accepts `low`, `high`, or `max`.
|
||||
Omitting it uses the upstream model default: `low` for DeepSeek and `max` for
|
||||
both GLM models. Unsupported values and conflicting thinking controls return a
|
||||
400 error instead of being converted to another effort.
|
||||
400 error instead of being converted to another effort. This per-model thinking
|
||||
selection is intentionally part of the DS4Server agent harness, not a DS4 parity
|
||||
surface; do not replace it with mapped, hidden, or shared choices.
|
||||
|
||||
### Data and recovery
|
||||
|
||||
|
||||
131
src/app.rs
131
src/app.rs
@@ -105,8 +105,6 @@ pub(crate) struct App {
|
||||
session_menu: Option<i32>,
|
||||
/// Session waiting for explicit confirmation before deletion.
|
||||
pending_session_delete: Option<i32>,
|
||||
/// Locked session model waiting to replace the current global selection.
|
||||
pending_session_model_switch: Option<ModelChoice>,
|
||||
/// Session being renamed, with the in-progress title.
|
||||
session_rename: Option<(i32, String)>,
|
||||
/// Projects whose archived sessions are expanded in the sidebar.
|
||||
@@ -502,8 +500,6 @@ pub(crate) enum Message {
|
||||
AllowToolOnce,
|
||||
DenyTool,
|
||||
SubmitPrompt,
|
||||
ConfirmSessionModelSwitch,
|
||||
CancelSessionModelSwitch,
|
||||
StopGeneration,
|
||||
GenerationTick,
|
||||
ChatScrolled(scrollable::Viewport),
|
||||
@@ -649,7 +645,6 @@ impl App {
|
||||
background_chats: HashMap::new(),
|
||||
session_menu: None,
|
||||
pending_session_delete: None,
|
||||
pending_session_model_switch: None,
|
||||
session_rename: None,
|
||||
expanded_archives: HashSet::new(),
|
||||
sidebar_drag: false,
|
||||
@@ -815,7 +810,6 @@ impl App {
|
||||
background_chats: HashMap::new(),
|
||||
session_menu: None,
|
||||
pending_session_delete: None,
|
||||
pending_session_model_switch: None,
|
||||
session_rename: None,
|
||||
expanded_archives: HashSet::new(),
|
||||
sidebar_drag: false,
|
||||
@@ -1247,8 +1241,6 @@ impl App {
|
||||
Message::DismissPanel => {
|
||||
if self.quit_confirmation {
|
||||
self.quit_confirmation = false;
|
||||
} else if self.pending_session_model_switch.is_some() {
|
||||
self.pending_session_model_switch = None;
|
||||
} else if self.git_diff.is_some() {
|
||||
self.git_diff = None;
|
||||
} else if self.git_commit_all_confirmation {
|
||||
@@ -1568,20 +1560,21 @@ impl App {
|
||||
}
|
||||
Message::SubmitPrompt => {
|
||||
if !self.generating && self.selected_session.is_some() {
|
||||
match required_session_model_switch(
|
||||
self.config.model,
|
||||
match required_session_model(
|
||||
self.selected_session_model(),
|
||||
self.chat_tool_protocol_model(),
|
||||
!self.conversation.is_empty(),
|
||||
) {
|
||||
Ok(Some(model)) => {
|
||||
self.pending_session_model_switch = Some(model);
|
||||
return Task::none();
|
||||
if let Err(error) = self.activate_model(model) {
|
||||
self.error = Some(error);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
Err(protocol) => {
|
||||
self.error = Some(format!(
|
||||
"This legacy session contains {} tool calls, but its exact model was not recorded and no compatible checkpoint remains. Start a new session to continue safely.",
|
||||
if protocol.is_glm() { "GLM" } else { "DeepSeek" }
|
||||
));
|
||||
Err(()) => {
|
||||
self.error = Some(
|
||||
"This legacy session has chat history, but its exact model was not recorded and no compatible checkpoint remains. Start a new session to continue safely."
|
||||
.into(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
Ok(None) => {}
|
||||
@@ -1591,33 +1584,6 @@ impl App {
|
||||
self.start_generation();
|
||||
return scroll_chat_to_end();
|
||||
}
|
||||
Message::ConfirmSessionModelSwitch => {
|
||||
let Some(model) = self.pending_session_model_switch.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
let mut config = self.config.clone();
|
||||
config.model = model;
|
||||
if let Err(error) = config.save(&config_path()) {
|
||||
self.error = Some(error);
|
||||
return Task::none();
|
||||
}
|
||||
self.config = config;
|
||||
self.preference_draft = PreferenceDraft::from_saved(&self.config);
|
||||
self.context_limit = self.config.active_generation().context_tokens.max(0) as u32;
|
||||
if model != ModelChoice::Glm53Flash {
|
||||
self.pending_vision_image = None;
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
self.agent_tools = None;
|
||||
preferences::update_runtime_config(&self.runtime_config, &self.config);
|
||||
}
|
||||
self.error = None;
|
||||
self.chat_follow_tail = true;
|
||||
self.start_generation();
|
||||
return scroll_chat_to_end();
|
||||
}
|
||||
Message::CancelSessionModelSwitch => self.pending_session_model_switch = None,
|
||||
Message::StopGeneration => {
|
||||
self.stop_requested = true;
|
||||
self.activity = Some("Stopping…".into());
|
||||
@@ -1993,7 +1959,18 @@ impl App {
|
||||
if self.selected_session == Some(session_id) {
|
||||
return Task::none();
|
||||
}
|
||||
self.pending_session_model_switch = None;
|
||||
let stored_model = self
|
||||
.projects
|
||||
.iter()
|
||||
.flat_map(|project| &project.sessions)
|
||||
.find(|session| session.id == session_id)
|
||||
.and_then(|session| session.model());
|
||||
if let Some(model) = stored_model
|
||||
&& let Err(error) = self.activate_model(model)
|
||||
{
|
||||
self.error = Some(error);
|
||||
return Task::none();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
self.leave_current_chat();
|
||||
@@ -2037,26 +2014,28 @@ impl App {
|
||||
self.error = Some(format!("Could not update the session: {error}"));
|
||||
return Task::none();
|
||||
}
|
||||
let stored_model = saved_context
|
||||
.as_ref()
|
||||
.and_then(|(_, _, _, _, model)| *model);
|
||||
let protocol_model = messages.iter().find_map(|message| {
|
||||
crate::agent::tool_protocol_model(&message.content)
|
||||
});
|
||||
if stored_model.is_none()
|
||||
&& let (Some(protocol_model), Some(checkpoint_model)) = (
|
||||
protocol_model,
|
||||
crate::engine::checkpoint_model(&session_checkpoint_path(
|
||||
session_id,
|
||||
)),
|
||||
&& let Some(checkpoint_model) = crate::engine::checkpoint_model(
|
||||
&session_checkpoint_path(session_id),
|
||||
)
|
||||
&& protocol_model.is_glm() == checkpoint_model.is_glm()
|
||||
&& let Err(error) =
|
||||
database.set_session_model(session_id, checkpoint_model)
|
||||
&& protocol_model.is_none_or(|protocol| {
|
||||
protocol.is_glm() == checkpoint_model.is_glm()
|
||||
})
|
||||
{
|
||||
self.error =
|
||||
Some(format!("Could not restore the session model: {error}"));
|
||||
return Task::none();
|
||||
if let Err(error) =
|
||||
database.set_session_model(session_id, checkpoint_model)
|
||||
{
|
||||
self.error =
|
||||
Some(format!("Could not restore the session model: {error}"));
|
||||
return Task::none();
|
||||
}
|
||||
if let Err(error) = self.activate_model(checkpoint_model) {
|
||||
self.error = Some(error);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
self.conversation = messages.into_iter().map(ChatMessage::from).collect();
|
||||
self.chat_follow_tail = true;
|
||||
@@ -3232,15 +3211,14 @@ fn session_checkpoint_path(session_id: i32) -> PathBuf {
|
||||
kv_cache_path().join(format!("{session_id}.bin"))
|
||||
}
|
||||
|
||||
fn required_session_model_switch(
|
||||
current: ModelChoice,
|
||||
fn required_session_model(
|
||||
locked: Option<ModelChoice>,
|
||||
legacy_protocol: Option<ModelChoice>,
|
||||
) -> Result<Option<ModelChoice>, ModelChoice> {
|
||||
has_history: bool,
|
||||
) -> Result<Option<ModelChoice>, ()> {
|
||||
match locked {
|
||||
Some(model) if model != current => Ok(Some(model)),
|
||||
Some(_) => Ok(None),
|
||||
None => legacy_protocol.map_or(Ok(None), Err),
|
||||
Some(model) => Ok(Some(model)),
|
||||
None if has_history => Err(()),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3295,22 +3273,11 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tool_calls_lock_continuation_to_the_session_model() {
|
||||
fn stored_session_always_selects_its_model() {
|
||||
let flash = ModelChoice::DeepSeekV4Flash0731;
|
||||
let glm = ModelChoice::Glm53Flash;
|
||||
assert_eq!(
|
||||
required_session_model_switch(flash, Some(flash), None),
|
||||
Ok(None)
|
||||
);
|
||||
assert_eq!(
|
||||
required_session_model_switch(glm, Some(flash), None),
|
||||
Ok(Some(flash))
|
||||
);
|
||||
assert_eq!(
|
||||
required_session_model_switch(glm, None, Some(flash)),
|
||||
Err(flash)
|
||||
);
|
||||
assert_eq!(required_session_model_switch(glm, None, None), Ok(None));
|
||||
assert_eq!(required_session_model(Some(flash), true), Ok(Some(flash)));
|
||||
assert_eq!(required_session_model(None, true), Err(()));
|
||||
assert_eq!(required_session_model(None, false), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
use super::*;
|
||||
|
||||
impl App {
|
||||
pub(super) fn activate_model(&mut self, model: ModelChoice) -> Result<(), String> {
|
||||
if self.config.model == model {
|
||||
return Ok(());
|
||||
}
|
||||
let mut config = self.config.clone();
|
||||
config.model = model;
|
||||
config.save(&config_path())?;
|
||||
self.config = config;
|
||||
self.preference_draft = PreferenceDraft::from_saved(&self.config);
|
||||
self.context_limit = self.config.active_generation().context_tokens.max(0) as u32;
|
||||
if model != ModelChoice::Glm53Flash {
|
||||
self.pending_vision_image = None;
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
self.agent_tools = None;
|
||||
preferences::update_runtime_config(&self.runtime_config, &self.config);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn selected_session_model(&self) -> Option<ModelChoice> {
|
||||
let session_id = self.selected_session?;
|
||||
self.projects
|
||||
@@ -192,7 +213,6 @@ impl App {
|
||||
self.drafts.entry(project_id).or_insert(title);
|
||||
self.remember_project(project_id);
|
||||
self.selected_session = None;
|
||||
self.pending_session_model_switch = None;
|
||||
self.permission_mode = self.config.default_permission_mode;
|
||||
self.conversation.clear();
|
||||
self.chat_follow_tail = true;
|
||||
@@ -234,7 +254,8 @@ impl App {
|
||||
.database
|
||||
.as_mut()
|
||||
.ok_or_else(|| "The project database is unavailable.".to_owned())?;
|
||||
let session = database.create_session(project_id, &title, self.permission_mode)?;
|
||||
let session =
|
||||
database.create_session(project_id, &title, self.permission_mode, self.config.model)?;
|
||||
self.drafts.remove(&project_id);
|
||||
self.remember_project(project_id);
|
||||
self.selected_session = Some(session.id);
|
||||
|
||||
@@ -110,7 +110,6 @@ impl App {
|
||||
/// tree, so the layers below have to stay out of the dialog's field order.
|
||||
pub(super) fn modal_open(&self) -> bool {
|
||||
self.quit_confirmation
|
||||
|| self.pending_session_model_switch.is_some()
|
||||
|| self.git_diff.is_some()
|
||||
|| self.git_commit_all_confirmation
|
||||
|| self.pending_project_path.is_some()
|
||||
@@ -175,8 +174,6 @@ impl App {
|
||||
#[cfg(target_os = "macos")]
|
||||
if self.quit_confirmation {
|
||||
layers.push(self.quit_confirmation_panel());
|
||||
} else if let Some(model) = self.pending_session_model_switch {
|
||||
layers.push(self.session_model_switch_panel(model));
|
||||
} else if let Some((prompt, _)) = &self.pending_tool_approval {
|
||||
layers.push(self.tool_approval_panel(prompt));
|
||||
} else if let Some(path) = &self.pending_project_path {
|
||||
@@ -199,8 +196,6 @@ impl App {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
if self.quit_confirmation {
|
||||
layers.push(self.quit_confirmation_panel());
|
||||
} else if let Some(model) = self.pending_session_model_switch {
|
||||
layers.push(self.session_model_switch_panel(model));
|
||||
} else if let Some(path) = &self.pending_project_path {
|
||||
layers.push(self.project_dialog(path));
|
||||
} else if let Some((_, title)) = &self.session_rename {
|
||||
@@ -257,39 +252,6 @@ impl App {
|
||||
)
|
||||
}
|
||||
|
||||
fn session_model_switch_panel(&self, model: ModelChoice) -> Element<'_, Message> {
|
||||
let title = self.active_chat_title().unwrap_or("This session");
|
||||
let dialog = container(
|
||||
column![
|
||||
text("Switch model to continue?").size(22),
|
||||
text(format!(
|
||||
"“{title}” is locked to {model} because it contains that model's tool calls. Continuing will switch the active model from {} to {model}.",
|
||||
self.config.model
|
||||
))
|
||||
.size(13),
|
||||
row![
|
||||
Space::new().width(Length::Fill),
|
||||
action_button("Cancel").on_press(Message::CancelSessionModelSwitch),
|
||||
action_button("Switch and continue")
|
||||
.on_press(Message::ConfirmSessionModelSwitch),
|
||||
]
|
||||
.spacing(8),
|
||||
]
|
||||
.spacing(12),
|
||||
)
|
||||
.padding(22)
|
||||
.width(520)
|
||||
.style(overview_style);
|
||||
opaque(
|
||||
container(dialog)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.style(|_| {
|
||||
container::Style::default().background(Color::from_rgba8(0, 0, 0, 0.68))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn tool_approval_panel<'a>(
|
||||
&'a self,
|
||||
|
||||
103
src/database.rs
103
src/database.rs
@@ -45,7 +45,7 @@ pub struct Session {
|
||||
last_used: i64,
|
||||
/// Raw column value; read it through [`Session::permission_mode`].
|
||||
permission_mode: String,
|
||||
/// Locked after the first tool call; read it through [`Session::model`].
|
||||
/// Locked when the first turn is persisted; read it through [`Session::model`].
|
||||
model: Option<String>,
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ struct NewSession<'a> {
|
||||
title: &'a str,
|
||||
last_used: i64,
|
||||
permission_mode: &'a str,
|
||||
model: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
|
||||
@@ -395,6 +396,7 @@ impl Database {
|
||||
project_id: i32,
|
||||
title: &str,
|
||||
permission_mode: PermissionMode,
|
||||
model: crate::model::ModelChoice,
|
||||
) -> Result<Session, String> {
|
||||
diesel::insert_into(sessions::table)
|
||||
.values(NewSession {
|
||||
@@ -402,6 +404,7 @@ impl Database {
|
||||
title,
|
||||
last_used: OffsetDateTime::now_utc().unix_timestamp(),
|
||||
permission_mode: permission_mode.as_id(),
|
||||
model: model.id(),
|
||||
})
|
||||
.returning(Session::as_returning())
|
||||
.get_result(&mut self.connection)
|
||||
@@ -838,29 +841,39 @@ mod tests {
|
||||
|
||||
let project = database.create_project("DS4", "/tmp/ds4").unwrap();
|
||||
let first = database
|
||||
.create_session(project.id, "First session", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"First session",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
let second = database
|
||||
.create_session(project.id, "Second session", PermissionMode::Ai)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Second session",
|
||||
PermissionMode::Ai,
|
||||
crate::model::ModelChoice::Glm53Flash,
|
||||
)
|
||||
.unwrap();
|
||||
database.delete_session(first.id).unwrap();
|
||||
let loaded = database.load_projects().unwrap();
|
||||
assert_eq!(loaded[0].sessions[0].title, "Second session");
|
||||
assert_eq!(loaded[0].sessions[0].state(), SessionState::Normal);
|
||||
assert_eq!(loaded[0].sessions[0].permission_mode(), PermissionMode::Ai);
|
||||
assert_eq!(loaded[0].sessions[0].model(), None);
|
||||
assert_eq!(
|
||||
loaded[0].sessions[0].model(),
|
||||
Some(crate::model::ModelChoice::Glm53Flash)
|
||||
);
|
||||
database
|
||||
.set_session_permission_mode(second.id, PermissionMode::Heuristic)
|
||||
.unwrap();
|
||||
database
|
||||
.set_session_model(second.id, crate::model::ModelChoice::DeepSeekV4Flash0731)
|
||||
.unwrap();
|
||||
database
|
||||
.set_session_model(second.id, crate::model::ModelChoice::DeepSeekV4Flash0731)
|
||||
.set_session_model(second.id, crate::model::ModelChoice::Glm53Flash)
|
||||
.unwrap();
|
||||
assert!(
|
||||
database
|
||||
.set_session_model(second.id, crate::model::ModelChoice::Glm53Flash)
|
||||
.set_session_model(second.id, crate::model::ModelChoice::DeepSeekV4Flash0731)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -869,15 +882,25 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
database.load_projects().unwrap()[0].sessions[0].model(),
|
||||
Some(crate::model::ModelChoice::DeepSeekV4Flash0731)
|
||||
Some(crate::model::ModelChoice::Glm53Flash)
|
||||
);
|
||||
|
||||
let ordinary = loaded[0].sessions[0].id;
|
||||
let pinned = database
|
||||
.create_session(project.id, "Pinned", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Pinned",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
let archived = database
|
||||
.create_session(project.id, "Archived", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Archived",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
database
|
||||
.set_session_state(pinned.id, SessionState::Pinned)
|
||||
@@ -934,13 +957,28 @@ mod tests {
|
||||
.create_project("DS4", "/tmp/ds4-session-order")
|
||||
.unwrap();
|
||||
let older = database
|
||||
.create_session(project.id, "Older", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Older",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
let newer = database
|
||||
.create_session(project.id, "Newer", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Newer",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
let pinned = database
|
||||
.create_session(project.id, "Pinned", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Pinned",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
database
|
||||
.set_session_state(pinned.id, SessionState::Pinned)
|
||||
@@ -980,7 +1018,12 @@ mod tests {
|
||||
.create_project("DS4", "/tmp/ds4-reactivate")
|
||||
.unwrap();
|
||||
let session = database
|
||||
.create_session(project.id, "Archived", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Archived",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
database
|
||||
.set_session_state(session.id, SessionState::Archived)
|
||||
@@ -1020,7 +1063,12 @@ mod tests {
|
||||
.create_project("DS4", "/tmp/ds4-a2ui-dismiss")
|
||||
.unwrap();
|
||||
let session = database
|
||||
.create_session(project.id, "A2UI", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"A2UI",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
let first = database
|
||||
.start_chat_turn(session.id, "First", None, &[], false)
|
||||
@@ -1100,7 +1148,12 @@ mod tests {
|
||||
let mut database = Database::open(&path).unwrap();
|
||||
let project = database.create_project("DS4", "/tmp/ds4-chat").unwrap();
|
||||
let session = database
|
||||
.create_session(project.id, "Chat", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Chat",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
let mut opening = database
|
||||
.start_chat_turn(
|
||||
@@ -1309,10 +1362,20 @@ mod tests {
|
||||
let mut database = Database::open(&path).unwrap();
|
||||
let project = database.create_project("DS4", "/tmp/ds4-svg-chat").unwrap();
|
||||
let session = database
|
||||
.create_session(project.id, "SVG", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"SVG",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::DeepSeekV4Flash0731,
|
||||
)
|
||||
.unwrap();
|
||||
let other = database
|
||||
.create_session(project.id, "Other", PermissionMode::Heuristic)
|
||||
.create_session(
|
||||
project.id,
|
||||
"Other",
|
||||
PermissionMode::Heuristic,
|
||||
crate::model::ModelChoice::Glm53Flash,
|
||||
)
|
||||
.unwrap();
|
||||
let assistant = database
|
||||
.start_chat_turn(session.id, "Draw it", None, &[], false)
|
||||
|
||||
Reference in New Issue
Block a user