Skip to content

Instantly share code, notes, and snippets.

@rutvij2292
Last active September 24, 2025 19:09
Show Gist options
  • Save rutvij2292/1519e503c4a55edab43a9861c8a6f0d6 to your computer and use it in GitHub Desktop.
Save rutvij2292/1519e503c4a55edab43a9861c8a6f0d6 to your computer and use it in GitHub Desktop.
AI Integration through Laravel App
<?php
namespace App\Http\Controllers;
use App\Services\ChartPromptTemplate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class ChatController extends Controller {
public function __invoke(Request $request): \Illuminate\Http\JsonResponse
{
$contents = [
[
"role" => "user",
"content" => ChartPromptTemplate::generatePrompt(
datasetJson: json_encode($request->chartData ?? []),
userQuery: $request->userMessage ?? '',
convertTime: $request->convertTime ?? true,
),
]
];
try {
$response = $this->callLLMApi($contents);
if (!$response['success']) {
return response()->json([
'error' => 'Failed to get AI response',
'details' => $response['error']
], 500);
}
// Clean and format the markdown response
$cleanedMarkdown = $this->cleanMarkdownResponse($response['text']);
return response()->json([
'success' => true,
'markdown' => $cleanedMarkdown,
'raw_text' => $response['text'] // Optional: for debugging
]);
} catch (\Exception $e) {
Log::error('ChatController error:', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return response()->json([
'error' => 'Internal server error',
'message' => 'Unable to process your request'
], 500);
}
}
private function callLLMApi(array $contents): string|array
{
$apiKey = env('VITE_GROQ_API_KEY');
if (!$apiKey) {
throw new \Exception('API key not configured');
}
$url = "https://api.groq.com/openai/v1/chat/completions";
$model = "deepseek-r1-distill-llama-70b";
$response = Http::withHeaders([
'Authorization' => "Bearer {$apiKey}",
'Content-Type' => 'application/json',
])->post($url, [
'model' => $model,
'messages' => $contents,
'temperature' => 0.5,
'max_tokens' => 8000,
]);
if ($response->failed()) {
return [
'success' => false,
'error' => 'API request failed: ' . $response->status() . ' - ' . $response->body()
];
}
$responseData = $response->json();
if (!isset($responseData['choices'][0]['message']['content'])) {
return [
'success' => false,
'error' => 'Invalid API response format'
];
}
return [
'success' => true,
'text' => $this->removeThinkingSteps($responseData['choices'][0]['message']['content']),
];
}
private function cleanMarkdownResponse(string $text): string
{
// Remove any leading/trailing whitespace
$text = trim($text);
// Remove markdown code block wrappers if present
$text = preg_replace('/^```(?:markdown)?\s*\n/', '', $text);
$text = preg_replace('/\n```\s*$/', '', $text);
// Ensure proper spacing after headings
$text = preg_replace('/^(#{1,4}\s+.*?)$/m', '$1' . "\n", $text);
// Clean up excessive line breaks (more than 2 in a row)
$text = preg_replace('/\n{3,}/', "\n\n", $text);
return trim($text);
}
protected function removeThinkingSteps($content): string
{
// Remove common thinking patterns
$patterns = [
'/^<thinking>.*?<\/thinking>\s*/s',
'/^<think>.*?<\/think>\s*/s',
'/^Let me think.*?\n\n/s',
'/^I need to.*?\n\n/s',
'/^First, let me.*?\n\n/s',
'/^To answer this.*?\n\n/s',
];
foreach ($patterns as $pattern) {
$content = preg_replace($pattern, '', $content);
}
return trim($content);
}
}
?>
<?php
namespace App\Services;
class ChartPromptTemplate
{
// Intent detection based on keywords
// Intent detection will later be handled by a smaller LLM model (e.g., llama-3.1-8b-instant) to reduce costs.
// A lightweight model is sufficient for handling intent detection effectively.
public static function detectIntent($userQuery): string
{
$query = strtolower(trim($userQuery));
// PRIORITY ORDER MATTERS - Check summary first to avoid calculation override
// 1. SUMMARY INTENT (highest priority)
if (preg_match('/\b(summarize|summary|overview|describe|explain|insights|key findings|brief|quick look|main points)\b/', $query)) {
return 'summary';
}
// 2. CALCULATION INTENT
if (preg_match('/\b(average|mean|total|sum|calculate|compute|minimum|maximum|highest|lowest|median|mode|add up|find the|get the)\b/', $query)) {
return 'calculation';
}
// 3. COMPARISON INTENT
if (preg_match('/\b(compare|comparison|versus|vs|against|between.*and|difference|change|growth|decline|increase|decrease|better|worse|higher|lower|more than|less than|which performed|which had more|which had less)\b/', $query)) {
return 'comparison';
}
// 4. ANOMALY INTENT
if (preg_match('/\b(anomaly|anomalies|outlier|outliers|unusual|abnormal|strange|odd|weird|different|stands out|irregularity|exception|deviation)\b/', $query)) {
return 'anomaly';
}
// 5. TREND INTENT
if (preg_match('/\b(trend|trends|pattern|patterns|over time|timeline|growth rate|progression|evolution|trajectory|increasing|decreasing|stable|fluctuation)\b/', $query)) {
return 'trend';
}
return 'general';
}
// Base prompt components
private static function getBasePrompt(): string
{
return "You are an expert data analyst assistant specializing in chart and dataset analysis. Provide direct, concise answers without showing your reasoning process or thinking steps. Give only the final answer or response.
**CRITICAL OUTPUT REQUIREMENTS:**
- Return ONLY valid **markdown** text.
- DO NOT include any greetings, conversational explanations, or helper phrases.
- Begin directly with the markdown title (`# Summary`) or another suitable heading that fits the content.
- Use **tables**, **lists**, or **headings** wherever appropriate to organize insights clearly.
- Structure your output in a way that **best fits the data** — it could be category-wise blocks, temporal summaries, pattern-focused sections, or grouped breakdowns.
- **Avoid math operations** like totals or averages unless explicitly required.
- Focus on **descriptive**, **comparative**, and **pattern-based** insights.";
}
private static function getTimeInstructions(): string
{
return "
---
**STRICT TIME CONVERSION RULE (ENFORCE THIS):**
If the dataset includes values representing time in minutes, **always convert these to human-readable formats**:
- Formula:
- `days = floor(minutes / 1440)`
- `hours = floor((minutes % 1440) / 60)`
- `minutes = minutes % 60`
- Output format:
- If ≥ 1 day: `X days Y hours Z minutes`
- If < 1 day: `Y hours Z minutes`
- If < 1 hour: `Z minutes`
**Examples:**
- 720 → 12 hours 0 minutes
- 1760 → 1 day 5 hours 20 minutes
- 42 → 42 minutes
**Do not** show raw minute values under any circumstance.
---
";
}
// Intent-specific format instructions
private static function getIntentInstructions($intent): string
{
$instructions = [
'summary' => "
**DATA SUMMARY REQUIREMENTS:**
1. Identify key patterns, spikes, dips, and consistency across categories or time.
2. Use **category-wise breakdowns** if the dataset includes groupings (e.g., job roles, product types).
3. Use **tables or bullet lists** when they help structure repetitive or comparative insights more clearly.
4. Highlight differences between groups or over time using clear, concise language.
5. Always include **what this could mean** from a practical or operational point of view — but do not over-speculate.
6. If the data includes time-based or weekly trends, note those patterns.
7. Do NOT repeat the input data unless it's used for presenting insights.
CRITICAL: This is a SUMMARY request - do NOT calculate totals, averages, or perform any math operations. Focus on describing what you observe in the data patterns.
FORMAT:
```
# Summary
| Label | [Entity A] | [Entity B] | ..... | [Entity N] |
|--------|------------|------------|------|------------|
| [First Label] | [Value A] | [Value B] |------|------------|
## Overview
[Brief description of what the dataset represents and covers]
## Categorical Observations:
[Provide list of categorical observations.]
## Key Findings
- **Performance Leader**: [Which performs better]
- **Significant Changes**: [Notable differences]
- **Trends**: [Directional patterns]
## Notable Insights
- [Interesting observations]
- [Standout patterns]
```",
'calculation' => "
**CALCULATION ANALYSIS REQUIREMENTS:**
1. **Show Your Thinking**: Always include step-by-step calculation process
2. **Verify Data**: Identify and list the relevant data points
3. **Mathematical Process**: Show each calculation step with actual numbers
4. **Final Answer**: Present result clearly with proper formatting
FORMAT:
```
# Calculation Analysis
## Data Identification
[List the specific values being used]
## Step-by-Step Calculation
1. **Method**: [Explain the mathematical approach]
2. **Process**:
- Step 1: [First calculation with actual numbers]
- Step 2: [Next calculation if needed]
- Step 3: [Continue as needed]
## Final Result
**Answer**: [Final calculated value with units/context]
```",
'comparison' => "
**COMPARATIVE ANALYSIS REQUIREMENTS:**
1. **Identify Entities**: Clearly state what is being compared
2. **Metrics Table**: Use structured comparison with percentage changes
3. **Key Insights**: Highlight significant differences and patterns
4. **Context**: Explain what the differences mean
FORMAT:
```
# Comparative Analysis
## Comparison Overview
Comparing: [Entity A] vs [Entity B]
## Metrics Comparison
| Metric | [Entity A] | [Entity B] | Difference | % Change |
|--------|------------|------------|------------|----------|
| [Metric 1] | [Value A] | [Value B] | [Diff] | [%] |
## Key Findings
- **Performance Leader**: [Which performs better]
- **Significant Changes**: [Notable differences]
- **Trends**: [Directional patterns]
```",
'anomaly' => "
**ANOMALY DETECTION REQUIREMENTS:**
1. **Statistical Context**: Compare values to normal ranges
2. **Severity Assessment**: Indicate how significant each anomaly is
3. **Potential Causes**: Suggest possible explanations
4. **Impact Analysis**: Explain what anomalies might mean
FORMAT:
```
# Anomaly Detection
## Identified Anomalies
1. **[Location/Time]**
- Observed Value: [Actual value]
- Normal Range: [Expected range]
- Deviation: [How far from normal]
## Severity Analysis
- **High Impact**: [Significant anomalies]
- **Moderate**: [Notable but less critical]
```",
'trend' => "
TREND ANALYSIS REQUIREMENTS:
1. **Pattern Identification**: Describe directional movements
2. **Quantify Changes**: Include growth rates and percentages
3. **Time Periods**: Break down trends by relevant time segments
4. **Forecasting**: Suggest what trends might continue
FORMAT:
```
# Trend Analysis
## Primary Trends
- **Overall Direction**: [Main trend direction]
- **Growth Rate**: [Quantified change rate]
- **Consistency**: [How stable the trend is]
## Time Period Breakdown
- **[Period 1]**: [Trend description]
- **[Period 2]**: [Trend description]
```",
'general' => "
GENERAL ANALYSIS REQUIREMENTS:
Analyze the dataset and user query to provide the most relevant insights.
Use appropriate formatting based on the nature of the question.
Include specific data points and clear explanations.
FORMAT:
```
# Data Analysis
## Analysis
[Comprehensive response based on query nature]
## Key Points
- [Important findings]
- [Supporting data]
## Conclusion
[Summary of insights]
```",
];
return $instructions[$intent] ?? $instructions['general'];
}
// Main prompt generator
public static function generatePrompt(string $datasetJson, string $userQuery, bool $convertTime = false) {
// Detect intent or use forced intent
$intent = self::detectIntent($userQuery);
// Build prompt
$prompt = self::getBasePrompt();
// Add time conversion if needed
if ($convertTime) {
$prompt .= self::getTimeInstructions();
}
// Add intent-specific instructions
$prompt .= self::getIntentInstructions($intent);
// Add dataset and query
$prompt .= "
DATASET TO ANALYZE:
" . $datasetJson . "
USER QUERY: " . $userQuery . "
ANALYSIS INSTRUCTIONS:
1. Parse the dataset carefully
2. Understand the user's specific question
3. Apply the appropriate analysis method based on intent: " . strtoupper($intent) . "
4. Follow the format requirements exactly
5. Include specific numbers and data points from the dataset
6. Ensure all calculations are accurate and shown step-by-step
7. Provide actionable insights where relevant
Remember: Start your response immediately with the markdown title (# Title). No other text before it.";
return $prompt;
}
}

Creating a separate route in Laravel helps exposing API keys via axios call. Currently, LLM calls are made from backend.

//File - Web.php
Route::post('/analytics/chat', ChatController::class);
//Axios call to get response from controller
    try {
        const response = await axios.post('/analytics/chat', {
            userMessage: query,
            chartData: props.chartData,
            convertTime: props.minutes,
        });

        const markdown = response.data.markdown;
        const markdownMessage = marked.parse(markdown);

        const aiMessage = {
            id: `ai-msg-${messageIdCounter++}`,
            role: 'ai',
            message: markdownMessage,
            isHtml: true
        };

        // Add AI message
        messageHistory.value = [...messageHistory.value, aiMessage];

        // Call scrollToBottom after adding AI message
        scrollToBottom();

        latestAiAnnouncement.value = markdownMessage;

    } catch (err) {
        console.error('Chat error:', err);
    } finally {
        isProcessing.value = false;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment