use crate::dialogue::{DialogueMode, ParticipantColor};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DialogueBuilderStep {
#[default]
Participants,
ConfigureParticipant,
Mode,
InitialPrompt,
Review,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ParticipantField {
#[default]
DisplayName,
SystemPrompt,
}
impl DialogueBuilderStep {
pub fn next(self) -> Option<Self> {
match self {
Self::Participants => Some(Self::Mode),
Self::ConfigureParticipant => Some(Self::Participants),
Self::Mode => Some(Self::InitialPrompt),
Self::InitialPrompt => Some(Self::Review),
Self::Review => None,
}
}
pub fn prev(self) -> Option<Self> {
match self {
Self::Participants => None,
Self::ConfigureParticipant => Some(Self::Participants),
Self::Mode => Some(Self::Participants),
Self::InitialPrompt => Some(Self::Mode),
Self::Review => Some(Self::InitialPrompt),
}
}
pub const fn display_name(self) -> &'static str {
match self {
Self::Participants => "Participants",
Self::ConfigureParticipant => "Configure",
Self::Mode => "Mode",
Self::InitialPrompt => "Initial Prompt",
Self::Review => "Review",
}
}
}
impl ParticipantField {
pub fn next(self) -> Self {
match self {
Self::DisplayName => Self::SystemPrompt,
Self::SystemPrompt => Self::DisplayName,
}
}
pub const fn display_name(self) -> &'static str {
match self {
Self::DisplayName => "Display Name",
Self::SystemPrompt => "System Prompt",
}
}
}
#[derive(Debug, Clone)]
pub struct ParticipantDraft {
pub provider_id: String,
pub model_id: String,
pub display_name: String,
pub system_prompt: Option<String>,
pub color: ParticipantColor,
}
impl ParticipantDraft {
pub fn from_provider_model(provider_model: &str, index: usize) -> Option<Self> {
let parts: Vec<&str> = provider_model.split(':').collect();
if parts.len() != 2 {
return None;
}
let provider_id = parts[0].to_string();
let model_id = parts[1].to_string();
let display_name = model_id.clone();
Some(Self {
provider_id,
model_id,
display_name,
system_prompt: None,
color: ParticipantColor::for_index(index),
})
}
}
#[derive(Debug, Clone)]
pub struct DialogueBuilderState {
pub step: DialogueBuilderStep,
pub participants: Vec<ParticipantDraft>,
pub mode: DialogueMode,
pub initial_prompt: String,
pub input: String,
pub error: Option<String>,
pub selected: usize,
pub available_modes: Vec<DialogueMode>,
pub editing_participant: usize,
pub editing_field: ParticipantField,
}
impl Default for DialogueBuilderState {
fn default() -> Self {
Self::new()
}
}
impl DialogueBuilderState {
pub fn new() -> Self {
Self {
step: DialogueBuilderStep::default(),
participants: Vec::new(),
mode: DialogueMode::FreeForm,
initial_prompt: String::new(),
input: String::new(),
error: None,
selected: 0,
available_modes: vec![
DialogueMode::FreeForm,
DialogueMode::Collaboration,
DialogueMode::Debate,
DialogueMode::Critique,
DialogueMode::Custom,
],
editing_participant: 0,
editing_field: ParticipantField::default(),
}
}
pub fn next_step(&mut self) -> bool {
if let Some(next) = self.step.next() {
self.step = next;
self.selected = 0;
self.error = None;
true
} else {
false
}
}
pub fn prev_step(&mut self) -> bool {
if let Some(prev) = self.step.prev() {
self.step = prev;
self.selected = 0;
self.error = None;
true
} else {
false
}
}
pub fn add_participant(&mut self) -> bool {
let input = self.input.trim();
if input.is_empty() {
return false;
}
let provider_model = input.trim_start_matches('@');
let index = self.participants.len();
if let Some(draft) = ParticipantDraft::from_provider_model(provider_model, index) {
self.participants.push(draft);
self.input.clear();
self.error = None;
true
} else {
self.error = Some("Invalid format. Use provider:model".to_string());
false
}
}
pub fn remove_participant(&mut self) {
if !self.participants.is_empty() && self.selected < self.participants.len() {
self.participants.remove(self.selected);
if self.selected > 0 && self.selected >= self.participants.len() {
self.selected = self.participants.len().saturating_sub(1);
}
}
}
pub fn push_input(&mut self, ch: char) {
self.input.push(ch);
}
pub fn pop_input(&mut self) {
self.input.pop();
}
pub fn next(&mut self) {
let max = match self.step {
DialogueBuilderStep::Participants => self.participants.len(),
DialogueBuilderStep::Mode => self.available_modes.len(),
_ => 0,
};
if max > 0 {
self.selected = (self.selected + 1).min(max.saturating_sub(1));
}
}
pub fn prev(&mut self) {
if self.selected > 0 {
self.selected -= 1;
}
}
pub fn select_mode(&mut self) {
if self.step == DialogueBuilderStep::Mode && self.selected < self.available_modes.len() {
self.mode = self.available_modes[self.selected];
}
}
pub fn configure_participant(&mut self) -> bool {
if self.step == DialogueBuilderStep::Participants
&& !self.participants.is_empty()
&& self.selected < self.participants.len()
{
self.editing_participant = self.selected;
self.editing_field = ParticipantField::DisplayName;
self.input = self.participants[self.editing_participant]
.display_name
.clone();
self.step = DialogueBuilderStep::ConfigureParticipant;
self.error = None;
true
} else {
false
}
}
pub fn finish_configure(&mut self) {
if self.step == DialogueBuilderStep::ConfigureParticipant {
self.save_current_field();
self.step = DialogueBuilderStep::Participants;
self.input.clear();
self.error = None;
}
}
pub fn next_field(&mut self) {
if self.step == DialogueBuilderStep::ConfigureParticipant {
self.save_current_field();
self.editing_field = self.editing_field.next();
self.load_current_field();
}
}
fn save_current_field(&mut self) {
if self.editing_participant < self.participants.len() {
let participant = &mut self.participants[self.editing_participant];
match self.editing_field {
ParticipantField::DisplayName => {
if !self.input.trim().is_empty() {
participant.display_name = self.input.trim().to_string();
}
}
ParticipantField::SystemPrompt => {
let trimmed = self.input.trim();
participant.system_prompt = if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
};
}
}
}
}
fn load_current_field(&mut self) {
if self.editing_participant < self.participants.len() {
let participant = &self.participants[self.editing_participant];
self.input = match self.editing_field {
ParticipantField::DisplayName => participant.display_name.clone(),
ParticipantField::SystemPrompt => {
participant.system_prompt.clone().unwrap_or_default()
}
};
}
}
pub fn current_editing_participant(&self) -> Option<&ParticipantDraft> {
if self.step == DialogueBuilderStep::ConfigureParticipant {
self.participants.get(self.editing_participant)
} else {
None
}
}
pub fn set_error(&mut self, error: impl Into<String>) {
self.error = Some(error.into());
}
pub fn validate(&self) -> Result<(), String> {
match self.step {
DialogueBuilderStep::Participants => {
if self.participants.len() < 2 {
Err("At least 2 participants required".to_string())
} else {
Ok(())
}
}
_ => Ok(()),
}
}
pub fn is_complete(&self) -> bool {
self.step == DialogueBuilderStep::Review && self.participants.len() >= 2
}
}
#[derive(Debug, Clone)]
pub struct DialogueBuilderResult {
pub participants: Vec<ParticipantDraft>,
pub mode: DialogueMode,
pub initial_prompt: String,
}
impl From<&DialogueBuilderState> for DialogueBuilderResult {
fn from(state: &DialogueBuilderState) -> Self {
Self {
participants: state.participants.clone(),
mode: state.mode,
initial_prompt: state.initial_prompt.clone(),
}
}
}