Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions app/Actions/Telegram/HandleTelegramUpdateAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class HandleTelegramUpdateAction
/link CODE: link your Content Machine account
/videos: your workspace's most recent videos
/posts: your workspace's most recent posts
/notes: your Scratch Pad note count and most recent captures
/notes: your active Scratch Pad note count and most recent ones
/note <text>: save a Scratch Pad note
/post <text>: create a draft, or send the next photo/voice/audio
/approve P-123: approve a generated draft
Expand Down Expand Up @@ -1182,29 +1182,26 @@ private function recentPosts(TelegramBotConfig $config): string

private function recentNotes(TelegramBotConfig $config): string
{
$notes = fn () => ScratchpadEntry::query()->where('workspace_id', $config->workspace_id)
// Only active (untriaged) notes count; triaged and dropped ones are filed or gone.
$notes = fn () => ScratchpadEntry::query()->where('workspace_id', $config->workspace_id)->where('status', 'new')
->when($this->voiceTranscriptionId !== null, fn ($query) => $query->whereDoesntHave(
'transcriptions', fn ($transcriptions) => $transcriptions->whereKey($this->voiceTranscriptionId)));
$entries = $notes()->orderByDesc('captured_at')->limit(10)->get();

if ($entries->isEmpty()) {
return 'No Scratch Pad captures yet.';
return 'No active Scratch Pad notes.';
}

// The list is capped at 10, so the total comes from the database, never from $entries.
$byStatus = $notes()
->selectRaw('status, count(*) as aggregate')->groupBy('status')->orderBy('status')
->pluck('aggregate', 'status');
$total = (int) $byStatus->sum();
$breakdown = $byStatus->map(fn ($count, $status) => "{$count} {$status}")->implode(', ');
$header = 'You have '.$total.' Scratch Pad '.Str::plural('note', $total)." ({$breakdown}).";
$total = $notes()->count();
$header = 'You have '.$total.' active Scratch Pad '.Str::plural('note', $total).'.';
$header .= $total > $entries->count() ? " Latest {$entries->count()}:" : '';

return $header."\n".$entries->map(function (ScratchpadEntry $entry) {
$preview = $entry->title ?? $entry->body;
$preview = $preview === null ? '(no preview)' : Str::limit($preview, 60);

return "{$entry->kind}: {$preview} ({$entry->status})";
return "{$entry->kind}: {$preview}";
})->implode("\n");
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Actions/Scratchpad/GistVoiceNoteActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public function test_a_voice_notes_lookup_does_not_count_the_voice_note_itself()
$this->transcribe(AiTranscriptionResult::success(text: 'how many notes are in my scratch pad', language: 'en', model: 'whisper-1'));

$reply = TelegramOutboundMessage::query()->orderByDesc('id')->first()->chunks[0];
$this->assertStringStartsWith('You have 1 Scratch Pad note (1 new).', $reply);
$this->assertStringStartsWith('You have 1 active Scratch Pad note.', $reply);
$this->assertNull($this->entry->fresh());
$this->assertSame(1, ScratchpadEntry::query()->count());
}
Expand Down
30 changes: 23 additions & 7 deletions tests/Unit/Actions/Telegram/HandleTelegramUpdateActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -267,23 +267,26 @@ public function test_notes_command_lists_recent_scratchpad_captures_for_the_work

$this->assertStringContainsString('text', $this->lastReply());
$this->assertStringContainsString('remember to renew the domain', $this->lastReply());
$this->assertStringContainsString('new', $this->lastReply());
}

public function test_notes_command_leads_with_the_true_total_not_the_capped_list_length()
{
$config = TelegramBotConfig::factory()->connected()->create();
$user = User::factory()->create();
TelegramBotLink::factory()->create(['telegram_bot_config_id' => $config->id, 'user_id' => $user->id, 'telegram_user_id' => 1]);
ScratchpadEntry::factory()->for($config->workspace)->count(12)->create(['status' => 'new']);
ScratchpadEntry::factory()->for($config->workspace)->triaged()->count(2)->create();
ScratchpadEntry::factory()->for($config->workspace)->count(12)->create(['status' => 'new', 'title' => null, 'body' => 'active note']);
ScratchpadEntry::factory()->for($config->workspace)->triaged()->count(2)->create(['captured_at' => now()->addMinute()]);
ScratchpadEntry::factory()->for($config->workspace)->dropped()->count(3)->create(['captured_at' => now()->addMinute()]);
ScratchpadEntry::factory()->count(5)->create();

$this->action()->handle($config, $this->update(1, '/notes'));

$lines = explode("\n", $this->lastReply());
$this->assertSame('You have 14 Scratch Pad notes (12 new, 2 triaged). Latest 10:', $lines[0]);
$this->assertSame('You have 12 active Scratch Pad notes. Latest 10:', $lines[0]);
$this->assertCount(11, $lines);
foreach (array_slice($lines, 1) as $line) {
$this->assertStringEndsWith(': active note', $line);
}
}

public function test_notes_command_with_one_note_says_singular_total()
Expand All @@ -295,7 +298,7 @@ public function test_notes_command_with_one_note_says_singular_total()

$this->action()->handle($config, $this->update(1, '/notes'));

$this->assertStringStartsWith("You have 1 Scratch Pad note (1 new).\n", $this->lastReply());
$this->assertStringStartsWith("You have 1 active Scratch Pad note.\n", $this->lastReply());
}

public function test_notes_command_with_none_yet_says_so()
Expand All @@ -306,7 +309,20 @@ public function test_notes_command_with_none_yet_says_so()

$this->action()->handle($config, $this->update(1, '/notes'));

$this->assertSame('No Scratch Pad captures yet.', $this->lastReply());
$this->assertSame('No active Scratch Pad notes.', $this->lastReply());
}

public function test_notes_command_with_only_triaged_or_dropped_notes_says_none_active()
{
$config = TelegramBotConfig::factory()->connected()->create();
$user = User::factory()->create();
TelegramBotLink::factory()->create(['telegram_bot_config_id' => $config->id, 'user_id' => $user->id, 'telegram_user_id' => 1]);
ScratchpadEntry::factory()->for($config->workspace)->triaged()->create();
ScratchpadEntry::factory()->for($config->workspace)->dropped()->create();

$this->action()->handle($config, $this->update(1, '/notes'));

$this->assertSame('No active Scratch Pad notes.', $this->lastReply());
}

public function test_clearnotes_command_deletes_recent_untriaged_notes()
Expand Down Expand Up @@ -768,7 +784,7 @@ public function complete($credential, $systemPrompt, $userContent): AiCompletion

$this->assertSame(0, ScratchpadEntry::query()->where('body', 'show me my scratchpad')->count());
$this->assertStringContainsString('remember to renew the domain', $this->lastReply());
$this->assertStringStartsWith('You have 1 Scratch Pad note (1 new).', $this->lastReply());
$this->assertStringStartsWith('You have 1 active Scratch Pad note.', $this->lastReply());
}

public function test_when_every_provider_fails_the_reply_says_so_before_capturing_anyway()
Expand Down
Loading