package main import ( "encoding/json" "fmt" "html" "strings" ) // FragmentUserMessage returns an HTML fragment for a user message. func FragmentUserMessage(text string) string { escaped := html.EscapeString(text) return fmt.Sprintf(`
%s
`, escaped) } // FragmentAssistantStart returns the opening tag for an assistant message with streaming. func FragmentAssistantStart(msgID string) string { return fmt.Sprintf(`
`, msgID) } // FragmentAssistantChunk appends text to an existing assistant message. func FragmentAssistantChunk(msgID, textChunk string) string { escaped := html.EscapeString(textChunk) return fmt.Sprintf(`
%s
`, msgID, escaped) } // FragmentAssistantComplete replaces the content of an assistant message with final rendered content. func FragmentAssistantComplete(msgID, htmlContent string) string { return fmt.Sprintf(`
%s
`, msgID, htmlContent) } // FragmentToolCall returns an HTML fragment for a tool use notification. func FragmentToolCall(toolName string, toolInput string) string { escapedName := html.EscapeString(toolName) summary := formatToolSummary(toolName, toolInput) return fmt.Sprintf(`
%s
%s
`, escapedName, summary) } // formatToolSummary produces a human-readable summary of a tool call. func formatToolSummary(toolName, rawInput string) string { var inputMap map[string]any if err := json.Unmarshal([]byte(rawInput), &inputMap); err != nil { // Not JSON, just escape and truncate s := html.EscapeString(rawInput) if len(s) > 200 { s = s[:200] + "..." } return s } switch toolName { case "Read": if fp, ok := inputMap["file_path"].(string); ok { return html.EscapeString(fp) } case "Edit": if fp, ok := inputMap["file_path"].(string); ok { return html.EscapeString(fmt.Sprintf("%s", fp)) } case "Write": if fp, ok := inputMap["file_path"].(string); ok { return html.EscapeString(fp) } case "Bash": if cmd, ok := inputMap["command"].(string); ok { s := cmd if len(s) > 150 { s = s[:150] + "..." } return "" + html.EscapeString(s) + "" } case "Glob": if pat, ok := inputMap["pattern"].(string); ok { return html.EscapeString(pat) } case "Grep": parts := []string{} if pat, ok := inputMap["pattern"].(string); ok { parts = append(parts, pat) } if p, ok := inputMap["path"].(string); ok { parts = append(parts, "in "+p) } if len(parts) > 0 { return html.EscapeString(strings.Join(parts, " ")) } case "WebSearch": if q, ok := inputMap["query"].(string); ok { return html.EscapeString(q) } case "WebFetch": if u, ok := inputMap["url"].(string); ok { return html.EscapeString(u) } } // Fallback: show key=value pairs var parts []string for k, v := range inputMap { s := fmt.Sprintf("%v", v) if len(s) > 80 { s = s[:80] + "..." } parts = append(parts, html.EscapeString(fmt.Sprintf("%s: %s", k, s))) } result := strings.Join(parts, ", ") if len(result) > 300 { result = result[:300] + "..." } return result } // FragmentSystemMessage returns an HTML fragment for a system message. func FragmentSystemMessage(text string) string { escaped := html.EscapeString(text) return fmt.Sprintf(`
%s
`, escaped) } // FragmentTypingIndicator shows or hides the typing indicator. func FragmentTypingIndicator(show bool) string { if show { return `
Claude razmišlja...
` } return `
` } // FragmentStatus updates the connection status indicator. func FragmentStatus(connected bool) string { if connected { return `Povezan` } return `Nepovezan` } // FragmentClearInput clears the message input field. func FragmentClearInput() string { return `` } // FragmentCombine joins multiple fragments into a single response. func FragmentCombine(fragments ...string) string { return strings.Join(fragments, "\n") }