package main
import (
"encoding/json"
"fmt"
"html"
"strings"
)
// FragmentUserMessage returns an HTML fragment for a user message.
func FragmentUserMessage(text string, isPlan bool) string {
escaped := html.EscapeString(text)
modeClass := "message-user"
if isPlan {
modeClass = "message-user message-user-plan"
}
return fmt.Sprintf(`
`, modeClass, 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(``, 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(``, 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")
}