Skip to content

Commit ad6432d

Browse files
authored
Merge pull request #118 from supreme-gg-gg/feat/message-summary
[feature] chat history summarization and command result display
2 parents 4d05166 + 4fdb2bf commit ad6432d

File tree

9 files changed

+294
-37
lines changed

9 files changed

+294
-37
lines changed

README.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,6 @@ The chat interface is the main feature of this package. It allows you to interac
107107

108108
In the chat list page, use arrow keys (or 'j', 'k') + Enter to select a chat. You can also search for user by username using @user_name + Enter.
109109

110-
> [!NOTE]
111-
> Chat commands (prefixed with `:`) are NOT available in the chat menu page. You must enter a chat to use chat commands.
112-
113110
After entering the chat page, you can type messages as usual and send them with Enter. You can also use chat commands to supercharge your chat experience.
114111

115112
> [!TIP]
@@ -136,9 +133,11 @@ All chat commands have the following syntax:
136133
- `:delay <seconds> "<message>"`: delay sending the message, similar as schedule
137134
- `:cancel`: cancel the latest scheduled/delayed message
138135
- `:upload`: upload media using the file navigator
139-
- `:upload <path>`: upload media (photo or video) directly from path
136+
- `:upload <path?>`: upload media (photo or video) directly from path
137+
- `:config <key?>=<value?>`: an in-chat version of `instagram config`
140138
- `:view <index>`: view and download media at index or open URL directly in browser
141139
- `:latex $<expr>$`: render and send LaTeX code as image, see [latex](#latex)
140+
- `:summarize <depth?>`: generate a summary of chat history using an LLM, see [chat summarization](#chat-summarization)
142141

143142
### Emoji
144143

@@ -150,7 +149,7 @@ will be rendered as
150149

151150
`This is an emoji 👍`
152151

153-
> ![TIP]
152+
> [!TIP]
154153
> This does not have to be an exact match with the emoji name. For example, `:thumbsup:` can also be written as `:thumbs_up:`.
155154
156155
### LaTeX
@@ -171,6 +170,37 @@ Please note that the LaTeX code **_MUST_** be enclosed in `$` symbols.
171170

172171
You can choose to render with [online API](https://latex.codecogs.com) (default) or local LaTeX installation such as TeX Live, MiKTeX, etc. You can set the rendering method with `instagram config --set latex_rendering_method <online|local>`.
173172

173+
### Chat Summarization
174+
175+
You can generate a summary of the chat history using the `:summarize` command. This will create a concise summary of the conversation, highlighting key points and important information.
176+
177+
Local LLMs are first-class citizens here, allowing for maximum privacy and flexibility. All you need is a local LLM inferencing server like [Ollama](https://ollama.com/), [LM Studio](https://lmstudio.ai/). You will need to specify `llm.endpoint` (OpenAI-compatible) and `llm.model` in the config. For example, for Ollama, this would likely be:
178+
179+
```plaintext
180+
http://localhost:11434/v1/
181+
```
182+
183+
You can do this with `instagram config --set llm.endpoint <URL>` and `instagram config --set llm.model <MODEL_NAME>`.
184+
185+
Once inside a chat conversation, you can summarize the chat history using:
186+
187+
```plaintext
188+
:summarize
189+
```
190+
191+
This will process all messages fetched in the current chat.
192+
193+
To limit (or expand) the summarization to the `n` most recent messages:
194+
195+
```plaintext
196+
:summarize n
197+
```
198+
199+
You can also turn on streaming mode with `instagram config --set llm.stream True` to see the summary being generated in real-time.
200+
201+
> [!TIP]
202+
> If you don't mind giving your data to AI companies, you may set the `llama.endpoint` and `llm.model` configs to a remote endpoint, e.g. `https://api.openai.com/v1/`, `gpt-5`.
203+
174204
### Scheduling Messages
175205

176206
You can schedule messages to be sent at a later time. The syntax is as follows:

instagram/chat_ui/components/chat_window.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import curses
2-
from typing import List
2+
from typing import List, Optional
33
from ..utils.types import LineInfo, ChatMode
44
from instagram.api import MessageInfo
55
from instagram.configs import Config
@@ -20,6 +20,7 @@ def __init__(self, window, height: int, width: int):
2020
self.scroll_offset = 0
2121
self.visible_messages_range = None
2222
self.visible_lines_range = None
23+
self.custom_content: Optional[str] = None
2324

2425
def set_messages(
2526
self, messages: List[MessageInfo]
@@ -28,6 +29,16 @@ def set_messages(
2829
self.messages = messages
2930
self._build_message_lines()
3031

32+
def set_custom_content(self, content: str):
33+
"""Set custom content to be displayed in the chat window."""
34+
self.custom_content = content
35+
self.update()
36+
37+
def clear_custom_content(self):
38+
"""Clear custom content."""
39+
self.custom_content = None
40+
self.update()
41+
3142
def _build_message_lines(self):
3243
"""
3344
Build wrapped lines for chat messages with word wrapping and formatting.
@@ -142,7 +153,23 @@ def update(self):
142153
- Basic word wrapping
143154
- Colored sender names
144155
- Replies and reactions
156+
157+
If custom content is set, it overrides the default message rendering.
145158
"""
159+
if self.custom_content:
160+
self.window.erase()
161+
content_lines = []
162+
for raw_line in self.custom_content.split("\n"):
163+
while len(raw_line) > self.width:
164+
content_lines.append(raw_line[: self.width])
165+
raw_line = raw_line[self.width :]
166+
content_lines.append(raw_line)
167+
# Only display up to available height
168+
for i, line in enumerate(content_lines[: self.height]):
169+
self.window.addstr(i, 0, line[: self.width])
170+
self.window.refresh()
171+
return
172+
146173
if not self.messages:
147174
return
148175

instagram/chat_ui/components/status_bar.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ def update(self, msg: str = None, override_default: bool = False):
3838
status_text = "[UNSEND] Use ↑↓ to select, Enter to confirm, Esc to exit"
3939
elif self.mode == ChatMode.COMMAND:
4040
self.window.bkgd(" ", curses.color_pair(2))
41-
status_text = f"[COMMAND] Command {msg} executed. Press any key to continue"
41+
status_text = f"[COMMAND] Executing command {msg if msg else '...'}"
42+
elif self.mode == ChatMode.COMMAND_RESULT:
43+
self.window.bkgd(" ", curses.color_pair(2))
44+
status_text = "[COMMAND RESULT] Press any key to return to chat"
4245
else:
4346
status_text = "Georgian mode"
4447

instagram/chat_ui/interface/chat_interface.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import curses
22
import threading
3+
import inspect
34
from ..components.input_box import InputBox
45
from ..components.chat_window import ChatWindow
56
from ..components.status_bar import StatusBar
@@ -166,7 +167,6 @@ def handle_input(self) -> Signal:
166167
if result[1] == ":":
167168
result = result[1:]
168169
else:
169-
self.set_mode(ChatMode.COMMAND)
170170
return self._handle_command(result[1:])
171171

172172
return self._handle_chat_message(result)
@@ -257,21 +257,26 @@ def _handle_command(self, command: str) -> Signal:
257257
"""
258258
Executes a command, listen for special return signals or display the result.
259259
"""
260+
# Show command execution status
260261
self.set_mode(ChatMode.COMMAND)
262+
self.status_bar.update(msg=command)
263+
264+
# Execute the command
261265
result = cmd_registry.execute(
262266
command, chat=self.direct_chat, screen=self.screen
263267
)
264268

265-
# Handle config changes command
266-
if isinstance(result, dict):
267-
for key, value in result.items():
268-
if Config().get(f"chat.{key}"):
269-
Config().set(f"chat.{key}", value)
270-
self.chat_window.update()
269+
# If result is a generator, stream the output
270+
if inspect.isgenerator(result):
271+
self.set_mode(ChatMode.COMMAND_RESULT)
272+
self.status_bar.update(msg=command)
273+
self._display_streaming_command_result(result)
271274
self.set_mode(ChatMode.CHAT)
275+
self.chat_window.update()
276+
self.status_bar.update()
272277
return Signal.CONTINUE
273278

274-
# Handle special return signals
279+
# Otherwise, the result is a string and we handle special return signals first
275280
elif result == "__QUIT__":
276281
self.stop_refresh.set()
277282
return Signal.QUIT
@@ -406,24 +411,46 @@ def _handle_command(self, command: str) -> Signal:
406411

407412
# Regular command result display
408413
else:
414+
# Display result and wait for key press
415+
self.set_mode(ChatMode.COMMAND_RESULT)
409416
self.status_bar.update(msg=command)
410417
self._display_command_result(result)
411-
curses.napms(1000)
412418
self.set_mode(ChatMode.CHAT)
419+
self.chat_window.update()
413420
self.status_bar.update()
414421
return Signal.CONTINUE
415422

416423
def _display_command_result(self, result: str):
417424
"""
418425
Display the text result of a command in the chat window.
426+
This is a blocking operation that waits for user key press.
419427
"""
420-
# TODO: Move this to chat window class
421-
self.chat_window.window.erase()
422-
lines = result.split("\n")
423-
for idx, line in enumerate(lines):
424-
if idx < self.height - 5:
425-
self.chat_window.window.addstr(idx, 0, line[: self.width - 1])
426-
self.chat_window.window.refresh()
428+
self.chat_window.set_custom_content(result)
429+
# Clear any buffered input that occurred during command execution
430+
curses.flushinp()
431+
# Handle command result mode - wait for any key press
432+
self.screen.get_wch() # Wait for any key press
433+
self.chat_window.clear_custom_content() # Clear content after display
434+
435+
def _display_streaming_command_result(self, result_generator):
436+
"""
437+
Display the streaming text result of a command in the chat window.
438+
"""
439+
self.chat_window.set_custom_content("")
440+
curses.flushinp()
441+
full_response = ""
442+
try:
443+
for chunk in result_generator:
444+
full_response += chunk
445+
self.chat_window.set_custom_content(full_response)
446+
self.chat_window.update()
447+
except Exception as e:
448+
self.status_bar.update(f"Streaming error: {e}", override_default=True)
449+
curses.napms(2000)
450+
451+
# After streaming is complete, wait for a key press to exit
452+
self.screen.get_wch()
453+
self.chat_window.clear_custom_content()
427454

428455
def _handle_chat_message(self, message: str) -> Signal:
429456
"""

0 commit comments

Comments
 (0)