feat: Go backend, enhanced search, new widgets, Docker deploy

Major changes:
- Add Go backend (backend/) with microservices architecture
- Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler,
  proxy-manager, media-search, fastClassifier, language detection
- New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard,
  UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions
- Improved discover-svc with discover-db integration
- Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md)
- Library-svc: project_id schema migration
- Remove deprecated finance-svc and travel-svc
- Localization improvements across services

Made-with: Cursor
This commit is contained in:
home
2026-02-27 04:15:32 +03:00
parent 328d968f3f
commit 06fe57c765
285 changed files with 53132 additions and 1871 deletions

View File

@@ -0,0 +1,759 @@
package labs
import (
"context"
"encoding/json"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/gooseek/backend/internal/llm"
"github.com/google/uuid"
)
type Generator struct {
llm llm.Client
}
func NewGenerator(llmClient llm.Client) *Generator {
return &Generator{llm: llmClient}
}
type GenerateOptions struct {
Query string
Data interface{}
PreferredTypes []VisualizationType
Theme string
Locale string
MaxVisualizations int
}
func (g *Generator) GenerateReport(ctx context.Context, opts GenerateOptions) (*Report, error) {
analysisPrompt := fmt.Sprintf(`Analyze this data and query to determine the best visualizations.
Query: %s
Data: %v
Determine:
1. What visualizations would best represent this data?
2. How should the data be structured for each visualization?
3. What insights can be highlighted?
Respond in JSON format:
{
"title": "Report title",
"sections": [
{
"title": "Section title",
"visualizations": [
{
"type": "chart_type",
"title": "Viz title",
"dataMapping": { "how to map the data" },
"insight": "Key insight"
}
]
}
]
}
Available visualization types: bar_chart, line_chart, pie_chart, donut_chart, table, stat_cards, kpi, comparison, timeline, progress, heatmap, code_block, markdown, collapsible, tabs, accordion`, opts.Query, opts.Data)
result, err := g.llm.GenerateText(ctx, llm.StreamRequest{
Messages: []llm.Message{{Role: "user", Content: analysisPrompt}},
})
if err != nil {
return nil, err
}
var analysis struct {
Title string `json:"title"`
Sections []struct {
Title string `json:"title"`
Visualizations []struct {
Type string `json:"type"`
Title string `json:"title"`
DataMapping map[string]interface{} `json:"dataMapping"`
Insight string `json:"insight"`
} `json:"visualizations"`
} `json:"sections"`
}
jsonStr := extractJSON(result)
if err := json.Unmarshal([]byte(jsonStr), &analysis); err != nil {
return g.createDefaultReport(opts)
}
report := &Report{
ID: uuid.New().String(),
Title: analysis.Title,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Theme: opts.Theme,
Sections: make([]ReportSection, 0),
}
for _, sec := range analysis.Sections {
section := ReportSection{
ID: uuid.New().String(),
Title: sec.Title,
Visualizations: make([]Visualization, 0),
}
for _, viz := range sec.Visualizations {
visualization := g.createVisualization(VisualizationType(viz.Type), viz.Title, opts.Data, viz.DataMapping)
if visualization != nil {
section.Visualizations = append(section.Visualizations, *visualization)
}
}
if len(section.Visualizations) > 0 {
report.Sections = append(report.Sections, section)
}
}
return report, nil
}
func (g *Generator) createDefaultReport(opts GenerateOptions) (*Report, error) {
report := &Report{
ID: uuid.New().String(),
Title: "Анализ данных",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Sections: []ReportSection{
{
ID: uuid.New().String(),
Title: "Обзор",
Visualizations: []Visualization{
g.CreateMarkdown("", formatDataAsMarkdown(opts.Data)),
},
},
},
}
return report, nil
}
func (g *Generator) createVisualization(vizType VisualizationType, title string, data interface{}, mapping map[string]interface{}) *Visualization {
switch vizType {
case VizBarChart, VizLineChart, VizAreaChart:
return g.createChartVisualization(vizType, title, data, mapping)
case VizPieChart, VizDonutChart:
return g.createPieVisualization(vizType, title, data, mapping)
case VizTable:
return g.createTableVisualization(title, data, mapping)
case VizStatCards:
return g.createStatCardsVisualization(title, data, mapping)
case VizKPI:
return g.createKPIVisualization(title, data, mapping)
case VizTimeline:
return g.createTimelineVisualization(title, data, mapping)
case VizComparison:
return g.createComparisonVisualization(title, data, mapping)
case VizProgress:
return g.createProgressVisualization(title, data, mapping)
case VizMarkdown:
content := extractStringFromData(data, mapping, "content")
viz := g.CreateMarkdown(title, content)
return &viz
default:
viz := g.CreateMarkdown(title, formatDataAsMarkdown(data))
return &viz
}
}
func (g *Generator) createChartVisualization(vizType VisualizationType, title string, data interface{}, mapping map[string]interface{}) *Visualization {
chartData := &ChartData{
Labels: make([]string, 0),
Datasets: make([]ChartDataset, 0),
}
if dataMap, ok := data.(map[string]interface{}); ok {
labels := make([]string, 0)
values := make([]float64, 0)
for k, v := range dataMap {
labels = append(labels, k)
values = append(values, toFloat64(v))
}
chartData.Labels = labels
chartData.Datasets = append(chartData.Datasets, ChartDataset{
Label: title,
Data: values,
})
}
if dataSlice, ok := data.([]interface{}); ok {
for _, item := range dataSlice {
if itemMap, ok := item.(map[string]interface{}); ok {
if label, ok := itemMap["label"].(string); ok {
chartData.Labels = append(chartData.Labels, label)
}
if value, ok := itemMap["value"]; ok {
if len(chartData.Datasets) == 0 {
chartData.Datasets = append(chartData.Datasets, ChartDataset{Label: title, Data: []float64{}})
}
chartData.Datasets[0].Data = append(chartData.Datasets[0].Data, toFloat64(value))
}
}
}
}
return &Visualization{
ID: uuid.New().String(),
Type: vizType,
Title: title,
Data: chartData,
Config: VisualizationConfig{
ShowLegend: true,
ShowTooltip: true,
ShowGrid: true,
Animated: true,
},
Responsive: true,
}
}
func (g *Generator) createPieVisualization(vizType VisualizationType, title string, data interface{}, mapping map[string]interface{}) *Visualization {
chartData := &ChartData{
Labels: make([]string, 0),
Datasets: make([]ChartDataset, 0),
}
dataset := ChartDataset{Label: title, Data: []float64{}}
if dataMap, ok := data.(map[string]interface{}); ok {
for k, v := range dataMap {
chartData.Labels = append(chartData.Labels, k)
dataset.Data = append(dataset.Data, toFloat64(v))
}
}
chartData.Datasets = append(chartData.Datasets, dataset)
return &Visualization{
ID: uuid.New().String(),
Type: vizType,
Title: title,
Data: chartData,
Config: VisualizationConfig{
ShowLegend: true,
ShowTooltip: true,
ShowValues: true,
Animated: true,
},
Style: VisualizationStyle{
Height: "300px",
},
Responsive: true,
}
}
func (g *Generator) createTableVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
tableData := &TableData{
Columns: make([]TableColumn, 0),
Rows: make([]TableRow, 0),
}
if dataSlice, ok := data.([]interface{}); ok && len(dataSlice) > 0 {
if firstRow, ok := dataSlice[0].(map[string]interface{}); ok {
for key := range firstRow {
tableData.Columns = append(tableData.Columns, TableColumn{
Key: key,
Label: formatColumnLabel(key),
Sortable: true,
})
}
}
for _, item := range dataSlice {
if rowMap, ok := item.(map[string]interface{}); ok {
tableData.Rows = append(tableData.Rows, TableRow(rowMap))
}
}
}
if dataMap, ok := data.(map[string]interface{}); ok {
tableData.Columns = []TableColumn{
{Key: "key", Label: "Параметр", Sortable: true},
{Key: "value", Label: "Значение", Sortable: true},
}
for k, v := range dataMap {
tableData.Rows = append(tableData.Rows, TableRow{
"key": k,
"value": v,
})
}
}
tableData.Summary = &TableSummary{
TotalRows: len(tableData.Rows),
}
return &Visualization{
ID: uuid.New().String(),
Type: VizTable,
Title: title,
Data: tableData,
Config: VisualizationConfig{
Sortable: true,
Searchable: true,
Paginated: len(tableData.Rows) > 10,
PageSize: 10,
},
Responsive: true,
}
}
func (g *Generator) createStatCardsVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
cardsData := &StatCardsData{
Cards: make([]StatCard, 0),
}
colors := []string{"#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899"}
colorIdx := 0
if dataMap, ok := data.(map[string]interface{}); ok {
for k, v := range dataMap {
card := StatCard{
ID: uuid.New().String(),
Title: formatColumnLabel(k),
Value: v,
Color: colors[colorIdx%len(colors)],
}
cardsData.Cards = append(cardsData.Cards, card)
colorIdx++
}
}
return &Visualization{
ID: uuid.New().String(),
Type: VizStatCards,
Title: title,
Data: cardsData,
Config: VisualizationConfig{
Animated: true,
},
Responsive: true,
}
}
func (g *Generator) createKPIVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
kpiData := &KPIData{
Value: data,
}
if dataMap, ok := data.(map[string]interface{}); ok {
if v, ok := dataMap["value"]; ok {
kpiData.Value = v
}
if v, ok := dataMap["change"].(float64); ok {
kpiData.Change = v
if v >= 0 {
kpiData.ChangeType = "increase"
} else {
kpiData.ChangeType = "decrease"
}
}
if v, ok := dataMap["target"]; ok {
kpiData.Target = v
}
if v, ok := dataMap["unit"].(string); ok {
kpiData.Unit = v
}
}
return &Visualization{
ID: uuid.New().String(),
Type: VizKPI,
Title: title,
Data: kpiData,
Config: VisualizationConfig{
Animated: true,
ShowValues: true,
},
Style: VisualizationStyle{
MinHeight: "150px",
},
Responsive: true,
}
}
func (g *Generator) createTimelineVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
timelineData := &TimelineData{
Events: make([]TimelineEvent, 0),
}
if dataSlice, ok := data.([]interface{}); ok {
for _, item := range dataSlice {
if itemMap, ok := item.(map[string]interface{}); ok {
event := TimelineEvent{
ID: uuid.New().String(),
}
if v, ok := itemMap["date"].(string); ok {
event.Date, _ = time.Parse(time.RFC3339, v)
}
if v, ok := itemMap["title"].(string); ok {
event.Title = v
}
if v, ok := itemMap["description"].(string); ok {
event.Description = v
}
timelineData.Events = append(timelineData.Events, event)
}
}
}
sort.Slice(timelineData.Events, func(i, j int) bool {
return timelineData.Events[i].Date.Before(timelineData.Events[j].Date)
})
return &Visualization{
ID: uuid.New().String(),
Type: VizTimeline,
Title: title,
Data: timelineData,
Config: VisualizationConfig{
Animated: true,
},
Responsive: true,
}
}
func (g *Generator) createComparisonVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
compData := &ComparisonData{
Items: make([]ComparisonItem, 0),
Categories: make([]string, 0),
}
if dataSlice, ok := data.([]interface{}); ok && len(dataSlice) > 0 {
if firstItem, ok := dataSlice[0].(map[string]interface{}); ok {
for k := range firstItem {
if k != "name" && k != "id" && k != "image" {
compData.Categories = append(compData.Categories, k)
}
}
}
for _, item := range dataSlice {
if itemMap, ok := item.(map[string]interface{}); ok {
compItem := ComparisonItem{
ID: uuid.New().String(),
Values: make(map[string]interface{}),
}
if v, ok := itemMap["name"].(string); ok {
compItem.Name = v
}
if v, ok := itemMap["image"].(string); ok {
compItem.Image = v
}
for _, cat := range compData.Categories {
if v, ok := itemMap[cat]; ok {
compItem.Values[cat] = v
}
}
compData.Items = append(compData.Items, compItem)
}
}
}
return &Visualization{
ID: uuid.New().String(),
Type: VizComparison,
Title: title,
Data: compData,
Config: VisualizationConfig{
ShowLabels: true,
},
Responsive: true,
}
}
func (g *Generator) createProgressVisualization(title string, data interface{}, mapping map[string]interface{}) *Visualization {
progressData := &ProgressData{
Current: 0,
Total: 100,
ShowValue: true,
Animated: true,
}
if dataMap, ok := data.(map[string]interface{}); ok {
if v, ok := dataMap["current"]; ok {
progressData.Current = toFloat64(v)
}
if v, ok := dataMap["total"]; ok {
progressData.Total = toFloat64(v)
}
if v, ok := dataMap["label"].(string); ok {
progressData.Label = v
}
}
if v, ok := data.(float64); ok {
progressData.Current = v
}
return &Visualization{
ID: uuid.New().String(),
Type: VizProgress,
Title: title,
Data: progressData,
Config: VisualizationConfig{
Animated: true,
ShowValues: true,
},
Responsive: true,
}
}
func (g *Generator) CreateBarChart(title string, labels []string, values []float64) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizBarChart,
Title: title,
Data: &ChartData{
Labels: labels,
Datasets: []ChartDataset{
{Label: title, Data: values},
},
},
Config: VisualizationConfig{
ShowLegend: true,
ShowTooltip: true,
Animated: true,
},
Responsive: true,
}
}
func (g *Generator) CreateLineChart(title string, labels []string, datasets []ChartDataset) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizLineChart,
Title: title,
Data: &ChartData{
Labels: labels,
Datasets: datasets,
},
Config: VisualizationConfig{
ShowLegend: true,
ShowTooltip: true,
ShowGrid: true,
Animated: true,
},
Responsive: true,
}
}
func (g *Generator) CreatePieChart(title string, labels []string, values []float64) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizPieChart,
Title: title,
Data: &ChartData{
Labels: labels,
Datasets: []ChartDataset{
{Label: title, Data: values},
},
},
Config: VisualizationConfig{
ShowLegend: true,
ShowTooltip: true,
ShowValues: true,
},
Responsive: true,
}
}
func (g *Generator) CreateTable(title string, columns []TableColumn, rows []TableRow) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizTable,
Title: title,
Data: &TableData{
Columns: columns,
Rows: rows,
Summary: &TableSummary{TotalRows: len(rows)},
},
Config: VisualizationConfig{
Sortable: true,
Searchable: true,
Paginated: len(rows) > 10,
PageSize: 10,
},
Responsive: true,
}
}
func (g *Generator) CreateStatCards(title string, cards []StatCard) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizStatCards,
Title: title,
Data: &StatCardsData{Cards: cards},
Config: VisualizationConfig{
Animated: true,
},
Responsive: true,
}
}
func (g *Generator) CreateKPI(title string, value interface{}, change float64, unit string) Visualization {
changeType := "neutral"
if change > 0 {
changeType = "increase"
} else if change < 0 {
changeType = "decrease"
}
return Visualization{
ID: uuid.New().String(),
Type: VizKPI,
Title: title,
Data: &KPIData{
Value: value,
Change: change,
ChangeType: changeType,
Unit: unit,
},
Config: VisualizationConfig{
Animated: true,
},
Responsive: true,
}
}
func (g *Generator) CreateMarkdown(title string, content string) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizMarkdown,
Title: title,
Data: &MarkdownData{Content: content},
Responsive: true,
}
}
func (g *Generator) CreateCodeBlock(title, code, language string) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizCodeBlock,
Title: title,
Data: &CodeBlockData{
Code: code,
Language: language,
ShowLineNum: true,
Copyable: true,
},
Responsive: true,
}
}
func (g *Generator) CreateTabs(title string, tabs []TabItem) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizTabs,
Title: title,
Data: &TabsData{Tabs: tabs},
Responsive: true,
}
}
func (g *Generator) CreateAccordion(title string, items []AccordionItem) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizAccordion,
Title: title,
Data: &AccordionData{Items: items},
Config: VisualizationConfig{
Animated: true,
},
Responsive: true,
}
}
func (g *Generator) CreateHeatmap(title string, xLabels, yLabels []string, values [][]float64) Visualization {
return Visualization{
ID: uuid.New().String(),
Type: VizHeatmap,
Title: title,
Data: &HeatmapData{
XLabels: xLabels,
YLabels: yLabels,
Values: values,
},
Config: VisualizationConfig{
ShowTooltip: true,
ShowLabels: true,
},
Responsive: true,
}
}
func extractJSON(text string) string {
re := regexp.MustCompile(`(?s)\{.*\}`)
match := re.FindString(text)
if match != "" {
return match
}
return "{}"
}
func toFloat64(v interface{}) float64 {
switch val := v.(type) {
case float64:
return val
case float32:
return float64(val)
case int:
return float64(val)
case int64:
return float64(val)
case string:
f, _ := strconv.ParseFloat(val, 64)
return f
default:
return 0
}
}
func formatColumnLabel(key string) string {
key = strings.ReplaceAll(key, "_", " ")
key = strings.ReplaceAll(key, "-", " ")
words := strings.Fields(key)
for i, word := range words {
if len(word) > 0 {
words[i] = strings.ToUpper(string(word[0])) + strings.ToLower(word[1:])
}
}
return strings.Join(words, " ")
}
func extractStringFromData(data interface{}, mapping map[string]interface{}, key string) string {
if dataMap, ok := data.(map[string]interface{}); ok {
if v, ok := dataMap[key].(string); ok {
return v
}
}
return fmt.Sprintf("%v", data)
}
func formatDataAsMarkdown(data interface{}) string {
jsonBytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Sprintf("%v", data)
}
return "```json\n" + string(jsonBytes) + "\n```"
}

View File

@@ -0,0 +1,335 @@
package labs
import "time"
type VisualizationType string
const (
VizBarChart VisualizationType = "bar_chart"
VizLineChart VisualizationType = "line_chart"
VizPieChart VisualizationType = "pie_chart"
VizDonutChart VisualizationType = "donut_chart"
VizAreaChart VisualizationType = "area_chart"
VizScatterPlot VisualizationType = "scatter_plot"
VizHeatmap VisualizationType = "heatmap"
VizTreemap VisualizationType = "treemap"
VizGauge VisualizationType = "gauge"
VizRadar VisualizationType = "radar"
VizSankey VisualizationType = "sankey"
VizTable VisualizationType = "table"
VizTimeline VisualizationType = "timeline"
VizKPI VisualizationType = "kpi"
VizProgress VisualizationType = "progress"
VizComparison VisualizationType = "comparison"
VizStatCards VisualizationType = "stat_cards"
VizMap VisualizationType = "map"
VizFlowChart VisualizationType = "flow_chart"
VizOrgChart VisualizationType = "org_chart"
VizCodeBlock VisualizationType = "code_block"
VizMarkdown VisualizationType = "markdown"
VizCollapsible VisualizationType = "collapsible"
VizTabs VisualizationType = "tabs"
VizAccordion VisualizationType = "accordion"
VizStepper VisualizationType = "stepper"
VizForm VisualizationType = "form"
)
type Visualization struct {
ID string `json:"id"`
Type VisualizationType `json:"type"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Data interface{} `json:"data"`
Config VisualizationConfig `json:"config,omitempty"`
Style VisualizationStyle `json:"style,omitempty"`
Actions []VisualizationAction `json:"actions,omitempty"`
Responsive bool `json:"responsive"`
}
type VisualizationConfig struct {
ShowLegend bool `json:"showLegend,omitempty"`
ShowGrid bool `json:"showGrid,omitempty"`
ShowTooltip bool `json:"showTooltip,omitempty"`
ShowLabels bool `json:"showLabels,omitempty"`
ShowValues bool `json:"showValues,omitempty"`
Animated bool `json:"animated,omitempty"`
Stacked bool `json:"stacked,omitempty"`
Horizontal bool `json:"horizontal,omitempty"`
Sortable bool `json:"sortable,omitempty"`
Filterable bool `json:"filterable,omitempty"`
Searchable bool `json:"searchable,omitempty"`
Paginated bool `json:"paginated,omitempty"`
PageSize int `json:"pageSize,omitempty"`
Expandable bool `json:"expandable,omitempty"`
DefaultExpanded bool `json:"defaultExpanded,omitempty"`
XAxisLabel string `json:"xAxisLabel,omitempty"`
YAxisLabel string `json:"yAxisLabel,omitempty"`
Colors []string `json:"colors,omitempty"`
DateFormat string `json:"dateFormat,omitempty"`
NumberFormat string `json:"numberFormat,omitempty"`
CurrencySymbol string `json:"currencySymbol,omitempty"`
}
type VisualizationStyle struct {
Width string `json:"width,omitempty"`
Height string `json:"height,omitempty"`
MinHeight string `json:"minHeight,omitempty"`
MaxHeight string `json:"maxHeight,omitempty"`
Padding string `json:"padding,omitempty"`
Margin string `json:"margin,omitempty"`
BorderRadius string `json:"borderRadius,omitempty"`
Background string `json:"background,omitempty"`
Shadow string `json:"shadow,omitempty"`
FontFamily string `json:"fontFamily,omitempty"`
FontSize string `json:"fontSize,omitempty"`
TextColor string `json:"textColor,omitempty"`
AccentColor string `json:"accentColor,omitempty"`
GridColor string `json:"gridColor,omitempty"`
}
type VisualizationAction struct {
ID string `json:"id"`
Label string `json:"label"`
Icon string `json:"icon,omitempty"`
Type string `json:"type"`
Handler string `json:"handler,omitempty"`
URL string `json:"url,omitempty"`
}
type ChartData struct {
Labels []string `json:"labels"`
Datasets []ChartDataset `json:"datasets"`
}
type ChartDataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
BackgroundColor string `json:"backgroundColor,omitempty"`
BorderColor string `json:"borderColor,omitempty"`
Fill bool `json:"fill,omitempty"`
}
type TableData struct {
Columns []TableColumn `json:"columns"`
Rows []TableRow `json:"rows"`
Summary *TableSummary `json:"summary,omitempty"`
}
type TableColumn struct {
Key string `json:"key"`
Label string `json:"label"`
Type string `json:"type,omitempty"`
Width string `json:"width,omitempty"`
Sortable bool `json:"sortable,omitempty"`
Align string `json:"align,omitempty"`
Format string `json:"format,omitempty"`
Highlight bool `json:"highlight,omitempty"`
}
type TableRow map[string]interface{}
type TableSummary struct {
TotalRows int `json:"totalRows"`
Aggregations map[string]interface{} `json:"aggregations,omitempty"`
}
type TimelineData struct {
Events []TimelineEvent `json:"events"`
}
type TimelineEvent struct {
ID string `json:"id"`
Date time.Time `json:"date"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
Icon string `json:"icon,omitempty"`
Color string `json:"color,omitempty"`
Link string `json:"link,omitempty"`
}
type KPIData struct {
Value interface{} `json:"value"`
PrevValue interface{} `json:"prevValue,omitempty"`
Change float64 `json:"change,omitempty"`
ChangeType string `json:"changeType,omitempty"`
Unit string `json:"unit,omitempty"`
Prefix string `json:"prefix,omitempty"`
Suffix string `json:"suffix,omitempty"`
Target interface{} `json:"target,omitempty"`
Trend []float64 `json:"trend,omitempty"`
Icon string `json:"icon,omitempty"`
Color string `json:"color,omitempty"`
Description string `json:"description,omitempty"`
}
type StatCardsData struct {
Cards []StatCard `json:"cards"`
}
type StatCard struct {
ID string `json:"id"`
Title string `json:"title"`
Value interface{} `json:"value"`
Change float64 `json:"change,omitempty"`
ChangeLabel string `json:"changeLabel,omitempty"`
Icon string `json:"icon,omitempty"`
Color string `json:"color,omitempty"`
Sparkline []float64 `json:"sparkline,omitempty"`
}
type ComparisonData struct {
Items []ComparisonItem `json:"items"`
Categories []string `json:"categories"`
}
type ComparisonItem struct {
ID string `json:"id"`
Name string `json:"name"`
Image string `json:"image,omitempty"`
Values map[string]interface{} `json:"values"`
}
type ProgressData struct {
Current float64 `json:"current"`
Total float64 `json:"total"`
Label string `json:"label,omitempty"`
Color string `json:"color,omitempty"`
ShowValue bool `json:"showValue,omitempty"`
Animated bool `json:"animated,omitempty"`
}
type HeatmapData struct {
XLabels []string `json:"xLabels"`
YLabels []string `json:"yLabels"`
Values [][]float64 `json:"values"`
Min float64 `json:"min,omitempty"`
Max float64 `json:"max,omitempty"`
}
type MapData struct {
Center []float64 `json:"center"`
Zoom int `json:"zoom"`
Markers []MapMarker `json:"markers,omitempty"`
Regions []MapRegion `json:"regions,omitempty"`
}
type MapMarker struct {
ID string `json:"id"`
Position []float64 `json:"position"`
Label string `json:"label,omitempty"`
Icon string `json:"icon,omitempty"`
Color string `json:"color,omitempty"`
Popup string `json:"popup,omitempty"`
}
type MapRegion struct {
ID string `json:"id"`
Name string `json:"name"`
Value float64 `json:"value"`
Color string `json:"color,omitempty"`
}
type CollapsibleData struct {
Title string `json:"title"`
Content interface{} `json:"content"`
DefaultOpen bool `json:"defaultOpen,omitempty"`
Icon string `json:"icon,omitempty"`
Children []Visualization `json:"children,omitempty"`
}
type TabsData struct {
Tabs []TabItem `json:"tabs"`
}
type TabItem struct {
ID string `json:"id"`
Label string `json:"label"`
Icon string `json:"icon,omitempty"`
Content interface{} `json:"content"`
Children []Visualization `json:"children,omitempty"`
}
type AccordionData struct {
Items []AccordionItem `json:"items"`
}
type AccordionItem struct {
ID string `json:"id"`
Title string `json:"title"`
Content interface{} `json:"content"`
Icon string `json:"icon,omitempty"`
Open bool `json:"open,omitempty"`
}
type StepperData struct {
Steps []StepperStep `json:"steps"`
CurrentStep int `json:"currentStep"`
Orientation string `json:"orientation,omitempty"`
}
type StepperStep struct {
ID string `json:"id"`
Label string `json:"label"`
Description string `json:"description,omitempty"`
Content interface{} `json:"content,omitempty"`
Status string `json:"status,omitempty"`
Icon string `json:"icon,omitempty"`
}
type FormData struct {
Fields []FormField `json:"fields"`
SubmitLabel string `json:"submitLabel,omitempty"`
Layout string `json:"layout,omitempty"`
}
type FormField struct {
ID string `json:"id"`
Type string `json:"type"`
Label string `json:"label"`
Placeholder string `json:"placeholder,omitempty"`
Value interface{} `json:"value,omitempty"`
Options []FormOption `json:"options,omitempty"`
Required bool `json:"required,omitempty"`
Validation string `json:"validation,omitempty"`
}
type FormOption struct {
Value string `json:"value"`
Label string `json:"label"`
}
type CodeBlockData struct {
Code string `json:"code"`
Language string `json:"language"`
Filename string `json:"filename,omitempty"`
Highlight []int `json:"highlight,omitempty"`
ShowLineNum bool `json:"showLineNum,omitempty"`
Copyable bool `json:"copyable,omitempty"`
}
type MarkdownData struct {
Content string `json:"content"`
}
type Report struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
Sections []ReportSection `json:"sections"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Author string `json:"author,omitempty"`
Tags []string `json:"tags,omitempty"`
IsPublic bool `json:"isPublic"`
Theme string `json:"theme,omitempty"`
CustomCSS string `json:"customCss,omitempty"`
}
type ReportSection struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Visualizations []Visualization `json:"visualizations"`
Layout string `json:"layout,omitempty"`
Columns int `json:"columns,omitempty"`
}