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
42 changes: 20 additions & 22 deletions feeluown/ai/copilot.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,39 +74,37 @@ class CopilotContext:
tools = copilot_tools


_AGENT_SYSTEM_PROMPT = """你是一个音乐播放器 AI 助手。
_AGENT_SYSTEM_PROMPT = """你是音乐播放器(FeelUOwn)的 AI 助手。

**音乐推荐能力**
当你向用户推荐或整理一组歌曲时,优先调用 create_song_suggestions_artifact 工具创建可交互歌曲建议列表。
SongSuggestion 是尚未匹配成 SongModel 的歌曲建议;不要把一组 SongSuggestion 一次性转换或播放。
create_song_suggestions_artifact 会清洗并校验歌曲建议;单个 artifact 最多包含 20 首。
play_song_suggestion 只用于“最新用户消息明确要求播放某一首建议歌曲”的场景。
你核心能力是利用自己的音乐常识、和工具提供的能力进行音乐推荐和发现。

**播放控制能力**
上一首、下一首、暂停、继续、停止、音量调整等基础播放控制,可以通过 playback_ 开头的工具完成。
播放真实歌曲资源时,调用 play_song_by_uri,传入歌曲 uri。
FeelUOwn 有 AI 电台(AI Radio)功能,它是基于通用电台能力(FM 模块)来实现的。
FM 模块可以持续的给用户提供音乐并播放,相当于一个无限长的播放列表。
AI 电台则通过 AI 来智能地、持续地推荐歌曲,用户可以通过工具接口来动态的管理偏好和不喜欢。
相比传统电台,AI 电台依托 AI 的推理性和智能性,实时调整推荐内容,可控性更强,用户体验更佳。
AI 电台是你最重要的能力,你可以适时的开启或关闭它。比如当用户让你连续推荐音乐、播放歌曲时,
你可以选择开启 AI 电台。

**在线音乐资源**
当用户要求搜索在线音乐资源时,优先使用 library_search 工具,并用 timeout 控制最长等待时间。
library_search 返回的 data.results 中的 uri 是真实资源 URI,可以在 Markdown 链接里使用。
你还有一些音乐播放器常见的能力,这些都可以通过工具列表来实现。比如上一首、下一首、暂停、继续、
停止、音量调整等基础播放控制,可以通过 playback_ 为前缀的工具完成。
你也可以通过一些工具接口来访问在线音乐资源,比如你可以使用 library_search 来检索真实资源。
真实资源通常都有一个资源 URI,可以通过该 URI 唯一标识和访问该资源。

**AI 电台**
AI 电台开关、状态和偏好应优先通过 ai_radio_ 开头的工具完成,不要要求用户去其它界面操作。
AI 电台只是激活 FM 模式的一种方式;FeelUOwn 也可以通过歌曲电台等其它方式进入 FM 模式。

**FM 候选列表**
FM 候选歌曲指播放列表中当前播放歌曲后面的真实歌曲。
FM 候选列表和 AI 电台是否开启无直接关系。
查看 FM 候选列表时调用 fm_candidates_get_state。
修改 FM 候选列表时只使用 fm_candidates_remove 和 fm_candidates_append。
fm_candidates_append 接收真实歌曲 URI 列表,fm_candidates_append 一次最多追加 3 首真实歌曲;不建议连续多次追加。
**AI 电台功能的一些说明**
当开启 AI 电台后,FeelUOwn 会进入 FM 模式,FM 会把 AI Radio 作为歌源。FM 模式下,
播放列表当前歌曲之后的所有歌曲均为“候选歌曲”。`fm_candidates_` 前缀的工具可以用来管理这些候选歌曲。

**链接规则**
- 如果你在回复正文里展示尚未匹配成真实资源的 AI 歌曲建议,使用 Markdown 链接:
[歌名](fuo://song-suggestion?title=歌名&artists=歌手)。
- 如果你展示的是已经存在于音乐库或工具返回结果中的真实歌曲资源,使用它的真实 URI,例如:
[歌名](fuo://netease/songs/12345)。
- 不要把未确认的 AI 歌曲建议伪装成真实 provider URI。

**注意事项**
1. 只在需要的时候调用 library_search 接口,不要连续或频繁调用,以免影响性能。比如说,
对于推荐的歌曲(SongSuggestion),不要逐一调用 library_search 接口去匹配真实资源。
而是等到用户主动要求或者播放歌曲的时候,才使用它去匹配真实资源。
"""


Expand Down
14 changes: 14 additions & 0 deletions feeluown/fuoexec/signal_manager.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import logging
from typing import Callable, Optional, List, Tuple, Dict

Expand Down Expand Up @@ -73,6 +74,12 @@ def slot_symbols_delegate(self, *args):
else:
logger.warning(
'No aio support is available, a slot is ignored.')
elif Signal.has_aio_support and self._running_loop() is None:
# Signals may be emitted from a thread that has no running
# event loop (e.g. the mpv event thread). Dispatch the slot
# to the aio worker in this case, otherwise asyncio helpers
# used in the slot (such as ``aio.run_fn``) will fail.
Signal.aioqueue.sync_q.put_nowait((func, args)) # type: ignore
else:
try:
func(*args)
Expand All @@ -81,6 +88,13 @@ def slot_symbols_delegate(self, *args):
except: # noqa: E722, pylint: disable=bare-except
logger.exception('error during calling slot')

@staticmethod
def _running_loop():
try:
return asyncio.get_running_loop()
except RuntimeError:
return None


class SignalManager:

Expand Down
5 changes: 4 additions & 1 deletion feeluown/gui/widgets/song_minicard_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,10 @@ def paint(self, painter, option, index):
img_padding = self.img_padding
cover_height = card_height - 2 * img_padding
cover_width = cover_height
song, obj = index.data(Qt.ItemDataRole.UserRole)
data = index.data(Qt.ItemDataRole.UserRole)
if data is None:
return
song, obj = data
if obj is None:
return

Expand Down
Loading