feat: Music Assistant, Claude primary LLM, model tag in chat, setup.sh rewrite

- Deploy Music Assistant on Pi (10.0.0.199:8095) with host networking for
  Chromecast mDNS discovery, Spotify + SMB library support
- Switch primary LLM from Ollama to Claude Sonnet 4 (Anthropic API),
  local models remain as fallback
- Add model info tag under each assistant message in dashboard chat,
  persisted in conversation JSON
- Rewrite homeai-agent/setup.sh: loads .env, injects API keys into plists,
  symlinks plists to ~/Library/LaunchAgents/, smoke tests services
- Update install_service() in common.sh to use symlinks instead of copies
- Open UFW ports on Pi for Music Assistant (8095, 8097, 8927)
- Add ANTHROPIC_API_KEY to openclaw + bridge launchd plists

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Aodhan Collins
2026-03-18 22:21:28 +00:00
parent 60eb89ea42
commit 117254d560
17 changed files with 1399 additions and 361 deletions

View File

@@ -107,16 +107,25 @@ export default function MessageBubble({ message, onReplay, character }) {
>
{isUser ? message.content : <RichContent text={message.content} />}
</div>
{!isUser && !message.isError && onReplay && (
<button
onClick={() => onReplay(message.content)}
className="mt-1 ml-1 text-gray-500 hover:text-indigo-400 transition-colors"
title="Replay audio"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.536 8.464a5 5 0 010 7.072M17.95 6.05a8 8 0 010 11.9M6.5 9H4a1 1 0 00-1 1v4a1 1 0 001 1h2.5l4 4V5l-4 4z" />
</svg>
</button>
{!isUser && (
<div className="flex items-center gap-2 mt-1 ml-1">
{message.model && (
<span className="text-[10px] text-gray-500 font-mono">
{message.model}
</span>
)}
{!message.isError && onReplay && (
<button
onClick={() => onReplay(message.content)}
className="text-gray-500 hover:text-indigo-400 transition-colors"
title="Replay audio"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.536 8.464a5 5 0 010 7.072M17.95 6.05a8 8 0 010 11.9M6.5 9H4a1 1 0 00-1 1v4a1 1 0 001 1h2.5l4 4V5l-4 4z" />
</svg>
</button>
)}
</div>
)}
</div>
</div>

View File

@@ -80,12 +80,13 @@ export function useChat(conversationId, conversationMeta, onConversationUpdate)
setIsLoading(true)
try {
const response = await sendMessage(text.trim(), conversationMeta?.characterId || null)
const { response, model } = await sendMessage(text.trim(), conversationMeta?.characterId || null)
const assistantMsg = {
id: Date.now() + 1,
role: 'assistant',
content: response,
timestamp: new Date().toISOString(),
...(model && { model }),
}
const allMessages = [...newMessages, assistantMsg]
setMessages(allMessages)

View File

@@ -31,7 +31,7 @@ export async function sendMessage(text, characterId = null) {
throw new Error(err.error || `HTTP ${res.status}`)
}
const data = await res.json()
return data.response
return { response: data.response, model: data.model || null }
}
export async function synthesize(text, voice, engine = 'kokoro', model = null) {