diff --git a/frontend-modern/src/components/AI/AIChat.tsx b/frontend-modern/src/components/AI/AIChat.tsx index 8632781..6d4fcfd 100644 --- a/frontend-modern/src/components/AI/AIChat.tsx +++ b/frontend-modern/src/components/AI/AIChat.tsx @@ -557,6 +557,14 @@ export const AIChat: Component = (props) => { }; // Add to both toolCalls and streamEvents for chronological display const events = msg.streamEvents || []; + + // Emit event for metadata refresh if AI set a resource URL + if (data.name === 'set_resource_url' && data.success) { + window.dispatchEvent(new CustomEvent('pulse:metadata-changed', { + detail: { source: 'ai', tool: data.name } + })); + } + return { ...msg, pendingTools: updatedPending, @@ -748,11 +756,14 @@ export const AIChat: Component = (props) => { }; const remainingApprovals = m.pendingApprovals?.filter((a) => a.toolId !== approval.toolId) || []; + const existingEvents = m.streamEvents || []; return { ...m, pendingApprovals: remainingApprovals, toolCalls: [...(m.toolCalls || []), newToolCall], + // Also add to streamEvents so it appears in the chronological view + streamEvents: [...existingEvents, { type: 'tool' as const, tool: newToolCall }], // Clear the stale "I need approval" content after the last approval is processed // The tool output will show the result instead content: remainingApprovals.length === 0 ? '' : m.content, diff --git a/frontend-modern/src/components/Dashboard/Dashboard.tsx b/frontend-modern/src/components/Dashboard/Dashboard.tsx index 0262ae0..48b7548 100644 --- a/frontend-modern/src/components/Dashboard/Dashboard.tsx +++ b/frontend-modern/src/components/Dashboard/Dashboard.tsx @@ -290,15 +290,31 @@ export function Dashboard(props: DashboardProps) { // Total columns for colspan calculations const totalColumns = createMemo(() => visibleColumns().length); - // Load all guest metadata on mount (single API call for all guests) - onMount(async () => { + // Helper function to refresh guest metadata from server + const refreshGuestMetadata = async () => { try { const metadata = await GuestMetadataAPI.getAllMetadata(); updateGuestMetadataState(() => metadata || {}); + logger.debug('Guest metadata refreshed'); } catch (err) { - // Silently fail - metadata is optional for display - logger.debug('Failed to load guest metadata', err); + logger.debug('Failed to refresh guest metadata', err); } + }; + + // Load all guest metadata on mount (single API call for all guests) + onMount(async () => { + await refreshGuestMetadata(); + }); + + // Listen for metadata changes from AI or other sources + createEffect(() => { + const handleMetadataChanged = () => { + logger.debug('Metadata changed event received, refreshing...'); + refreshGuestMetadata(); + }; + + window.addEventListener('pulse:metadata-changed', handleMetadataChanged); + return () => window.removeEventListener('pulse:metadata-changed', handleMetadataChanged); }); // Callback to update a guest's custom URL in metadata diff --git a/internal/ai/providers/anthropic.go b/internal/ai/providers/anthropic.go index cf4acf9..3ec4c59 100644 --- a/internal/ai/providers/anthropic.go +++ b/internal/ai/providers/anthropic.go @@ -166,6 +166,10 @@ func (c *AnthropicClient) Chat(ctx context.Context, req ChatRequest) (*ChatRespo // Use provided model or fall back to client default model := req.Model + // Strip provider prefix if present - callers may pass the full "provider:model" string + if len(model) > 10 && model[:10] == "anthropic:" { + model = model[10:] + } if model == "" { model = c.model } diff --git a/internal/ai/providers/anthropic_oauth.go b/internal/ai/providers/anthropic_oauth.go index 9a6e344..368e942 100644 --- a/internal/ai/providers/anthropic_oauth.go +++ b/internal/ai/providers/anthropic_oauth.go @@ -431,6 +431,10 @@ func (c *AnthropicOAuthClient) Chat(ctx context.Context, req ChatRequest) (*Chat } model := req.Model + // Strip provider prefix if present - callers may pass the full "provider:model" string + if len(model) > 10 && model[:10] == "anthropic:" { + model = model[10:] + } if model == "" { model = c.model } diff --git a/internal/ai/providers/openai.go b/internal/ai/providers/openai.go index cdf29e0..e8cfa50 100644 --- a/internal/ai/providers/openai.go +++ b/internal/ai/providers/openai.go @@ -28,11 +28,18 @@ type OpenAIClient struct { client *http.Client } + // NewOpenAIClient creates a new OpenAI API client func NewOpenAIClient(apiKey, model, baseURL string) *OpenAIClient { if baseURL == "" { baseURL = openaiAPIURL } + // Strip provider prefix if present - the model should be just the model name + if strings.HasPrefix(model, "openai:") { + model = strings.TrimPrefix(model, "openai:") + } else if strings.HasPrefix(model, "deepseek:") { + model = strings.TrimPrefix(model, "deepseek:") + } return &OpenAIClient{ apiKey: apiKey, model: model, @@ -205,10 +212,19 @@ func (c *OpenAIClient) Chat(ctx context.Context, req ChatRequest) (*ChatResponse // Use provided model or fall back to client default model := req.Model + // Strip provider prefix if present - callers may pass the full "provider:model" string + if strings.HasPrefix(model, "openai:") { + model = strings.TrimPrefix(model, "openai:") + } else if strings.HasPrefix(model, "deepseek:") { + model = strings.TrimPrefix(model, "deepseek:") + } if model == "" { model = c.model } + // Debug log to trace model issues + log.Debug().Str("model", model).Str("req_model", req.Model).Str("c_model", c.model).Str("base_url", c.baseURL).Msg("OpenAI/DeepSeek Chat request") + // Build request openaiReq := openaiRequest{ Model: model, @@ -245,6 +261,9 @@ func (c *OpenAIClient) Chat(ctx context.Context, req ChatRequest) (*ChatResponse } } + // Log actual model being sent (INFO level for visibility) + log.Info().Str("model_in_request", openaiReq.Model).Str("base_url", c.baseURL).Msg("Sending OpenAI/DeepSeek request") + body, err := json.Marshal(openaiReq) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err)