From f26d68f6d45434588ecb1413dc87fb52707edf52 Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 12:10:01 -0400 Subject: [PATCH 01/13] Add TUI environment for testing encoding in tiktoken --- Cargo.toml | 5 ++ src/lib.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++ tiktoken/core.py | 4 ++ 3 files changed, 134 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 4efb156f..5cc22ac5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,8 @@ fancy-regex = "0.11.0" regex = "1.8.3" rustc-hash = "1.1.0" bstr = "1.5.0" +# cli dependencies +tui-textarea = "0.4.0" +crossterm = "0.27.0" +ratatui = "0.26.3" + diff --git a/src/lib.rs b/src/lib.rs index b466edd1..e9799252 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,12 +5,33 @@ use std::collections::HashSet; use std::num::NonZeroU64; use std::thread; +use bstr::ByteSlice; +use crossterm::style::Stylize; use fancy_regex::Regex; use pyo3::exceptions; use pyo3::prelude::*; use pyo3::pyclass; use pyo3::PyResult; use pyo3::types::{PyBytes, PyList, PyTuple}; + +use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; +use crossterm::terminal::{ + disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, +}; + +use ratatui::layout::{Layout, Direction}; +use ratatui::prelude::{Span, Constraint}; +use ratatui::backend::CrosstermBackend; +use ratatui::text::Line; +use ratatui::widgets::block::Title; +use ratatui::widgets::Padding; +use ratatui::widgets::{Block, Borders, Wrap}; +use ratatui::style::{Color, Style}; +use ratatui::Terminal; +use std::io; +use tui_textarea::{Input, Key, TextArea}; + +use ratatui::widgets::Paragraph; use rustc_hash::FxHashMap as HashMap; type Rank = u32; @@ -562,8 +583,112 @@ impl CoreBPE { .map(|x| PyBytes::new(py, x).into()) .collect() } + + fn _environment(&self, py : Python, name : String) -> PyResult<()> { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + enable_raw_mode()?; + crossterm::execute!(stdout, EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout); + let mut term = Terminal::new(backend)?; + + let mut textarea = TextArea::default(); + textarea.set_block( + Block::default() + .borders(Borders::ALL) + .title(format!("{} Encoder",name)).padding(Padding::new(1, 1, 1, 0)) + ); + + let colours = vec![Color::Red, Color::Green, Color::Blue, Color::Yellow, Color::Magenta, Color::Cyan]; + + let parent_layout = Layout::default() + .constraints([Constraint::Percentage(100), Constraint::Min(1)]); + + let layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()); + + + + loop { + let mut current_color_index = 0; + term.draw(|f| { + + + let chunks = parent_layout.split(f.size()); + + let sub_chunk = layout.split(chunks[0]); + + let encoding = self.encode_ordinary(py, textarea.lines().join("\n").as_str()); + let decoding: Vec> = encoding.iter().map(|&token| self + .decode_single_token_bytes(py, token).unwrap()).collect(); + + let tokens : Vec = decoding.iter().map(|py_bytes| { + let bytes = py_bytes.as_ref(py); // Convert Py to &PyBytes + bytes.as_bytes() + .to_str() + .unwrap() + .to_string() // Convert &PyBytes to &str and then to String + }).collect(); + + let spans: Vec = tokens.iter().map(|token| { + let color = colours[current_color_index]; + current_color_index = (current_color_index + 1) % colours.len(); + if token == "\n"{ + println!("{}", token); + } + Span::styled(token, Style::default().bg(color).fg(Color::White)) + }).collect(); + + let paragraph = Paragraph::new(Line::from(spans.clone())) + .block(Block::default().borders(Borders::ALL) + .title("Decoded Tokens") + .title(Title::from(Line::from(vec![ + Span::styled(spans.len().to_string(), + Style::new().fg(Color::Green)), + Span::from(" token(s)")])) + .alignment(ratatui::layout::Alignment::Center) + .position(ratatui::widgets::block::Position::Bottom)) + .padding(Padding::new(1, 1, 1, 1))) + .wrap(Wrap { trim: true }); + + let menu = Block::new() + .title(Title::from("[Esc] Exit") + .alignment(ratatui::layout::Alignment::Left)) + .padding(Padding::horizontal(5u16)) + .border_style(Style::default().fg(Color::White)) + .borders(Borders::TOP); + + f.render_widget(menu, chunks[1]); + f.render_widget(textarea.widget(), sub_chunk[0]); + f.render_widget(paragraph, sub_chunk[1]); + + })?; + match crossterm::event::read()?.into() { + Input { key: Key::Esc, .. } => break, + input => { + textarea.input(input); + } + } + } + + disable_raw_mode()?; + crossterm::execute!( + term.backend_mut(), + LeaveAlternateScreen, + )?; + term.show_cursor()?; + + Ok(()) + + } } + + + + #[pymodule] fn _tiktoken(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; diff --git a/tiktoken/core.py b/tiktoken/core.py index 3e32d4ce..5eb574a5 100644 --- a/tiktoken/core.py +++ b/tiktoken/core.py @@ -367,6 +367,10 @@ def _encode_only_native_bpe(self, text: str) -> list[int]: def _encode_bytes(self, text: bytes) -> list[int]: return self._core_bpe._encode_bytes(text) + + def enviorment(self) -> None: + """Builds a Text User Interface (TUI) environment to test out encoding.""" + return self._core_bpe._environment(self.name) def __getstate__(self) -> object: import tiktoken.registry From ccab1bba012608859f6b8c0661746b929ebada1a Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 12:24:12 -0400 Subject: [PATCH 02/13] Fix typo in environment method name in tiktoken/core.py --- tiktoken/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiktoken/core.py b/tiktoken/core.py index 5eb574a5..e3948625 100644 --- a/tiktoken/core.py +++ b/tiktoken/core.py @@ -368,7 +368,7 @@ def _encode_only_native_bpe(self, text: str) -> list[int]: def _encode_bytes(self, text: bytes) -> list[int]: return self._core_bpe._encode_bytes(text) - def enviorment(self) -> None: + def environment(self) -> None: """Builds a Text User Interface (TUI) environment to test out encoding.""" return self._core_bpe._environment(self.name) From a9e404755a0c4282474ab069bf0876e206ee7e90 Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 12:51:16 -0400 Subject: [PATCH 03/13] Refactor environment method in tiktoken/core.py to accept allowed_special and disallowed_special parameters --- src/lib.rs | 4 ++-- tiktoken/core.py | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e9799252..58ac3f49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -584,7 +584,7 @@ impl CoreBPE { .collect() } - fn _environment(&self, py : Python, name : String) -> PyResult<()> { + fn _environment(&self, py : Python, name : &str, allowed_special: HashSet<&str>) -> PyResult<()> { let stdout = io::stdout(); let mut stdout = stdout.lock(); @@ -620,7 +620,7 @@ impl CoreBPE { let sub_chunk = layout.split(chunks[0]); - let encoding = self.encode_ordinary(py, textarea.lines().join("\n").as_str()); + let encoding = self._encode_native(textarea.lines().join("\n").as_str(), &allowed_special).0; let decoding: Vec> = encoding.iter().map(|&token| self .decode_single_token_bytes(py, token).unwrap()).collect(); diff --git a/tiktoken/core.py b/tiktoken/core.py index e3948625..9330fcd7 100644 --- a/tiktoken/core.py +++ b/tiktoken/core.py @@ -115,7 +115,7 @@ def encode( disallowed_special = frozenset(disallowed_special) if match := _special_token_regex(disallowed_special).search(text): raise_disallowed_special_token(match.group()) - + # https://github.com/PyO3/pyo3/pull/3632 if isinstance(allowed_special, frozenset): allowed_special = set(allowed_special) @@ -368,9 +368,18 @@ def _encode_only_native_bpe(self, text: str) -> list[int]: def _encode_bytes(self, text: bytes) -> list[int]: return self._core_bpe._encode_bytes(text) - def environment(self) -> None: + def environment(self, + *, + allowed_special: Union[Literal["all"], AbstractSet[str]] = set(), # noqa: B006 + disallowed_special: Union[Literal["all"], Collection[str]] = "all",) -> None: """Builds a Text User Interface (TUI) environment to test out encoding.""" - return self._core_bpe._environment(self.name) + + if allowed_special == "all": + allowed_special = self.special_tokens_set + if disallowed_special == "all": + disallowed_special = self.special_tokens_set - allowed_special + + return self._core_bpe._environment(self.name, allowed_special) def __getstate__(self) -> object: import tiktoken.registry From 1800e3ebcf0f8dbd553b1554d5b74d8b31c90cb5 Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 12:53:31 -0400 Subject: [PATCH 04/13] update README.md to mention tiktoken environment --- README.md | 12 ++++++++++++ environment.png | Bin 0 -> 71954 bytes 2 files changed, 12 insertions(+) create mode 100644 environment.png diff --git a/README.md b/README.md index 124d5828..fc6f3d56 100644 --- a/README.md +++ b/README.md @@ -128,3 +128,15 @@ setup( Then simply `pip install ./my_tiktoken_extension` and you should be able to use your custom encodings! Make sure **not** to use an editable install. + + +## Tiktoken tokenizer environment + +Test your tokenizer through a terminal-based environment that allows you to visualize tokenized data points. This tool helps you better grasp model information by providing immediate feedback on how input text is being tokenized. You can see token types, and their positions in the input text, making it easier to understand and debug your tokenizer. + +```python +import tiktoken +enc = tiktoken.get_encoding("gpt2") +enc.environment() +``` +![image](https://raw.githubusercontent.com/openai/tiktoken/main/environment.png) \ No newline at end of file diff --git a/environment.png b/environment.png new file mode 100644 index 0000000000000000000000000000000000000000..899374a94b794d350138aa9378ba41d39805523d GIT binary patch literal 71954 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%0DDvPya*<%+ahI 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 Date: Tue, 18 Jun 2024 12:59:09 -0400 Subject: [PATCH 05/13] update README.md to mention tiktoken environment --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc6f3d56..9227c483 100644 --- a/README.md +++ b/README.md @@ -139,4 +139,4 @@ import tiktoken enc = tiktoken.get_encoding("gpt2") enc.environment() ``` -![image](https://raw.githubusercontent.com/openai/tiktoken/main/environment.png) \ No newline at end of file +![image](/environment.png) From c674c916474d0a8c8826be6d3531756b565b188c Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 13:38:36 -0400 Subject: [PATCH 06/13] Add support for PNG files in MANIFEST.in --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 7f25b271..e74e83da 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ +include *.png include *.svg include *.toml include *.md From def13cc6070fa0ca07e35433a73d87a8887cfb2f Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 16:30:31 -0400 Subject: [PATCH 07/13] clean up unused dependencies src/lib.rs --- src/lib.rs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 58ac3f49..fa5ceb24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,6 @@ use std::num::NonZeroU64; use std::thread; use bstr::ByteSlice; -use crossterm::style::Stylize; use fancy_regex::Regex; use pyo3::exceptions; use pyo3::prelude::*; @@ -14,24 +13,21 @@ use pyo3::pyclass; use pyo3::PyResult; use pyo3::types::{PyBytes, PyList, PyTuple}; -use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; + use crossterm::terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }; - -use ratatui::layout::{Layout, Direction}; -use ratatui::prelude::{Span, Constraint}; +use ratatui::Terminal; use ratatui::backend::CrosstermBackend; use ratatui::text::Line; -use ratatui::widgets::block::Title; -use ratatui::widgets::Padding; -use ratatui::widgets::{Block, Borders, Wrap}; +use ratatui::layout::{Layout, Direction}; +use ratatui::prelude::{Span, Constraint}; use ratatui::style::{Color, Style}; -use ratatui::Terminal; -use std::io; +use ratatui::widgets::{ + block::Title, Padding, Paragraph, Block, Borders, Wrap as WrapR +}; use tui_textarea::{Input, Key, TextArea}; -use ratatui::widgets::Paragraph; use rustc_hash::FxHashMap as HashMap; type Rank = u32; @@ -585,7 +581,7 @@ impl CoreBPE { } fn _environment(&self, py : Python, name : &str, allowed_special: HashSet<&str>) -> PyResult<()> { - let stdout = io::stdout(); + let stdout = std::io::stdout(); let mut stdout = stdout.lock(); enable_raw_mode()?; @@ -613,9 +609,9 @@ impl CoreBPE { loop { let mut current_color_index = 0; - term.draw(|f| { - + term.draw(|f| { + let chunks = parent_layout.split(f.size()); let sub_chunk = layout.split(chunks[0]); @@ -650,9 +646,9 @@ impl CoreBPE { Span::from(" token(s)")])) .alignment(ratatui::layout::Alignment::Center) .position(ratatui::widgets::block::Position::Bottom)) - .padding(Padding::new(1, 1, 1, 1))) - .wrap(Wrap { trim: true }); - + .padding(Padding::new(1, 1, 1, 1))) + .wrap(WrapR { trim: true }); + let menu = Block::new() .title(Title::from("[Esc] Exit") .alignment(ratatui::layout::Alignment::Left)) From 9a35100570bcc258b471deb499d5e6dc690197f4 Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 16:33:33 -0400 Subject: [PATCH 08/13] Remove unused bstr::ByteSlice import in src/lib.rs --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fa5ceb24..e533dab1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ use std::collections::HashSet; use std::num::NonZeroU64; use std::thread; -use bstr::ByteSlice; use fancy_regex::Regex; use pyo3::exceptions; use pyo3::prelude::*; From afc66ed824705f53419cd410a0319f9c80b4e943 Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 18:00:31 -0400 Subject: [PATCH 09/13] Revert "clean up unused dependencies src/lib.rs" This reverts commit def13cc6070fa0ca07e35433a73d87a8887cfb2f. --- src/lib.rs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e533dab1..4d463a7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,11 @@ use std::collections::HashSet; use std::num::NonZeroU64; use std::thread; +<<<<<<< HEAD +======= +use bstr::ByteSlice; +use crossterm::style::Stylize; +>>>>>>> parent of def13cc (clean up unused dependencies src/lib.rs) use fancy_regex::Regex; use pyo3::exceptions; use pyo3::prelude::*; @@ -12,21 +17,24 @@ use pyo3::pyclass; use pyo3::PyResult; use pyo3::types::{PyBytes, PyList, PyTuple}; - +use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; use crossterm::terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }; -use ratatui::Terminal; -use ratatui::backend::CrosstermBackend; -use ratatui::text::Line; + use ratatui::layout::{Layout, Direction}; use ratatui::prelude::{Span, Constraint}; +use ratatui::backend::CrosstermBackend; +use ratatui::text::Line; +use ratatui::widgets::block::Title; +use ratatui::widgets::Padding; +use ratatui::widgets::{Block, Borders, Wrap}; use ratatui::style::{Color, Style}; -use ratatui::widgets::{ - block::Title, Padding, Paragraph, Block, Borders, Wrap as WrapR -}; +use ratatui::Terminal; +use std::io; use tui_textarea::{Input, Key, TextArea}; +use ratatui::widgets::Paragraph; use rustc_hash::FxHashMap as HashMap; type Rank = u32; @@ -580,7 +588,7 @@ impl CoreBPE { } fn _environment(&self, py : Python, name : &str, allowed_special: HashSet<&str>) -> PyResult<()> { - let stdout = std::io::stdout(); + let stdout = io::stdout(); let mut stdout = stdout.lock(); enable_raw_mode()?; @@ -608,9 +616,9 @@ impl CoreBPE { loop { let mut current_color_index = 0; - term.draw(|f| { - + + let chunks = parent_layout.split(f.size()); let sub_chunk = layout.split(chunks[0]); @@ -645,9 +653,9 @@ impl CoreBPE { Span::from(" token(s)")])) .alignment(ratatui::layout::Alignment::Center) .position(ratatui::widgets::block::Position::Bottom)) - .padding(Padding::new(1, 1, 1, 1))) - .wrap(WrapR { trim: true }); - + .padding(Padding::new(1, 1, 1, 1))) + .wrap(Wrap { trim: true }); + let menu = Block::new() .title(Title::from("[Esc] Exit") .alignment(ratatui::layout::Alignment::Left)) From a075d45917b7ba14eeea1852da2157c5efd05705 Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Tue, 18 Jun 2024 18:15:19 -0400 Subject: [PATCH 10/13] Refactor imports and clean up code in src/lib.rs --- src/lib.rs | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4d463a7c..f130aa2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,7 @@ use std::collections::HashSet; use std::num::NonZeroU64; use std::thread; -<<<<<<< HEAD -======= use bstr::ByteSlice; -use crossterm::style::Stylize; ->>>>>>> parent of def13cc (clean up unused dependencies src/lib.rs) use fancy_regex::Regex; use pyo3::exceptions; use pyo3::prelude::*; @@ -17,25 +13,23 @@ use pyo3::pyclass; use pyo3::PyResult; use pyo3::types::{PyBytes, PyList, PyTuple}; -use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; +use rustc_hash::FxHashMap as HashMap; + +// so many imports :D use crossterm::terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }; - -use ratatui::layout::{Layout, Direction}; use ratatui::prelude::{Span, Constraint}; +use ratatui::Terminal; use ratatui::backend::CrosstermBackend; +use ratatui::layout::{Layout, Direction}; use ratatui::text::Line; -use ratatui::widgets::block::Title; -use ratatui::widgets::Padding; -use ratatui::widgets::{Block, Borders, Wrap}; +use ratatui::widgets::{ + block::Title, Padding, Block, Borders, Wrap, Paragraph +}; use ratatui::style::{Color, Style}; -use ratatui::Terminal; -use std::io; use tui_textarea::{Input, Key, TextArea}; -use ratatui::widgets::Paragraph; -use rustc_hash::FxHashMap as HashMap; type Rank = u32; @@ -588,7 +582,7 @@ impl CoreBPE { } fn _environment(&self, py : Python, name : &str, allowed_special: HashSet<&str>) -> PyResult<()> { - let stdout = io::stdout(); + let stdout = std::io::stdout(); let mut stdout = stdout.lock(); enable_raw_mode()?; @@ -638,9 +632,6 @@ impl CoreBPE { let spans: Vec = tokens.iter().map(|token| { let color = colours[current_color_index]; current_color_index = (current_color_index + 1) % colours.len(); - if token == "\n"{ - println!("{}", token); - } Span::styled(token, Style::default().bg(color).fg(Color::White)) }).collect(); @@ -689,9 +680,6 @@ impl CoreBPE { } - - - #[pymodule] fn _tiktoken(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; From 04f1f8d89f3d4b9ea056c73dfe2d5dc46838cefe Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Wed, 19 Jun 2024 16:39:08 -0400 Subject: [PATCH 11/13] update layout, and scrollview of tokenizer --- src/lib.rs | 98 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f130aa2a..d3a02cc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,10 +22,10 @@ use crossterm::terminal::{ use ratatui::prelude::{Span, Constraint}; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; -use ratatui::layout::{Layout, Direction}; +use ratatui::layout::{Layout, Direction, Margin}; use ratatui::text::Line; use ratatui::widgets::{ - block::Title, Padding, Block, Borders, Wrap, Paragraph + block::Title, Padding, Block, Borders, Wrap, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState }; use ratatui::style::{Color, Style}; use tui_textarea::{Input, Key, TextArea}; @@ -606,45 +606,71 @@ impl CoreBPE { .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()); - + let mut vertical_scroll = 0; + let mut line_count : usize = 0; loop { let mut current_color_index = 0; term.draw(|f| { - - let chunks = parent_layout.split(f.size()); let sub_chunk = layout.split(chunks[0]); - - let encoding = self._encode_native(textarea.lines().join("\n").as_str(), &allowed_special).0; - let decoding: Vec> = encoding.iter().map(|&token| self - .decode_single_token_bytes(py, token).unwrap()).collect(); - let tokens : Vec = decoding.iter().map(|py_bytes| { - let bytes = py_bytes.as_ref(py); // Convert Py to &PyBytes - bytes.as_bytes() - .to_str() - .unwrap() - .to_string() // Convert &PyBytes to &str and then to String - }).collect(); - - let spans: Vec = tokens.iter().map(|token| { - let color = colours[current_color_index]; - current_color_index = (current_color_index + 1) % colours.len(); - Span::styled(token, Style::default().bg(color).fg(Color::White)) + let tokens: Vec> = textarea.lines().iter().map(|line| { + + // Encode the line + let encoding = self._encode_native(line, &allowed_special).0; + + // Decode the encoded line + let decoding: Vec> = encoding.iter() + .map(|&token| self.decode_single_token_bytes(py, token).unwrap()) + .collect(); + + // Convert decoded tokens to Strings + let tokens: Vec = decoding.iter().map(|py_bytes| { + let bytes = py_bytes.as_bytes(py); + + bytes.to_str() + .unwrap() + .to_string() + + }).collect(); + + tokens + // Create spans for the line + }).collect(); - let paragraph = Paragraph::new(Line::from(spans.clone())) + let mut lines : Vec = Vec::new(); + let mut token_count = 0; + for token in &tokens{ + let span : Vec = token.iter().map(|token| { + let color = colours[current_color_index]; + current_color_index = (current_color_index + 1) % colours.len(); + token_count += 1; // Increment the token count + Span::styled(token, Style::default().bg(color).fg(Color::White)) + }).collect(); + lines.push(Line::from(span)); + + } + + + let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) + .begin_symbol(Some("↑")) + .end_symbol(Some("↓")); + + + let paragraph = Paragraph::new(lines.clone()) .block(Block::default().borders(Borders::ALL) .title("Decoded Tokens") .title(Title::from(Line::from(vec![ - Span::styled(spans.len().to_string(), + Span::styled(token_count.to_string(), Style::new().fg(Color::Green)), Span::from(" token(s)")])) .alignment(ratatui::layout::Alignment::Center) .position(ratatui::widgets::block::Position::Bottom)) .padding(Padding::new(1, 1, 1, 1))) + .scroll((vertical_scroll as u16, 0)) .wrap(Wrap { trim: true }); let menu = Block::new() @@ -653,14 +679,40 @@ impl CoreBPE { .padding(Padding::horizontal(5u16)) .border_style(Style::default().fg(Color::White)) .borders(Borders::TOP); + + line_count = lines.len(); + let mut scrollbar_state = ScrollbarState::new(line_count) + .position(vertical_scroll); f.render_widget(menu, chunks[1]); f.render_widget(textarea.widget(), sub_chunk[0]); f.render_widget(paragraph, sub_chunk[1]); + f.render_stateful_widget( + scrollbar, + sub_chunk[1].inner(&Margin { + // using an inner vertical margin of 1 unit makes the scrollbar inside the block + vertical: 1, + horizontal: 0, + }), + &mut scrollbar_state, + ); + })?; + match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => break, + Input { key: Key::Char('s'), ctrl : true, ..} => { + if vertical_scroll < line_count - 1 { + vertical_scroll+=1; + } + + }, + Input { key: Key::Char('a'), ctrl : true, ..} => { + if vertical_scroll > 0 { + vertical_scroll-=1; + } + }, input => { textarea.input(input); } From 2c6af5c6aed2184bc9e8cd26eab29326cc0d281a Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Wed, 19 Jun 2024 16:45:16 -0400 Subject: [PATCH 12/13] Update menu layout and add scroll functionality in src/lib.rs --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d3a02cc3..70b8797e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -674,8 +674,9 @@ impl CoreBPE { .wrap(Wrap { trim: true }); let menu = Block::new() - .title(Title::from("[Esc] Exit") - .alignment(ratatui::layout::Alignment::Left)) + .title(Title::from("[Esc] Exit").alignment(ratatui::layout::Alignment::Left)) + .title(Title::from("[Ctrl+S] Scroll Down").alignment(ratatui::layout::Alignment::Center)) + .title(Title::from("[Ctrl+A] Scroll Up").alignment(ratatui::layout::Alignment::Center)) .padding(Padding::horizontal(5u16)) .border_style(Style::default().fg(Color::White)) .borders(Borders::TOP); From 1ffca5a4c48c24d957190b3cceb405a79a2612fd Mon Sep 17 00:00:00 2001 From: Luca Vivona Date: Mon, 17 Feb 2025 11:37:07 -0500 Subject: [PATCH 13/13] replace Union with | in environment function annotations --- tiktoken/core.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tiktoken/core.py b/tiktoken/core.py index 58d34816..464381f1 100644 --- a/tiktoken/core.py +++ b/tiktoken/core.py @@ -375,6 +375,18 @@ def n_vocab(self) -> int: """For backwards compatibility. Prefer to use `enc.max_token_value + 1`.""" return self.max_token_value + 1 + def environment(self, + *, + allowed_special: Literal["all"] | AbstractSet[str] = set(), # noqa: B006 + disallowed_special: Literal["all"] | Collection[str] = "all",) -> None: + """Builds a Text User Interface (TUI) environment to test out encoding.""" + + if allowed_special == "all": + allowed_special = self.special_tokens_set + if disallowed_special == "all": + disallowed_special = self.special_tokens_set - allowed_special + + return self._core_bpe._environment(self.name, allowed_special) # ==================== # Private # ==================== @@ -403,19 +415,6 @@ def _encode_only_native_bpe(self, text: str) -> list[int]: def _encode_bytes(self, text: bytes) -> list[int]: return self._core_bpe._encode_bytes(text) - - def environment(self, - *, - allowed_special: Union[Literal["all"], AbstractSet[str]] = set(), # noqa: B006 - disallowed_special: Union[Literal["all"], Collection[str]] = "all",) -> None: - """Builds a Text User Interface (TUI) environment to test out encoding.""" - - if allowed_special == "all": - allowed_special = self.special_tokens_set - if disallowed_special == "all": - disallowed_special = self.special_tokens_set - allowed_special - - return self._core_bpe._environment(self.name, allowed_special) def __getstate__(self) -> object: import tiktoken.registry