Skip to content

Commit be8f34c

Browse files
committed
fix: loop and llm response
1 parent 787e072 commit be8f34c

File tree

7 files changed

+148
-31
lines changed

7 files changed

+148
-31
lines changed

rosetta/commands/basics.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,13 @@ async def version(self, interaction: discord.Interaction):
3838
)
3939
await interaction.response.send_message(embed=embed)
4040

41-
@commands.is_owner()
42-
@commands.command(name="version", description="Show version information")
43-
async def admin(self, ctx: commands.Context):
44-
embed = await self.generate_version_embed(ctx, is_admin=True)
45-
46-
await ctx.reply(embed=embed)
47-
4841
@commands.is_owner()
4942
@commands.command(name="guilds", description="Show all guilds the bot is in")
5043
async def guilds(self, ctx: commands.Context):
5144
"""Display all guilds the bot is currently in"""
5245
from ..utils.views import GuildsView
5346

54-
view = GuildsView(self.bot)
47+
view = GuildsView(self.bot, ctx.author)
5548
await ctx.reply(view=view)
5649

5750
async def generate_version_embed(

rosetta/commands/llm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ async def chat(
9292
{"role": "user", "content": prompt},
9393
],
9494
stream=True,
95-
stream_options={"include_usage": True},
95+
stream_options={"include_usage": True}
9696
)
9797

9898
async for chunk in stream:

rosetta/commands/music.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ async def _play(
248248
return embed
249249

250250
@app_commands.command(name="play", description="Play Youtube music")
251+
@app_commands.allowed_installs(guilds=True, users=False)
252+
@app_commands.allowed_contexts(guilds=True, dms=False, private_channels=False)
251253
@app_commands.describe(
252254
url="url",
253255
loop="loop",
@@ -280,6 +282,8 @@ async def play(
280282
await message.edit(embed=embed)
281283

282284
@app_commands.command(name="loop", description="Set loop")
285+
@app_commands.allowed_installs(guilds=True, users=False)
286+
@app_commands.allowed_contexts(guilds=True, dms=False, private_channels=False)
283287
@app_commands.describe(loop="loop mode")
284288
@app_commands.choices(
285289
loop=[
@@ -305,6 +309,8 @@ async def loop_command(self, interaction: discord.Interaction, loop: str = "Off"
305309
)
306310

307311
@app_commands.command(name="search", description="Search in Youtube")
312+
@app_commands.allowed_installs(guilds=True, users=False)
313+
@app_commands.allowed_contexts(guilds=True, dms=False, private_channels=False)
308314
@app_commands.describe(keyword="keyword")
309315
async def search(self, interaction: discord.Interaction, keyword: str):
310316
adapter = interaction.extras.get("logger")
@@ -340,35 +346,46 @@ async def do_skip(self, interaction: discord.Interaction, ephemeral: bool = Fals
340346
await interaction.followup.send(embed=embed, ephemeral=ephemeral)
341347
else:
342348
adapter.info("Skip requested but queue is empty")
349+
await player.destroy()
343350
await interaction.followup.send(
344-
embed=SuccessEmbed(self.bot.user, "No song left")
351+
embed=SuccessEmbed(self.bot.user, "No song left, leaving")
345352
)
346353

347354
@app_commands.command(name="shuffle", description="Shuffle")
355+
@app_commands.allowed_installs(guilds=True, users=False)
356+
@app_commands.allowed_contexts(guilds=True, dms=False, private_channels=False)
348357
@app_commands.describe(ephemeral="hide response")
349358
async def shuffle(self, interaction: discord.Interaction, ephemeral: bool = False):
350359
await self.do_shuffle(interaction, ephemeral)
351360

352361
@app_commands.command(name="skip", description="Skip to next song")
362+
@app_commands.allowed_installs(guilds=True, users=False)
363+
@app_commands.allowed_contexts(guilds=True, dms=False, private_channels=False)
353364
@app_commands.describe(ephemeral="hide response")
354365
async def skip(self, interaction: discord.Interaction, ephemeral: bool = False):
355366
await self.do_skip(interaction, ephemeral)
356367

357368
@app_commands.command(name="leave", description="Leave current channel")
369+
@app_commands.allowed_installs(guilds=True, users=False)
370+
@app_commands.allowed_contexts(guilds=True, dms=False, private_channels=False)
358371
async def leave(self, interaction: discord.Interaction):
359372
player = await self.ensure_player(interaction)
360373
await player.destroy()
361374
await interaction.response.send_message(embed=LeaveEmbed(self.bot.user))
362375

363376
@app_commands.command(name="nowplaying", description="Show the song playing now")
377+
@app_commands.allowed_installs(guilds=True, users=False)
378+
@app_commands.allowed_contexts(guilds=True, dms=False, private_channels=False)
364379
async def nowplaying(self, interaction: discord.Interaction):
365380
await interaction.response.defer()
366381
player = await self.ensure_player(interaction)
367382

368-
view = NowPlayingView(player)
383+
view = NowPlayingView(player, interaction.user)
369384
await interaction.followup.send(view=view)
370385

371386
@app_commands.command(name="switchnode", description="Switch to a different Lavalink node")
387+
@app_commands.allowed_installs(guilds=True, users=False)
388+
@app_commands.allowed_contexts(guilds=True, dms=False, private_channels=False)
372389
@app_commands.describe(node_name="The node to switch to")
373390
@app_commands.autocomplete(node_name=node_autocomplete)
374391
async def switchnode(

rosetta/utils/player.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class LoopMode(Enum):
1616
class Queue(Generic[T]):
1717
def __init__(self):
1818
self._queue: list[T] = []
19+
self._now_playing: T | None = None
1920
self.loop = LoopMode.NONE
2021

2122
def __len__(self) -> int:
@@ -31,6 +32,11 @@ def __getitem__(self, index: int) -> T:
3132
def is_empty(self) -> bool:
3233
return len(self._queue) == 0
3334

35+
@property
36+
def now_playing(self) -> T | None:
37+
"""Return the currently playing item."""
38+
return self._now_playing
39+
3440
def add(self, items: list[T]):
3541
"""Add items to the end of the queue."""
3642
self._queue.extend(items)
@@ -41,24 +47,43 @@ def add_front(self, items: list[T]):
4147

4248
def get(self) -> T | None:
4349
"""Remove and return the first item from the queue."""
50+
# Loop one: return the currently playing track
51+
if self.loop == LoopMode.ONE and self._now_playing is not None:
52+
return self._now_playing
53+
54+
# Empty queue handling
4455
if self.is_empty:
56+
# Loop queue with nothing left: return now_playing
57+
if self.loop == LoopMode.QUEUE and self._now_playing is not None:
58+
return self._now_playing
4559
return None
46-
if self.loop == LoopMode.ONE:
47-
return self._queue[0]
60+
61+
# Get next item
4862
item = self._queue.pop(0)
4963
if self.loop == LoopMode.QUEUE:
50-
self._queue.append(item)
64+
self._queue.append(self._now_playing)
65+
66+
self._now_playing = item
5167
return item
5268

53-
def skip_to(self, index: int) -> T:
69+
def skip_to(self, index: int) -> T | None:
70+
"""Skip to a specific index in the queue."""
5471
if self.is_empty:
5572
return None
56-
item = self._queue.pop(0)
73+
74+
# Add current now_playing to end if loop queue
75+
if self.loop == LoopMode.QUEUE and self._now_playing is not None:
76+
self._queue.append(self._now_playing)
77+
78+
# Skip through items up to index
5779
for _ in range(index):
5880
item = self._queue.pop(0)
5981
if self.loop == LoopMode.QUEUE:
6082
self._queue.append(item)
61-
assert item is not None
83+
84+
# Get the target item
85+
item = self._queue.pop(0)
86+
self._now_playing = item
6287
return item
6388

6489
def peek(self) -> T | None:
@@ -80,6 +105,7 @@ def remove(self, index: int) -> T | None:
80105
def clear(self):
81106
"""Clear all items from the queue."""
82107
self._queue.clear()
108+
self._now_playing = None
83109

84110
def shuffle(self):
85111
"""Shuffle the queue randomly."""

rosetta/utils/views/Guilds.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,23 @@
66

77

88
class GuildsView(discord.ui.LayoutView):
9-
def __init__(self, bot: commands.Bot, accent_color: int = 0x229AE0):
9+
def __init__(self, bot: commands.Bot, user: discord.User | discord.Member, accent_color: int = 0x229AE0):
1010
super().__init__(timeout=300)
1111
self.bot = bot
12+
self.user = user
1213
self.accent_color = accent_color
1314
self.page_size = 5
1415
self.container = self.construct_container()
1516
self.add_item(self.container)
1617

18+
async def interaction_check(self, interaction: discord.Interaction) -> bool:
19+
if interaction.user.id != self.user.id:
20+
await interaction.response.send_message(
21+
"You cannot interact with this view.", ephemeral=True
22+
)
23+
return False
24+
return True
25+
1726
def refresh_item(self, old_item: discord.ui.Item, new_item: discord.ui.Item):
1827
new_item._update_view(self)
1928
self._swap_item(old_item, new_item, "")

rosetta/utils/views/LLM.py

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@
55

66
UPDATE_INTERVAL_SECONDS = 1
77
DISCORD_CHAR_LIMIT = 4000
8-
SAFE_SPLIT_LIMIT = 3980
8+
SAFE_SPLIT_LIMIT = 3900
99

1010

1111
class LLMView(discord.ui.LayoutView):
1212
def __init__(self, model: str):
13-
super().__init__()
13+
super().__init__(timeout=300)
1414
self.last_update_time = time.time()
1515
self.start_time, self.first_token_time, self.end_time = time.time(), None, None
1616

1717
self.response_messages = []
1818
self.current_message_content = ""
1919
self.full_response = ""
2020
self.model = model
21+
self.current_page = 1
22+
self.message: discord.WebhookMessage | None = None
2123

2224
self.usage = None
2325
self.ttft, self.tps, self.completion_tokens = 0.0, 0.0, 0
@@ -57,23 +59,84 @@ def find_best_split_position(self, text: str, max_len: int) -> int:
5759
# 4. If all else fails, force a hard cut at the safe limit
5860
return max_len
5961

60-
async def update_view(self, message: discord.WebhookMessage):
61-
self.clear_items()
62-
container = discord.ui.Container()
62+
def refresh_item(self, old_item: discord.ui.Item, new_item: discord.ui.Item):
63+
new_item._update_view(self)
64+
self._swap_item(old_item, new_item, "")
65+
del old_item
66+
67+
def pagination_callback(self, current_page: int = 1):
68+
async def _callback(interaction: discord.Interaction):
69+
total_pages = len(self.response_messages)
70+
new_page = current_page
71+
72+
if interaction.data["custom_id"] == "next":
73+
new_page = min(current_page + 1, total_pages)
74+
elif interaction.data["custom_id"] == "previous":
75+
new_page = max(current_page - 1, 1)
76+
77+
self.current_page = new_page
78+
new_container = self.construct_container(new_page)
79+
self.refresh_item(self.container, new_container)
80+
self.container = new_container
81+
82+
await interaction.response.edit_message(view=self)
6383

64-
for response in self.response_messages:
65-
container.add_item(discord.ui.TextDisplay(response))
84+
return _callback
6685

67-
if not self.end_time:
86+
def construct_container(self, page: int = -1):
87+
container = discord.ui.Container()
88+
total_pages = len(self.response_messages)
89+
90+
# page -1 means show current streaming content only
91+
if page == -1:
6892
current_message = self.current_message_content + " █"
6993
container.add_item(discord.ui.TextDisplay(current_message))
70-
else:
94+
elif total_pages > 0 and 1 <= page <= total_pages:
95+
# Show only the specified page's content (1 response message per page)
96+
page_content = self.response_messages[page - 1]
97+
container.add_item(discord.ui.TextDisplay(page_content))
98+
99+
# Footer with stats (only when finished)
100+
if self.end_time:
101+
container.add_item(
102+
discord.ui.Separator(spacing=discord.enums.SeparatorSpacing.small)
103+
)
71104
container.add_item(
72105
discord.ui.TextDisplay(
73106
f"-# {self.model}{self.tps:.2f} tps • TTFT: {self.ttft:.2f}s • Tokens: {self.completion_tokens}"
74107
)
75108
)
76-
self.add_item(container)
109+
110+
# Pagination controls (only when there are multiple pages and streaming is done)
111+
if self.end_time and total_pages > 1:
112+
container.add_item(
113+
discord.ui.Separator(spacing=discord.enums.SeparatorSpacing.small)
114+
)
115+
footer = discord.ui.TextDisplay(f"-# Page {self.current_page}/{total_pages}")
116+
container.add_item(footer)
117+
118+
actionrow = discord.ui.ActionRow()
119+
previous_button = discord.ui.Button(
120+
label="Previous", custom_id="previous", disabled=self.current_page <= 1
121+
)
122+
previous_button.callback = self.pagination_callback(self.current_page)
123+
actionrow.add_item(previous_button)
124+
125+
next_button = discord.ui.Button(
126+
label="Next", custom_id="next", disabled=self.current_page >= total_pages
127+
)
128+
next_button.callback = self.pagination_callback(self.current_page)
129+
actionrow.add_item(next_button)
130+
131+
container.add_item(actionrow)
132+
133+
return container
134+
135+
async def update_view(self, message: discord.WebhookMessage, page: int = -1):
136+
self.message = message
137+
self.clear_items()
138+
self.container = self.construct_container(page)
139+
self.add_item(self.container)
77140

78141
await message.edit(view=self)
79142

@@ -107,6 +170,7 @@ async def update_chunk(
107170
async def end_chunk(self, message: discord.WebhookMessage):
108171
self.end_time = time.time()
109172
self.response_messages.append(self.current_message_content)
173+
self.current_page = len(self.response_messages) # Go to the last page
110174

111175
if self.usage:
112176
self.completion_tokens = self.usage.completion_tokens
@@ -117,4 +181,4 @@ async def end_chunk(self, message: discord.WebhookMessage):
117181
if generation_time > 0 and self.completion_tokens > 1:
118182
self.tps = (self.completion_tokens - 1) / generation_time
119183

120-
await self.update_view(message)
184+
await self.update_view(message, self.current_page)

rosetta/utils/views/NowPlaying.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@
1111

1212

1313
class NowPlayingView(discord.ui.LayoutView):
14-
def __init__(self, player: CustomPlayer, accent_color: int = 0x229AE0):
14+
def __init__(self, player: CustomPlayer, user: discord.User | discord.Member, accent_color: int = 0x229AE0):
1515
super().__init__()
16+
self.user = user
1617
self.accent_color = accent_color
1718
self.page_size = 10
1819
self.container = self.construct_container(player)
1920
self.add_item(self.container)
2021

22+
async def interaction_check(self, interaction: discord.Interaction) -> bool:
23+
if interaction.user.id != self.user.id:
24+
await interaction.response.send_message(
25+
"You cannot interact with this view.", ephemeral=True
26+
)
27+
return False
28+
return True
29+
2130
def refresh_callback(self):
2231
async def _callback(interaction: discord.Interaction):
2332
player = await self.ensure_player(interaction)
@@ -53,7 +62,6 @@ async def _callback(interaction: discord.Interaction):
5362
player = await self.ensure_player(interaction)
5463

5564
result = interaction.data["values"][0]
56-
logger.info(result)
5765
track = player.queue.skip_to(int(result))
5866
await player.play(track)
5967

0 commit comments

Comments
 (0)