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:
759
backend/internal/labs/generator.go
Normal file
759
backend/internal/labs/generator.go
Normal 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```"
|
||||
}
|
||||
Reference in New Issue
Block a user