T23c: AI chat, input

- PromptInput: attachments and context above the field, the owner's tools and
  send below; Enter sends, Shift-Enter breaks the line; / offers commands and
  @ offers context over forms::Suggestions; files dropped on it attach behind a
  drop veil
- AttachmentButton, AttachmentChip with thumbnails and uploads, ContextChips
- VoiceInputButton and VoiceWaveform with a red dot and the time
- ParameterPanel, SystemPromptEditor, TokenCounter, CostEstimator
- forms: a Newline action on Shift-Enter keeps one-line fields to one line;
  replace_trigger moves beside Suggestions; choose and dropped shared
- gallery: composer, pickers and tuning
This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 06:29:20 -04:00
1 parent fd9bcf486c
commit b2ce2df428
17 files changed
+1562 -38

No files matched your search

+14
View File
@@ -32,6 +32,20 @@ pub(crate) fn active_trigger(text: &str, caret: usize, triggers: &[char]) -> Opt
(opens && word).then_some((ix, ch))
}
/// Replaces the trigger and its query, `at..caret`, with `text`, as one undo step.
pub(crate) fn replace_trigger(
field: &Entity<TextInput>,
at: usize,
caret: usize,
text: &str,
cx: &mut App,
) {
field.update(cx, |input, cx| {
input.select(at..caret, cx);
input.insert(text, cx);
});
}
/// The dismissed trigger, while it is still the one being typed.
fn dismissal(dismissed: Option<usize>, active: Option<usize>) -> Option<usize> {
dismissed.filter(|at| active == Some(*at))
+3 -1
View File
@@ -63,6 +63,7 @@ pub use date::{
WeekPicker, YearPicker,
};
pub use expression::{ExpressionInput, evaluate, expression_highlights};
pub(crate) use files::dropped;
pub use files::{DropZone, FileInput};
pub(crate) use glyphs::emoji_found;
pub use glyphs::{EmojiPicker, IconPicker};
@@ -75,7 +76,7 @@ pub use knob::Knob;
pub use listbox::ListBox;
pub use masked::{COUNTRIES, MaskedInput, PhoneInput};
pub use mention::{MentionInput, mention_highlights};
pub(crate) use mention::{Suggestions, active_trigger, handles_matching};
pub(crate) use mention::{Suggestions, active_trigger, handles_matching, replace_trigger};
pub use multi::MultiSelect;
pub use number::{NumberInput, ScrubInput};
pub use options::Choice;
@@ -84,6 +85,7 @@ pub(crate) use options::{
};
pub use pairs::{FieldArray, KeyValueInput, ListInput};
pub use path::PathInput;
pub(crate) use path::choose;
pub use pattern::{RegexInput, regex_highlights};
pub use pin::PinInput;
pub use radio::{Radio, RadioGroup};
+14
View File
@@ -299,3 +299,17 @@ fn a_range_gives_a_box_per_line_it_spans(cx: &mut TestAppContext) {
"the second line starts at the edge"
);
}
#[gpui::test]
fn shift_enter_breaks_only_a_field_of_many_lines(cx: &mut TestAppContext) {
let (fields, cx) = open(1, false, cx);
focus(&fields[0], cx);
cx.simulate_input("one line");
cx.simulate_keystrokes("shift-enter");
cx.run_until_parked();
assert_eq!(
text(&fields[0], cx),
"one line",
"a single line stays single"
);
}
+9
View File
@@ -39,6 +39,7 @@ actions!(
Undo,
Redo,
Enter,
Newline,
Submit,
ShowCharacterPalette,
]
@@ -84,6 +85,7 @@ pub(crate) fn bind_keys(cx: &mut App) {
KeyBinding::new("secondary-z", Undo, Some(CONTEXT)),
KeyBinding::new("secondary-shift-z", Redo, Some(CONTEXT)),
KeyBinding::new("enter", Enter, Some(CONTEXT)),
KeyBinding::new("shift-enter", Newline, Some(CONTEXT)),
KeyBinding::new("secondary-enter", Submit, Some(CONTEXT)),
];
if cfg!(target_os = "macos") {
@@ -290,6 +292,12 @@ impl TextInput {
}
}
fn newline(&mut self, _: &Newline, _: &mut Window, cx: &mut Context<Self>) {
if self.rows.is_some() {
self.replace(self.selection.clone(), "\n", false, cx);
}
}
fn submit(&mut self, _: &Submit, _: &mut Window, cx: &mut Context<Self>) {
log::info!("text input: submitted");
cx.emit(InputEvent::Submit);
@@ -380,6 +388,7 @@ impl Render for TextInput {
.on_action(cx.listener(Self::undo))
.on_action(cx.listener(Self::redo))
.on_action(cx.listener(Self::enter))
.on_action(cx.listener(Self::newline))
.on_action(cx.listener(Self::submit))
.on_action(cx.listener(Self::show_character_palette))
.on_mouse_down(MouseButton::Left, cx.listener(Self::mouse_down))