feat(chat): enhance chat functionality with web search and article integration
- Updated ChatMessage and ChatWindow components to support inline web search results and articles. - Integrated new web search and RSS plugins into the chat system for real-time information retrieval. - Enhanced useContentPanel to manage web search results alongside existing media types. - Added ArticleOverlay component for displaying selected articles from search results. - Improved UI elements and styles for better user interaction with web search features. Made-with: Cursor
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { useChatStore } from '@/stores/chat'
|
||||
import { searchWeb } from '@/composables/useWebSearch'
|
||||
|
||||
type Provider = 'claude' | 'openrouter' | 'mock'
|
||||
|
||||
@@ -24,6 +25,8 @@ const podcastContext = mockPodcasts.map((p) =>
|
||||
|
||||
const SYSTEM_PROMPT = `You are AIUI, a helpful AI assistant with access to the user's media library (films, songs, and podcasts).
|
||||
|
||||
**News/Factual queries:** When the user asks for "news", "latest", "recent", or current information, lead with a direct answer summarizing the news/facts. You MAY add "For deeper coverage:" with [[podcast_ext:...]] tags only. Do NOT use [[song_ext:...]] or [[film_ext:...]] for news queries—podcasts are the appropriate follow-up. Never substitute an answer with only recommendations.
|
||||
|
||||
**Films:** When recommending or discussing films from the user's library, use [[film:ID]] where ID is the film's id. For films NOT in the library, use [[film_ext:Title|Year|Director]], e.g. [[film_ext:Brokeback Mountain|2005|Ang Lee]].
|
||||
|
||||
**Songs:** When recommending or discussing songs, ALWAYS use tags for every song you mention:
|
||||
@@ -36,6 +39,8 @@ Never list songs in plain text only—each recommendation must have a tag so the
|
||||
- Other podcasts: [[podcast_ext:Title|Host|Year]] (year optional), e.g. [[podcast_ext:What Bitcoin Did|Peter McCormack|2018]].
|
||||
Prioritize Podcasting 2.0–friendly platforms: Fountain.fm, Podcast Index, Castopod, Odysee, Rumble, YouTube, Podverse.
|
||||
|
||||
**Websites / "Best places to check":** When listing resources, places to check online, or websites for the user to visit, use markdown links: [Name](https://full-url). For simple domains use **Name** (domain.com), e.g. **Bitcoin Mailing List** (gnusha.org).
|
||||
|
||||
**Music discovery:** For genre-based requests (e.g. "best math rock"), pick from the user's library when relevant, or use [[song_ext:...]] for others. Prioritize indie-friendly platforms: Wavlake, Bandcamp, Internet Archive, SoundCloud, Odysee, Jamendo.
|
||||
|
||||
Always include these tags so the UI can render rich cards. Write a brief reason why each is worth checking out.
|
||||
@@ -124,15 +129,18 @@ async function streamClaude(
|
||||
messages: ChatMessage[],
|
||||
onToken: (text: string) => void,
|
||||
onError: (err: string) => void,
|
||||
systemPrompt: string,
|
||||
webSearch: boolean,
|
||||
): Promise<void> {
|
||||
const res = await fetch(CLAUDE_PATH, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: activeModel.value,
|
||||
system: SYSTEM_PROMPT,
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
stream: true,
|
||||
webSearch,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -156,6 +164,7 @@ async function streamOpenRouter(
|
||||
messages: ChatMessage[],
|
||||
onToken: (text: string) => void,
|
||||
onError: (err: string) => void,
|
||||
systemPrompt: string,
|
||||
): Promise<void> {
|
||||
if (!openrouterApiKey) {
|
||||
onError('Missing VITE_OPENROUTER_API_KEY in .env.local')
|
||||
@@ -163,7 +172,7 @@ async function streamOpenRouter(
|
||||
}
|
||||
|
||||
const orMessages = [
|
||||
{ role: 'system' as const, content: SYSTEM_PROMPT },
|
||||
{ role: 'system' as const, content: systemPrompt },
|
||||
...messages.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content })),
|
||||
]
|
||||
|
||||
@@ -232,6 +241,17 @@ async function readSSE(
|
||||
}
|
||||
}
|
||||
|
||||
function formatWebSearchContext(results: { title: string; url: string; content?: string }[]): string {
|
||||
if (results.length === 0) return ''
|
||||
const lines = results.map((r, i) => {
|
||||
const snippet = r.content ? ` — ${r.content.slice(0, 200)}${r.content.length > 200 ? '…' : ''}` : ''
|
||||
return `${i + 1}. [${r.title}](${r.url})${snippet}`
|
||||
})
|
||||
return `\n\n**Web search results (PRIORITIZE these):**
|
||||
- Answer the user's question using these results. Cite sources.
|
||||
- You MAY add [[podcast_ext:...]] or [[film_ext:...]] tags for "to learn more" recommendations after your answer.\n\n${lines.join('\n')}`
|
||||
}
|
||||
|
||||
export function useAI() {
|
||||
const chatStore = useChatStore()
|
||||
|
||||
@@ -250,6 +270,23 @@ export function useAI() {
|
||||
|
||||
chatStore.isStreaming = true
|
||||
|
||||
let systemPrompt = SYSTEM_PROMPT
|
||||
if (chatStore.webSearchEnabled) {
|
||||
systemPrompt += `
|
||||
|
||||
**Web search:** You have access to WebSearch and WebFetch tools. Use them to look up current information, news, and facts when the user asks. You can search the web and fetch page content. Web search is enabled for this session—do not tell the user it is unavailable.`
|
||||
}
|
||||
if (chatStore.webSearchEnabled && userText.trim()) {
|
||||
const results = await searchWeb(userText)
|
||||
if (results.length > 0) {
|
||||
systemPrompt += formatWebSearchContext(results)
|
||||
chatStore.setMessageWebResults(cid, assistantMsg.id, results)
|
||||
console.log('[AIUI] Injected', results.length, 'web search results into context')
|
||||
} else {
|
||||
console.warn('[AIUI] Web search enabled but 0 results — check browser console for [AIUI web-search] logs')
|
||||
}
|
||||
}
|
||||
|
||||
const history: ChatMessage[] = chatStore.messages
|
||||
.filter((m) => m.id !== assistantMsg.id)
|
||||
.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content }))
|
||||
@@ -262,9 +299,9 @@ export function useAI() {
|
||||
|
||||
try {
|
||||
if (provider === 'claude') {
|
||||
await streamClaude(history, onToken, onError)
|
||||
await streamClaude(history, onToken, onError, systemPrompt, chatStore.webSearchEnabled)
|
||||
} else if (provider === 'openrouter') {
|
||||
await streamOpenRouter(history, onToken, onError)
|
||||
await streamOpenRouter(history, onToken, onError, systemPrompt)
|
||||
} else {
|
||||
await streamMock(history, onToken)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user