Skip to content

Commit

Permalink
feat: enable chat on suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
1x-eng committed Nov 5, 2024
1 parent a878bfb commit 0328561
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 1 deletion.
8 changes: 8 additions & 0 deletions pkg/llm/assistant.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,11 @@ Maintain maximum analytical depth while ensuring clarity and actionability in pr

return response, nil
}

func (a *Assistant) StartSuggestionChat(suggestions []string) *SuggestionChat {
return NewSuggestionChat(
a,
a.context,
suggestions,
)
}
93 changes: 93 additions & 0 deletions pkg/llm/chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package llm

import (
"fmt"
"strings"
)

type SuggestionChat struct {
assistant *Assistant
history []Message
context string
suggestions []string
}

func NewSuggestionChat(assistant *Assistant, initialContext string, suggestions []string) *SuggestionChat {
return &SuggestionChat{
assistant: assistant,
history: make([]Message, 0),
context: initialContext,
suggestions: suggestions,
}
}

func (sc *SuggestionChat) Chat(userInput string) (string, error) {
// Add user message to history
sc.history = append(sc.history, Message{
Role: "user",
Content: userInput,
})

systemPrompt := `You are an advanced task optimization assistant engaged in a discussion about specific task suggestions. Your core responsibilities:
CONTEXT AWARENESS:
- Maintain strict relevance to the session context and current suggestions
- Detect and flag off-topic or digressing questions
- Guide users back to relevant discussion points
SUGGESTION CLARIFICATION:
- Provide detailed, actionable explanations for suggestions
- Break down complex tasks into clear, achievable steps
- Highlight dependencies and prerequisites
- Explain the reasoning behind each suggestion
- Focus on practical implementation details
RESPONSE GUIDELINES:
1. If question is relevant:
- Provide clear, structured response
- Include specific steps or clarifications
- Reference context when applicable
- Maintain focus on task completion
2. If question seems off-topic:
- Politely flag the digression
- Explain why it seems unrelated
- Offer to hear user's perspective
- Guide back to relevant discussion
3. For implementation queries:
- Break down into concrete steps
- Highlight potential challenges
- Suggest specific approaches
- Focus on actionability
Current Session Context:
%s
Current Suggestions Under Discussion:
%s`

messages := []Message{
{
Role: "system",
Content: fmt.Sprintf(systemPrompt, sc.context, strings.Join(sc.suggestions, "\n")),
},
}

// Add chat history
messages = append(messages, sc.history...)

// Get response from llm
response, err := sc.assistant.perplexity.GetResponse(messages)
if err != nil {
return "", err
}

// Add assistant's response to history
sc.history = append(sc.history, Message{
Role: "assistant",
Content: response,
})

return response, nil
}
97 changes: 96 additions & 1 deletion pkg/pomodoro/pom.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/1x-eng/tomatick/pkg/ltm"

"github.com/1x-eng/tomatick/pkg/llm"

"github.com/1x-eng/tomatick/pkg/markdown"

"github.com/1x-eng/tomatick/config"
Expand All @@ -21,6 +22,8 @@ import (
"github.com/chzyer/readline"
"github.com/logrusorgru/aurora"

"bufio"

"github.com/1x-eng/tomatick/pkg/context"
"github.com/1x-eng/tomatick/pkg/ui"
tea "github.com/charmbracelet/bubbletea"
Expand All @@ -37,6 +40,7 @@ var commandInstructions = []struct {
{"edit N text", "Edit task number N with new text"},
{"remove N", "Remove task number N from the list"},
{"suggest", "Get AI-powered task suggestions"},
{"discuss suggestions", "Start an interactive discussion about current suggestions"},
{"flush", "Clear any existing in-memory AI suggestions"},
{"help", "Show this help message"},
{"quit", "End the session and save progress"},
Expand All @@ -55,6 +59,7 @@ type TomatickMemento struct {
currentSuggestions []string
currentTasks []string
lastAnalysis string
currentChat *llm.SuggestionChat
}

func NewTomatickMemento(cfg *config.Config) *TomatickMemento {
Expand Down Expand Up @@ -235,7 +240,6 @@ func (p *TomatickMemento) captureTasks() []string {
instructions := p.theme.Styles.SystemInstruction.Render(sb.String())
fmt.Println(p.theme.Styles.Subtitle.Render(header + "\n" + instructions))

assistant := llm.NewAssistant(p.llmClient, p.sessionContext)
var tasks []string
rl, _ := readline.New(p.auroraInstance.BrightGreen("➤ ").String())
defer rl.Close()
Expand Down Expand Up @@ -271,6 +275,7 @@ func (p *TomatickMemento) captureTasks() []string {
}
}()

assistant := llm.NewAssistant(p.llmClient, p.sessionContext)
suggestions, err := assistant.GetTaskSuggestions(tasks, p.lastAnalysis)
done <- true
fmt.Print("\r") // Clear spinner line
Expand All @@ -280,7 +285,10 @@ func (p *TomatickMemento) captureTasks() []string {
continue
}
p.currentSuggestions = suggestions // Store suggestions
// Initialize chat session here
p.currentChat = assistant.StartSuggestionChat(suggestions)
p.displaySuggestions(suggestions)
fmt.Println(p.theme.Styles.InfoText.Render("\nType 'discuss suggestions' to discuss these suggestions with your copilot"))
case "flush":
p.FlushSuggestions()
case "quit":
Expand All @@ -292,6 +300,12 @@ func (p *TomatickMemento) captureTasks() []string {
continue
case "":
fmt.Println(p.auroraInstance.Red("❗ Task cannot be empty. Please try again."))
case "discuss suggestions":
if p.currentChat == nil {
fmt.Println(p.auroraInstance.Red("❗ No active suggestion session. Use 'suggest' first."))
continue
}
p.handleSuggestionChat()
default:
if strings.HasPrefix(input, "edit ") {
p.editTask(&tasks, input)
Expand Down Expand Up @@ -603,3 +617,84 @@ func (p *TomatickMemento) FlushSuggestions() {
p.lastAnalysis = ""
fmt.Println(p.auroraInstance.Green("✓ Copilot suggestions and analysis cache flushed successfully."))
}

func (p *TomatickMemento) handleSuggestionChat() {
// Display chat session start
chatBorder := strings.Repeat(p.theme.Emoji.ChatDivider, 50)
fmt.Println(p.theme.Styles.ChatBorder.Render(chatBorder))
fmt.Println(p.theme.Styles.ChatHeader.Render(
fmt.Sprintf("%s Interactive Suggestion Discussion %s",
p.theme.Emoji.ChatStart,
p.theme.Emoji.Brain)))
fmt.Println(p.theme.Styles.ChatBorder.Render(chatBorder))

// Display current suggestions for reference
fmt.Println(p.theme.Styles.InfoText.Render("\nCurrent Suggestions:"))
for i, suggestion := range p.currentSuggestions {
fmt.Printf("%s %s %s\n",
p.theme.Emoji.Bullet,
p.theme.Styles.TaskNumber.Render(fmt.Sprintf("%d.", i+1)),
p.theme.Styles.AIMessage.Render(suggestion))
}

fmt.Println(p.theme.Styles.SystemInstruction.Render("\nAsk questions or discuss these suggestions (type 'exit' to end chat)"))

scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Printf("%s", p.theme.Styles.ChatPrompt.PaddingTop(0).PaddingBottom(0).Render(p.theme.Emoji.UserInput+" "))

if !scanner.Scan() {
break
}

input := strings.TrimSpace(scanner.Text())
if input == "" {
continue
}

if input == "exit" {
fmt.Println(p.theme.Styles.ChatBorder.Render(chatBorder))
fmt.Println(p.theme.Styles.ChatHeader.Render(
fmt.Sprintf("%s Chat session ended %s",
p.theme.Emoji.ChatEnd,
p.theme.Emoji.Success)))
fmt.Println(p.theme.Styles.ChatBorder.Render(chatBorder))
break
}

// Show thinking spinner
spinner := ui.NewSpinner(p.theme.Styles.Spinner.
Foreground(lipgloss.Color("#818CF8")).
Bold(true))
done := make(chan bool)

go func() {
for {
select {
case <-done:
return
default:
fmt.Printf("\r%s Thinking...", spinner.Next())
time.Sleep(100 * time.Millisecond)
}
}
}()

response, err := p.currentChat.Chat(input)
done <- true
fmt.Print("\r\033[K") // Clear spinner line

if err != nil {
fmt.Println(p.theme.Styles.ErrorText.Render(
fmt.Sprintf("%s Error: %v", p.theme.Emoji.Error, err)))
continue
}

// Format and display response
fmt.Println(p.theme.Styles.ChatDivider.Render(strings.Repeat("─", 50)))
fmt.Printf("%s %s\n",
p.theme.Emoji.AIResponse,
p.theme.Styles.AIMessage.Render(response))
fmt.Println(p.theme.Styles.ChatDivider.Render(strings.Repeat("─", 50)))
}
}
46 changes: 46 additions & 0 deletions pkg/ui/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type ThemeStyles struct {
Spinner lipgloss.Style
AIMessage lipgloss.Style
Break lipgloss.Style
ChatHeader lipgloss.Style
ChatBorder lipgloss.Style
UserMessage lipgloss.Style
ChatPrompt lipgloss.Style
ChatSession lipgloss.Style
ChatDivider lipgloss.Style
}

type ThemeEmoji struct {
Expand All @@ -48,6 +54,11 @@ type ThemeEmoji struct {
Brain string
Bullet string
Section string
ChatStart string
UserInput string
AIResponse string
ChatEnd string
ChatDivider string
}

func NewTheme() *Theme {
Expand Down Expand Up @@ -111,6 +122,36 @@ func NewTheme() *Theme {
Break: lipgloss.NewStyle().
Foreground(lipgloss.Color("#86EFAC")).
Bold(true),

ChatHeader: lipgloss.NewStyle().
Foreground(lipgloss.Color("#9D8CFF")).
Bold(true).
Padding(1, 0),

ChatBorder: lipgloss.NewStyle().
Foreground(lipgloss.Color("#9D8CFF")).
Bold(true).
Padding(1, 0),

UserMessage: lipgloss.NewStyle().
Foreground(lipgloss.Color("#9D8CFF")).
Bold(true).
Padding(1, 0),

ChatPrompt: lipgloss.NewStyle().
Foreground(lipgloss.Color("#9D8CFF")).
Bold(true).
Padding(1, 0),

ChatSession: lipgloss.NewStyle().
Foreground(lipgloss.Color("#9D8CFF")).
Bold(true).
Padding(1, 0),

ChatDivider: lipgloss.NewStyle().
Foreground(lipgloss.Color("#9D8CFF")).
Bold(true).
Padding(1, 0),
},
Emoji: ThemeEmoji{
TaskComplete: "✅",
Expand All @@ -131,6 +172,11 @@ func NewTheme() *Theme {
Brain: "🧠",
Bullet: "•",
Section: "📋",
ChatStart: "👋",
UserInput: "👤",
AIResponse: "🤖",
ChatEnd: "👋",
ChatDivider: "🔀",
},
}
}

0 comments on commit 0328561

Please sign in to comment.