Merge PR #38 with conflict resolution

This commit is contained in:
xmanrui
2026-06-05 03:20:46 +08:00
4 changed files with 69 additions and 19 deletions
+30 -6
View File
@@ -71,7 +71,7 @@ interface AllStats {
monthly: DayStat[];
}
type TimeRange = "daily" | "weekly" | "monthly";
type TimeRange = "daily" | "weekly" | "monthly" | "total";
interface SubagentActivityEvent {
key: string;
@@ -238,7 +238,7 @@ export default function Home() {
const [agentStates, setAgentStates] = useState<Record<string, string>>(cachedHomeAgentStates);
const [agentActivity, setAgentActivity] = useState<AgentActivityData[] | null>(null);
const RANGE_LABELS: Record<TimeRange, string> = { daily: t("range.daily"), weekly: t("range.weekly"), monthly: t("range.monthly") };
const RANGE_LABELS: Record<TimeRange, string> = { daily: t("range.daily"), weekly: t("range.weekly"), monthly: t("range.monthly"), total: t("range.total") };
const REFRESH_OPTIONS = [
{ label: t("refresh.manual"), value: 0 },
@@ -780,10 +780,34 @@ export default function Home() {
</div>
</div>
{(() => {
const currentData = allStats[statsRange];
const totalInput = currentData.reduce((s, d) => s + d.inputTokens, 0);
const totalOutput = currentData.reduce((s, d) => s + d.outputTokens, 0);
const totalMsgs = currentData.reduce((s, d) => s + d.messageCount, 0);
const currentData = statsRange === "total" ? allStats.monthly : allStats[statsRange];
let totalInput: number, totalOutput: number, totalMsgs: number;
if (statsRange === "total") {
// Sum all data points (use daily for accurate totals, chart uses monthly)
const allDaily = allStats.daily;
totalInput = allDaily.reduce((s, d) => s + d.inputTokens, 0);
totalOutput = allDaily.reduce((s, d) => s + d.outputTokens, 0);
totalMsgs = allDaily.reduce((s, d) => s + d.messageCount, 0);
} else {
// Show current period only (today/this week/this month)
const now = new Date();
const getCurrentPeriodKey = () => {
if (statsRange === "daily") return now.toISOString().slice(0, 10);
if (statsRange === "weekly") {
const day = now.getUTCDay();
const mondayOffset = day === 0 ? -6 : 1 - day;
const monday = new Date(now.getTime() + mondayOffset * 86400000);
return monday.toISOString().slice(0, 10);
}
return now.toISOString().slice(0, 7); // monthly
};
const periodKey = getCurrentPeriodKey();
const latestEntry = currentData.length > 0 ? currentData[currentData.length - 1] : null;
const currentEntry = latestEntry?.date === periodKey ? latestEntry : null;
totalInput = currentEntry?.inputTokens ?? 0;
totalOutput = currentEntry?.outputTokens ?? 0;
totalMsgs = currentEntry?.messageCount ?? 0;
}
return (
<>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-4">
+20 -3
View File
@@ -360,9 +360,26 @@ function StatsDetail({ agentId }: { agentId: string }) {
if (!stats) return null;
const currentData = stats[range];
const totalInput = currentData.reduce((s, d) => s + d.inputTokens, 0);
const totalOutput = currentData.reduce((s, d) => s + d.outputTokens, 0);
const totalMessages = currentData.reduce((s, d) => s + d.messageCount, 0);
// Summary cards show the current period only (latest entry = today/this week/this month)
const latestEntry = currentData.length > 0 ? currentData[currentData.length - 1] : null;
const now = new Date();
const isCurrentPeriod = (entry: typeof latestEntry) => {
if (!entry) return false;
if (range === "daily") return entry.date === now.toISOString().slice(0, 10);
if (range === "weekly") {
const day = now.getUTCDay();
const mondayOffset = day === 0 ? -6 : 1 - day;
const monday = new Date(now.getTime() + mondayOffset * 86400000);
return entry.date === monday.toISOString().slice(0, 10);
}
if (range === "monthly") return entry.date === now.toISOString().slice(0, 7);
return false;
};
const currentEntry = isCurrentPeriod(latestEntry) ? latestEntry : null;
const totalInput = currentEntry?.inputTokens ?? 0;
const totalOutput = currentEntry?.outputTokens ?? 0;
const totalMessages = currentEntry?.messageCount ?? 0;
return (
<main className="min-h-screen p-4 md:p-8 max-w-6xl mx-auto">
+3
View File
@@ -174,6 +174,7 @@ const translations: Record<Locale, Record<string, string>> = {
"range.daily": "按日",
"range.weekly": "按週",
"range.monthly": "按月",
"range.total": "總計",
// refresh options
"refresh.manual": "手動重新整理",
@@ -499,6 +500,7 @@ const translations: Record<Locale, Record<string, string>> = {
"range.daily": "按天",
"range.weekly": "按周",
"range.monthly": "按月",
"range.total": "总计",
// refresh options
"refresh.manual": "手动刷新",
@@ -828,6 +830,7 @@ const translations: Record<Locale, Record<string, string>> = {
"range.daily": "Daily",
"range.weekly": "Weekly",
"range.monthly": "Monthly",
"range.total": "Total",
// refresh options
"refresh.manual": "Manual Refresh",
+16 -10
View File
@@ -71,17 +71,21 @@ function scanSkillsDir(dir: string, source: string): SkillInfo[] {
return skills;
}
function getConfiguredAgentWorkspaces(): Array<{ id: string; workspace?: string }> {
function getConfiguredAgentWorkspaces(): Array<{ id: string; workspace?: string; agentDir?: string }> {
if (!fs.existsSync(OPENCLAW_CONFIG_PATH)) return [];
try {
const config = JSON.parse(fs.readFileSync(OPENCLAW_CONFIG_PATH, "utf-8"));
const agentList = Array.isArray(config.agents?.list) ? config.agents.list : [];
return agentList
.filter((agent: unknown): agent is { id: string; workspace?: string } => {
.filter((agent: unknown): agent is { id: string; workspace?: string; agentDir?: string } => {
return Boolean(agent && typeof agent === "object" && typeof (agent as { id?: string }).id === "string");
})
.map((agent: { id: string; workspace?: string }) => ({ id: agent.id, workspace: agent.workspace }));
.map((agent: { id: string; workspace?: string; agentDir?: string }) => ({
id: agent.id,
workspace: agent.workspace,
agentDir: agent.agentDir,
}));
} catch {
return [];
}
@@ -102,8 +106,8 @@ function getWorkspaceSkillSources(): Array<{ dir: string; source: string }> {
addSource(path.join(OPENCLAW_HOME, "workspace", "skills"), "workspace:main");
for (const agent of getConfiguredAgentWorkspaces()) {
if (!agent.workspace) continue;
addSource(path.join(agent.workspace, "skills"), `workspace:${agent.id}`);
addSource(agent.workspace ? path.join(agent.workspace, "skills") : undefined, `workspace:${agent.id}`);
addSource(agent.agentDir ? path.join(agent.agentDir, "skills") : undefined, `workspace:${agent.id}`);
}
return sources;
@@ -180,9 +184,12 @@ export function listOpenclawSkills(): { skills: SkillInfo[]; agents: Record<stri
}
}
const legacyCustomSkills = scanSkillsDir(path.join(OPENCLAW_HOME, "skills"), "custom");
const customSkills = scanSkillsDir(path.join(OPENCLAW_HOME, "skills"), "custom");
const workspaceSkills = getWorkspaceSkillSources().flatMap(({ dir, source }) => scanSkillsDir(dir, source));
const allSkills = mergeSkillsByLocation([...builtinSkills, ...extSkills, ...legacyCustomSkills, ...workspaceSkills]);
const allSkills = mergeSkillsByLocation([...builtinSkills, ...extSkills, ...customSkills, ...workspaceSkills]);
let config: any = null;
try { config = JSON.parse(fs.readFileSync(OPENCLAW_CONFIG_PATH, "utf-8")); } catch { /* config unavailable */ }
const agentSkills = getAgentSkillsFromSessions();
for (const skill of allSkills) {
@@ -194,10 +201,9 @@ export function listOpenclawSkills(): { skills: SkillInfo[]; agents: Record<stri
skill.usedBy = Array.from(new Set(skill.usedBy)).sort();
}
const config = JSON.parse(fs.readFileSync(OPENCLAW_CONFIG_PATH, "utf-8"));
const agentList = config.agents?.list || [];
const agentListForInfo = (config || {}).agents?.list || [];
const agents: Record<string, SkillAgentInfo> = {};
for (const agent of agentList) {
for (const agent of agentListForInfo) {
agents[agent.id] = {
name: agent.identity?.name || agent.name || agent.id,
emoji: agent.identity?.emoji || "🤖",