Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
a84e2c5
new coding skill: find-skills
makiroll1125 May 19, 2026
3a20e6c
new coding skill: firecrawl (scrape the web)
makiroll1125 May 19, 2026
c8e0c89
new coding skill: excalidraw diagrams
makiroll1125 May 20, 2026
9037235
Update contributing md with SOPs
ahmad-ajmal May 20, 2026
03b2161
new coding skill: planetscale postgres
makiroll1125 May 20, 2026
b6fe613
new coding skill: planetscale mysql
makiroll1125 May 20, 2026
4628fd7
new coding skill: planetscale vitess
makiroll1125 May 20, 2026
5d92c45
new coding skill: planetscale neki
makiroll1125 May 20, 2026
169fb23
new coding skill: cloudflare workers best practices
makiroll1125 May 20, 2026
35a37d4
new coding skills: essential Trail of Bits skills + modified existing…
makiroll1125 May 20, 2026
cecef3c
new coding skill: awesome-copilot git-commit
makiroll1125 May 20, 2026
2cb6a75
new coding skill: shannon pentester
makiroll1125 May 20, 2026
3b2a84a
clear conversation and task data with clear command
May 20, 2026
b278f65
improvement:more github action
May 20, 2026
67bc3db
Added more google calendar actions
May 20, 2026
1607cde
action expansion for gmail, gdrive, and outlook
May 20, 2026
1922a15
action expansion for Notion, Discord, and Slack
May 21, 2026
4768518
action expansion for Lark
May 21, 2026
d4adc52
action expansion of Jira, Line business, and telegram bot
May 21, 2026
c0b5a55
action expansion for whatsapp
May 21, 2026
8b9cfab
action expansion google docs
May 21, 2026
2d25be3
action expansion lark calendar
May 21, 2026
08a436f
action expansion twitter
May 21, 2026
1bdcedb
Lint and Formating Fix
ahmad-ajmal May 21, 2026
7ab8c1d
integration connection in onboarding step
May 22, 2026
b32b96e
refactor: migrate browser frontend state to Redux Toolkit
ahmad-ajmal May 22, 2026
f28f967
selectEnabledSkills
ahmad-ajmal May 22, 2026
0ac4601
revamp: modal component
ahmad-ajmal May 22, 2026
5830b0b
current version fix
ahmad-ajmal May 22, 2026
30e8ad4
Check update fix
ahmad-ajmal May 22, 2026
2f6f6e9
Remove TUI support
May 22, 2026
9510585
fix: display user-invoked skills in Skills icon in Dashboard
makiroll1125 May 25, 2026
c51a785
Feat/slash command autocomplete (#268)
makiroll1125 May 25, 2026
1fc7014
fix: skill information (invocations, top skills) persist across sessions
makiroll1125 May 25, 2026
e2f3e39
fix: skill information (invocations, top skills) persist across sessi…
makiroll1125 May 25, 2026
9327d17
Reset setting config
May 25, 2026
6d0bc89
fix: add support for OpenAI Images 2.0 and Gemini Nano Banana 2 image…
makiroll1125 May 26, 2026
d326f70
fix: Gemini is default provider if both OpenAI and Gemini API keys ar…
makiroll1125 May 26, 2026
d8ea880
Merge branch 'V1.3.2' into feature/coding-skills
makiroll1125 May 26, 2026
73a43af
Merge pull request #283 from CraftOS-dev/feature/coding-skills
makiroll1125 May 26, 2026
76d6e70
Merge pull request #284 from CraftOS-dev/fix/skill-dashboard
zfoong May 26, 2026
bc67127
feature:added bedrock as provider and fixed gemini and openrouter cac…
May 26, 2026
7064917
Merge branch 'V1.3.2' of https://github.com/craftos-dev/craftbot into…
May 26, 2026
5d3a8ba
update requirement and setting of Bedrock
May 26, 2026
4729ced
merge to V1.3.2
May 26, 2026
99e56c9
Merge pull request #271 from CraftOS-dev/improvement/integration-acti…
zfoong May 26, 2026
eb91b76
Improvement/task interative update (#287)
zfoong May 27, 2026
160ca29
removed repeated validation check
ahmad-ajmal May 27, 2026
a249292
create dm channel struct check
ahmad-ajmal May 27, 2026
40d95df
Fix issue 238: reinitialize cache session mid task provider switch
May 27, 2026
fc0958c
Merge branch 'V1.3.2' of https://github.com/craftos-dev/craftbot into…
May 27, 2026
ad5e886
bug:fix tui module not found issue
May 27, 2026
0cc7c07
UI:task list scrolling logic
May 27, 2026
cff524e
UI update: attachement preview
May 27, 2026
6752564
UI improve:Increase contrast and other improvement
May 27, 2026
03f0235
UI update:link color and marketplace icon
May 27, 2026
f8f888d
Merge pull request #285 from CraftOS-dev/fix-generate-image
zfoong May 28, 2026
74ef9b5
add base logic for integration dashboard icon
makiroll1125 May 28, 2026
add81fb
fix: dashboard integration not showing data properly
makiroll1125 May 28, 2026
37d3e66
V1.3.2 Lint Checks and fixes
ahmad-ajmal May 28, 2026
862dcb8
Merge branch 'V1.3.2' of https://github.com/CraftOS-dev/CraftBot into…
makiroll1125 May 28, 2026
85f2075
feat: base working storage for integrations
makiroll1125 May 28, 2026
78a32d3
fix: github and other integrations not counting
makiroll1125 May 28, 2026
97d4ace
feat: add integration icon and analytics in dashboard + finish testei…
makiroll1125 May 29, 2026
8216c5b
Merge branch 'V1.3.3' into feat/dashboard-integration-icon
ahmad-ajmal Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions app/data/action/integrations/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,21 @@ async def run_client(
raw = method(**kwargs)
if asyncio.iscoroutine(raw):
raw = await raw
return _shape_result(
result = _shape_result(
raw,
unwrap_envelope=unwrap_envelope,
success_message=success_message,
fail_message=fail_message,
)
if result.get("status") != "error":
try:
from app.ui_layer.metrics.collector import MetricsCollector
collector = MetricsCollector.get_instance()
if collector:
collector.record_integration_call(integration)
except Exception:
pass
return result
except Exception as e:
return {"status": "error", "message": str(e)}

Expand Down Expand Up @@ -187,12 +196,21 @@ def run_client_sync(
"status": "error",
"message": f"{method_name!r} is async — use run_client (await) instead",
}
return _shape_result(
result = _shape_result(
raw,
unwrap_envelope=unwrap_envelope,
success_message=success_message,
fail_message=fail_message,
)
if result.get("status") != "error":
try:
from app.ui_layer.metrics.collector import MetricsCollector
collector = MetricsCollector.get_instance()
if collector:
collector.record_integration_call(integration)
except Exception:
pass
return result
except Exception as e:
return {"status": "error", "message": str(e)}

Expand Down Expand Up @@ -243,6 +261,13 @@ async def with_client(
result = fn(client, *args, **kwargs)
if asyncio.iscoroutine(result):
result = await result
try:
from app.ui_layer.metrics.collector import MetricsCollector
collector = MetricsCollector.get_instance()
if collector:
collector.record_integration_call(integration)
except Exception:
pass
return {"status": "success", "result": result}
except Exception as e:
return {"status": "error", "message": str(e)}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
.running,
.thinking,
.working {
color: #FF4F18;
color: var(--text-primary);
}

.completed {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import {
Timer,
PlayCircle,
Hammer,
Wrench,
Bot,
Building2,
Hash
Hash,
Globe
} from 'lucide-react'
import { useWebSocket } from '../../contexts/WebSocketContext'
import { Badge, StatusIndicator } from '../../components/ui'
Expand Down Expand Up @@ -113,9 +113,10 @@ export function DashboardPage() {
const [tokenPeriod, setTokenPeriod] = useState<MetricsTimePeriod>('total')
const [usagePeriod, setUsagePeriod] = useState<MetricsTimePeriod>('total')

// Expand/collapse state for top tools/skills lists
// Expand/collapse state for top tools/skills/integrations lists
const [showAllTools, setShowAllTools] = useState(false)
const [showAllSkills, setShowAllSkills] = useState(false)
const [showAllIntegrations, setShowAllIntegrations] = useState(false)

// Request filtered metrics when period changes (for all periods including 'total')
const handlePeriodChange = useCallback((
Expand Down Expand Up @@ -218,6 +219,11 @@ export function DashboardPage() {
const skillTotalInvocations = metrics?.skill?.totalInvocations ?? 0
const topSkills = metrics?.skill?.topSkills ?? []

// Integration metrics
const integrationConnected = metrics?.integration?.connectedIntegrations ?? 0
const integrationTotalCalls = metrics?.integration?.totalCalls ?? 0
const topIntegrations = metrics?.integration?.topIntegrations ?? []

// Model metrics
const modelProvider = metrics?.model?.provider ?? ''
const modelId = metrics?.model?.modelId ?? ''
Expand Down Expand Up @@ -575,6 +581,52 @@ export function DashboardPage() {
</div>
</div>

{/* Integrations Panel */}
<div className={styles.panel}>
<div className={styles.panelHeader}>
<Globe size={16} />
<h3>Integrations</h3>
</div>
<div className={styles.panelContent}>
<div className={styles.compactStats}>
<div className={styles.compactStatItem}>
<CheckCircle size={14} className={styles.successIcon} />
<span className={styles.compactStatValue}>{integrationConnected}</span>
<span className={styles.compactStatLabel}>Connected</span>
</div>
<div className={styles.compactStatItem}>
<Activity size={14} className={styles.primaryIcon} />
<span className={styles.compactStatValue}>{integrationTotalCalls}</span>
<span className={styles.compactStatLabel}>Total Calls</span>
</div>
</div>
<div className={styles.usageSection}>
<div className={styles.usageSectionHeader}>Top Integrations</div>
{topIntegrations.length > 0 ? (
<div className={styles.usageList}>
{(showAllIntegrations ? topIntegrations : topIntegrations.slice(0, 3)).map((intg, index) => (
<div key={intg.name} className={styles.usageItem}>
<span className={styles.usageRank}>#{index + 1}</span>
<span className={styles.usageName}>{intg.name}</span>
<span className={styles.usageCount}>{intg.count}</span>
</div>
))}
{topIntegrations.length > 3 && (
<button
className={styles.viewAllButton}
onClick={() => setShowAllIntegrations(!showAllIntegrations)}
>
{showAllIntegrations ? 'Show less' : `View all (${topIntegrations.length})`}
</button>
)}
</div>
) : (
<div className={styles.emptyUsage}>No usage yet</div>
)}
</div>
</div>
</div>

{/* Model Information Panel */}
<div className={styles.panel}>
<div className={styles.panelHeader}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
}

.stepDot.active {
background: #FF4F18;
border-color: #FF4F18;
background: var(--text-primary);
border-color: var(--text-primary);
color: var(--color-white);
}

Expand Down Expand Up @@ -82,7 +82,7 @@
}

.stepConnector.active {
background: #FF4F18;
background: var(--text-primary);
}

/* Main Content */
Expand Down
8 changes: 8 additions & 0 deletions app/ui_layer/browser/frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,13 @@ export interface ModelMetrics {
modelName: string
}

export interface IntegrationMetrics {
totalIntegrations: number
connectedIntegrations: number
totalCalls: number
topIntegrations: UsageCount[]
}

export interface DashboardMetrics {
uptimeSeconds: number
timestamp: number
Expand All @@ -320,6 +327,7 @@ export interface DashboardMetrics {
usage: UsageMetrics
mcp: MCPMetrics
skill: SkillMetrics
integration: IntegrationMetrics
model: ModelMetrics
}

Expand Down
121 changes: 121 additions & 0 deletions app/ui_layer/metrics/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,27 @@ class SkillMetrics:
top_skills: List[UsageCount] = field(default_factory=list)


@dataclass
class IntegrationInfo:
"""Information about an integration."""

name: str
connected: bool
description: str = ""
icon: str = ""


@dataclass
class IntegrationMetrics:
"""Integration metrics."""

total_integrations: int = 0
connected_integrations: int = 0
total_calls: int = 0
integrations: List[IntegrationInfo] = field(default_factory=list)
top_integrations: List[UsageCount] = field(default_factory=list)


@dataclass
class ModelMetrics:
"""Current model information."""
Expand Down Expand Up @@ -269,6 +290,9 @@ class DashboardMetrics:
# Skills
skill: SkillMetrics = field(default_factory=SkillMetrics)

# Integrations
integration: IntegrationMetrics = field(default_factory=IntegrationMetrics)

# Model info
model: ModelMetrics = field(default_factory=ModelMetrics)

Expand Down Expand Up @@ -367,6 +391,15 @@ def to_dict(self) -> Dict[str, Any]:
"modelId": self.model.model_id,
"modelName": self.model.model_name,
},
"integration": {
"totalIntegrations": self.integration.total_integrations,
"connectedIntegrations": self.integration.connected_integrations,
"totalCalls": self.integration.total_calls,
"topIntegrations": [
{"name": i.name, "count": i.count}
for i in self.integration.top_integrations
],
},
}


Expand Down Expand Up @@ -468,10 +501,15 @@ def __init__(self, agent: Optional["AgentBase"] = None) -> None:
self._skill_usage: Dict[str, int] = defaultdict(int)
self._skill_total_invocations: int = 0

# Integration usage tracking
self._integration_usage: Dict[str, int] = defaultdict(int)
self._integration_total_calls: int = 0

# Storage references for historical data
self._usage_storage = None
self._task_storage = None
self._skill_storage = None
self._integration_storage = None
self._init_storage()

def _init_storage(self) -> None:
Expand All @@ -491,7 +529,14 @@ def _init_storage(self) -> None:
self._skill_storage = get_skill_storage()
except Exception:
pass
try:
from app.usage.integration_storage import get_integration_storage

self._integration_storage = get_integration_storage()
except Exception:
pass
self._load_skill_metrics()
self._load_integration_metrics()

def _load_skill_metrics(self) -> None:
"""Restore skill invocation counts from persistent storage on startup."""
Expand Down Expand Up @@ -689,6 +734,42 @@ def get_top_skills(self, limit: int = 3) -> List[Tuple[str, int]]:
)
return sorted_skills[:limit]

# ─────────────────────────────────────────────────────────────────────
# Integration Usage Tracking
# ─────────────────────────────────────────────────────────────────────

def _load_integration_metrics(self) -> None:
"""Restore integration call counts from persistent storage on startup."""
if not self._integration_storage:
return
try:
totals = self._integration_storage.get_integration_totals()
with self._lock:
for integration_name, count in totals.items():
self._integration_usage[integration_name] = count
self._integration_total_calls = sum(totals.values())
except Exception:
pass

def record_integration_call(self, integration_name: str) -> None:
"""Record a successful integration call."""
with self._lock:
self._integration_usage[integration_name] += 1
self._integration_total_calls += 1
if self._integration_storage:
try:
self._integration_storage.insert_call(integration_name)
except Exception:
pass

def get_top_integrations(self, limit: int = 3) -> List[Tuple[str, int]]:
"""Get top N most used integrations."""
with self._lock:
sorted_integrations = sorted(
self._integration_usage.items(), key=lambda x: x[1], reverse=True
)
return sorted_integrations[:limit]

# ─────────────────────────────────────────────────────────────────────
# System Metrics
# ─────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -888,6 +969,44 @@ def _get_skill_metrics(self) -> SkillMetrics:
except Exception:
return SkillMetrics()

def _get_integration_metrics(self) -> IntegrationMetrics:
"""Get integration metrics."""
try:
from craftos_integrations import list_integrations_sync

integrations_data = list_integrations_sync()
integrations = []
connected = 0

for intg in integrations_data:
is_connected = intg.get("connected", False)
if is_connected:
connected += 1
integrations.append(
IntegrationInfo(
name=intg.get("name", intg.get("id", "")),
connected=is_connected,
description=intg.get("description", ""),
icon=intg.get("icon", ""),
)
)

# Get top integrations usage
top_integrations = [
UsageCount(name=name, count=count)
for name, count in self.get_top_integrations(3)
]

return IntegrationMetrics(
total_integrations=len(integrations),
connected_integrations=connected,
total_calls=self._integration_total_calls,
integrations=integrations,
top_integrations=top_integrations,
)
except Exception:
return IntegrationMetrics()

# ─────────────────────────────────────────────────────────────────────
# Model Metrics
# ─────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -1033,6 +1152,7 @@ def get_metrics(self) -> DashboardMetrics:
thread_pool_metrics = self._get_thread_pool_metrics()
mcp_metrics = self._get_mcp_metrics()
skill_metrics = self._get_skill_metrics()
integration_metrics = self._get_integration_metrics()
model_metrics = self._get_model_metrics()

return DashboardMetrics(
Expand Down Expand Up @@ -1070,6 +1190,7 @@ def get_metrics(self) -> DashboardMetrics:
),
mcp=mcp_metrics,
skill=skill_metrics,
integration=integration_metrics,
model=model_metrics,
)

Expand Down
Loading