claude-smart 0.2.49 → 0.2.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.md +10 -43
- package/bin/claude-smart.js +105 -0
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.coverage +0 -0
- package/plugin/README.md +4 -3
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
- package/plugin/dashboard/app/dashboard/page.tsx +6 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
- package/plugin/dashboard/app/preferences/page.tsx +32 -35
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
- package/plugin/dashboard/app/sessions/page.tsx +2 -0
- package/plugin/dashboard/app/skills/page.tsx +65 -50
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
- package/plugin/dashboard/components/common/host-badge.tsx +118 -0
- package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +3 -3
- package/plugin/dashboard/lib/config-file.ts +5 -1
- package/plugin/dashboard/lib/host-attribution.ts +62 -0
- package/plugin/dashboard/lib/session-reader.ts +40 -2
- package/plugin/dashboard/lib/types.ts +7 -1
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +197 -1
- package/plugin/scripts/backend-python-runner.py +46 -0
- package/plugin/scripts/backend-service.sh +757 -119
- package/plugin/scripts/codex-hook.js +63 -225
- package/plugin/scripts/dashboard-open.sh +6 -4
- package/plugin/scripts/dashboard-service.sh +117 -136
- package/plugin/scripts/hook_entry.sh +3 -0
- package/plugin/scripts/smart-install.sh +15 -1
- package/plugin/src/claude_smart/cli.py +14 -0
- package/plugin/src/claude_smart/context_inject.py +3 -0
- package/plugin/src/claude_smart/env_config.py +4 -1
- package/plugin/src/claude_smart/events/post_tool.py +2 -1
- package/plugin/src/claude_smart/events/session_end.py +2 -1
- package/plugin/src/claude_smart/events/stop.py +3 -0
- package/plugin/src/claude_smart/events/user_prompt.py +2 -1
- package/plugin/src/claude_smart/internal_call.py +5 -2
- package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
- package/plugin/src/claude_smart/publish.py +59 -7
- package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
- package/plugin/src/claude_smart/runtime.py +15 -6
- package/plugin/src/claude_smart/state.py +211 -52
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +13 -0
- package/plugin/vendor/reflexio/reflexio/README.md +7 -3
- package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +126 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +177 -25
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +66 -3
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
- package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +62 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +20 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
- package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +85 -16
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +510 -63
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
- package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +122 -44
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +43 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +45 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +146 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +42 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +59 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +52 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo, useState } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
AlertTriangle,
|
|
7
|
+
BookOpen,
|
|
8
|
+
ChevronRight,
|
|
9
|
+
Layers3,
|
|
10
|
+
} from "lucide-react";
|
|
6
11
|
import { PageHeader } from "@/components/common/page-header";
|
|
12
|
+
import { HostBadge } from "@/components/common/host-badge";
|
|
13
|
+
import { LearningApplicationBadge } from "@/components/common/learning-application-badge";
|
|
7
14
|
import { EmptyState } from "@/components/common/empty-state";
|
|
8
15
|
import { DeleteAllButton } from "@/components/common/delete-all-button";
|
|
9
16
|
import { PageTabs } from "@/components/common/page-tabs";
|
|
@@ -18,6 +25,7 @@ import {
|
|
|
18
25
|
} from "@/components/ui/select";
|
|
19
26
|
import { reflexio } from "@/lib/reflexio-client";
|
|
20
27
|
import { formatRelative } from "@/lib/format";
|
|
28
|
+
import { useRequestHostAttribution } from "@/lib/host-attribution";
|
|
21
29
|
import { cn } from "@/lib/utils";
|
|
22
30
|
import {
|
|
23
31
|
agentPlaybookStatusLabel,
|
|
@@ -27,6 +35,7 @@ import {
|
|
|
27
35
|
} from "@/lib/status";
|
|
28
36
|
import type {
|
|
29
37
|
AgentPlaybook,
|
|
38
|
+
Host,
|
|
30
39
|
PlaybookApplicationStat,
|
|
31
40
|
UserPlaybook,
|
|
32
41
|
} from "@/lib/types";
|
|
@@ -58,24 +67,29 @@ const SHARED_STATUS_META: Record<
|
|
|
58
67
|
interface SkillCard {
|
|
59
68
|
kind: SkillKind;
|
|
60
69
|
id: number;
|
|
61
|
-
agentVersion: string;
|
|
62
70
|
createdAt: number;
|
|
63
71
|
content: string;
|
|
64
72
|
trigger: string | null;
|
|
65
73
|
rationale: string | null;
|
|
66
74
|
status: SkillStatus;
|
|
75
|
+
scopeId: string;
|
|
76
|
+
host: Host | null;
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
function projectSkill(
|
|
79
|
+
function projectSkill(
|
|
80
|
+
p: UserPlaybook,
|
|
81
|
+
requestHosts?: ReadonlyMap<string, Host | null>,
|
|
82
|
+
): SkillCard {
|
|
70
83
|
return {
|
|
71
84
|
kind: "project",
|
|
72
85
|
id: p.user_playbook_id,
|
|
73
|
-
agentVersion: p.agent_version || "default",
|
|
74
86
|
createdAt: p.created_at,
|
|
75
87
|
content: p.content,
|
|
76
88
|
trigger: p.trigger,
|
|
77
89
|
rationale: p.rationale,
|
|
78
90
|
status: statusLabel(p),
|
|
91
|
+
scopeId: p.user_id || "unknown",
|
|
92
|
+
host: requestHosts?.get(p.request_id) ?? null,
|
|
79
93
|
};
|
|
80
94
|
}
|
|
81
95
|
|
|
@@ -83,12 +97,13 @@ function sharedSkill(p: AgentPlaybook): SkillCard {
|
|
|
83
97
|
return {
|
|
84
98
|
kind: "shared",
|
|
85
99
|
id: p.agent_playbook_id,
|
|
86
|
-
agentVersion: p.agent_version || "default",
|
|
87
100
|
createdAt: p.created_at,
|
|
88
101
|
content: p.content,
|
|
89
102
|
trigger: p.trigger,
|
|
90
103
|
rationale: p.rationale,
|
|
91
104
|
status: agentPlaybookStatusLabel(p),
|
|
105
|
+
scopeId: p.agent_version || "default",
|
|
106
|
+
host: null,
|
|
92
107
|
};
|
|
93
108
|
}
|
|
94
109
|
|
|
@@ -106,10 +121,11 @@ export default function SkillsPage() {
|
|
|
106
121
|
);
|
|
107
122
|
const [error, setError] = useState<string | null>(null);
|
|
108
123
|
const [activeKind, setActiveKind] = useState<SkillKind>("project");
|
|
109
|
-
const [
|
|
124
|
+
const [scope, setScope] = useState<string>("__all__");
|
|
110
125
|
const [statusFilter, setStatusFilter] = useState<string>("CURRENT");
|
|
111
126
|
const [sortBy, setSortBy] = useState<SkillSort>("newest");
|
|
112
127
|
const [search, setSearch] = useState("");
|
|
128
|
+
const attribution = useRequestHostAttribution();
|
|
113
129
|
|
|
114
130
|
useEffect(() => {
|
|
115
131
|
let cancelled = false;
|
|
@@ -158,24 +174,26 @@ export default function SkillsPage() {
|
|
|
158
174
|
|
|
159
175
|
const activeSkills = useMemo(() => {
|
|
160
176
|
return activeKind === "project"
|
|
161
|
-
? (projectSkills ?? []).map(
|
|
177
|
+
? (projectSkills ?? []).map((playbook) =>
|
|
178
|
+
projectSkill(playbook, attribution?.hosts),
|
|
179
|
+
)
|
|
162
180
|
: (sharedSkills ?? []).map(sharedSkill);
|
|
163
|
-
}, [activeKind, projectSkills, sharedSkills]);
|
|
181
|
+
}, [activeKind, attribution, projectSkills, sharedSkills]);
|
|
164
182
|
|
|
165
|
-
const
|
|
183
|
+
const scopes = useMemo(() => {
|
|
166
184
|
const set = new Set<string>();
|
|
167
|
-
for (const p of activeSkills) set.add(p.
|
|
185
|
+
for (const p of activeSkills) set.add(p.scopeId);
|
|
168
186
|
return Array.from(set).sort();
|
|
169
187
|
}, [activeSkills]);
|
|
170
188
|
|
|
171
189
|
const filtered = useMemo(() => {
|
|
172
190
|
const matches = activeSkills.filter((p) => {
|
|
173
|
-
if (
|
|
191
|
+
if (scope !== "__all__" && p.scopeId !== scope)
|
|
174
192
|
return false;
|
|
175
193
|
if (statusFilter !== "__all__" && p.status !== statusFilter) return false;
|
|
176
194
|
if (search) {
|
|
177
195
|
const s = search.toLowerCase();
|
|
178
|
-
const hay = `${p.content} ${p.trigger ?? ""} ${p.rationale ?? ""}`.toLowerCase();
|
|
196
|
+
const hay = `${p.scopeId} ${p.content} ${p.trigger ?? ""} ${p.rationale ?? ""}`.toLowerCase();
|
|
179
197
|
if (!hay.includes(s)) return false;
|
|
180
198
|
}
|
|
181
199
|
return true;
|
|
@@ -193,7 +211,7 @@ export default function SkillsPage() {
|
|
|
193
211
|
}
|
|
194
212
|
return b.createdAt - a.createdAt;
|
|
195
213
|
});
|
|
196
|
-
}, [activeSkills,
|
|
214
|
+
}, [activeSkills, scope, search, sortBy, statsByRule, statusFilter]);
|
|
197
215
|
|
|
198
216
|
const projectCount = projectSkills?.length ?? 0;
|
|
199
217
|
const sharedCount = sharedSkills?.length ?? 0;
|
|
@@ -201,10 +219,12 @@ export default function SkillsPage() {
|
|
|
201
219
|
const activeCount = activeKind === "project" ? projectCount : sharedCount;
|
|
202
220
|
const loading = projectSkills === null || sharedSkills === null;
|
|
203
221
|
const hasNoSharedSkills = activeKind === "shared" && sharedCount === 0;
|
|
222
|
+
const allScopesLabel =
|
|
223
|
+
activeKind === "project" ? "All user scopes" : "All agents";
|
|
204
224
|
|
|
205
225
|
const switchKind = (kind: SkillKind) => {
|
|
206
226
|
setActiveKind(kind);
|
|
207
|
-
|
|
227
|
+
setScope("__all__");
|
|
208
228
|
setStatusFilter(kind === "project" ? "CURRENT" : "__all__");
|
|
209
229
|
};
|
|
210
230
|
|
|
@@ -216,15 +236,17 @@ export default function SkillsPage() {
|
|
|
216
236
|
actions={
|
|
217
237
|
<div className="flex max-w-full flex-wrap items-center justify-end gap-2">
|
|
218
238
|
<Select
|
|
219
|
-
value={
|
|
220
|
-
onValueChange={(v) =>
|
|
239
|
+
value={scope}
|
|
240
|
+
onValueChange={(v) => setScope(v ?? "__all__")}
|
|
221
241
|
>
|
|
222
242
|
<SelectTrigger size="sm" className="w-40 text-xs bg-background/80">
|
|
223
|
-
<SelectValue
|
|
243
|
+
<SelectValue>
|
|
244
|
+
{scope === "__all__" ? allScopesLabel : scope}
|
|
245
|
+
</SelectValue>
|
|
224
246
|
</SelectTrigger>
|
|
225
247
|
<SelectContent>
|
|
226
|
-
<SelectItem value="__all__">
|
|
227
|
-
{
|
|
248
|
+
<SelectItem value="__all__">{allScopesLabel}</SelectItem>
|
|
249
|
+
{scopes.map((p) => (
|
|
228
250
|
<SelectItem key={p} value={p}>
|
|
229
251
|
{p}
|
|
230
252
|
</SelectItem>
|
|
@@ -337,6 +359,13 @@ export default function SkillsPage() {
|
|
|
337
359
|
{error}. Is reflexio running on the configured backend URL?
|
|
338
360
|
</div>
|
|
339
361
|
)}
|
|
362
|
+
{activeKind === "project" && attribution?.unavailable && !error && (
|
|
363
|
+
<div className="mb-4 flex items-center gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800 dark:text-amber-200">
|
|
364
|
+
<AlertTriangle className="h-4 w-4 shrink-0" />
|
|
365
|
+
Skill source details are temporarily unavailable. Skills remain
|
|
366
|
+
available.
|
|
367
|
+
</div>
|
|
368
|
+
)}
|
|
340
369
|
|
|
341
370
|
{loading && !error ? (
|
|
342
371
|
<div className="text-sm text-muted-foreground">Loading...</div>
|
|
@@ -366,17 +395,20 @@ export default function SkillsPage() {
|
|
|
366
395
|
>
|
|
367
396
|
<header className="mb-2 flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
368
397
|
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
369
|
-
<
|
|
370
|
-
variant="outline"
|
|
371
|
-
className="h-5 max-w-56 truncate font-mono text-[10px]"
|
|
372
|
-
>
|
|
373
|
-
{p.agentVersion}
|
|
374
|
-
</Badge>
|
|
398
|
+
<LearningApplicationBadge stat={stat} />
|
|
375
399
|
<StatusBadge kind={p.kind} status={p.status} />
|
|
376
400
|
<Badge variant="secondary" className="h-5 text-[10px]">
|
|
377
401
|
{p.kind === "project" ? "project-specific" : "shared"}
|
|
378
402
|
</Badge>
|
|
379
|
-
|
|
403
|
+
{p.kind === "shared" && (
|
|
404
|
+
<Badge
|
|
405
|
+
variant="outline"
|
|
406
|
+
className="h-5 max-w-56 truncate font-mono text-[10px]"
|
|
407
|
+
title="Shared-skill applicability scope; not the source host."
|
|
408
|
+
>
|
|
409
|
+
Agent scope: {p.scopeId}
|
|
410
|
+
</Badge>
|
|
411
|
+
)}
|
|
380
412
|
</div>
|
|
381
413
|
<div className="flex shrink-0 items-center gap-1.5 pt-0.5">
|
|
382
414
|
<span className="text-[11px] text-muted-foreground">
|
|
@@ -406,6 +438,13 @@ export default function SkillsPage() {
|
|
|
406
438
|
{p.rationale}
|
|
407
439
|
</p>
|
|
408
440
|
)}
|
|
441
|
+
{p.kind === "project" &&
|
|
442
|
+
attribution &&
|
|
443
|
+
!attribution.unavailable && (
|
|
444
|
+
<div className="mt-2">
|
|
445
|
+
<HostBadge host={p.host} display="provenance" />
|
|
446
|
+
</div>
|
|
447
|
+
)}
|
|
409
448
|
</Link>
|
|
410
449
|
);
|
|
411
450
|
})}
|
|
@@ -416,30 +455,6 @@ export default function SkillsPage() {
|
|
|
416
455
|
);
|
|
417
456
|
}
|
|
418
457
|
|
|
419
|
-
function ApplicationStatBadge({ stat }: { stat: PlaybookApplicationStat | undefined }) {
|
|
420
|
-
if (!stat || stat.applied_count === 0) {
|
|
421
|
-
return (
|
|
422
|
-
<Badge
|
|
423
|
-
variant="outline"
|
|
424
|
-
className="h-5 text-[10px] text-muted-foreground"
|
|
425
|
-
title="No citations recorded yet for this rule. It will count once an assistant reply cites it."
|
|
426
|
-
>
|
|
427
|
-
Never applied
|
|
428
|
-
</Badge>
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
const last = formatRelative(stat.last_applied_at);
|
|
432
|
-
return (
|
|
433
|
-
<Badge
|
|
434
|
-
variant="secondary"
|
|
435
|
-
className="h-5 text-[10px]"
|
|
436
|
-
title={`Last applied ${last}`}
|
|
437
|
-
>
|
|
438
|
-
Applied {stat.applied_count}×{stat.last_applied_at ? ` · ${last}` : ""}
|
|
439
|
-
</Badge>
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
458
|
function statusFilterLabel(kind: SkillKind, status: string): string {
|
|
444
459
|
if (status === "__all__") return "All";
|
|
445
460
|
if (kind === "shared") {
|
|
@@ -13,11 +13,11 @@ import {
|
|
|
13
13
|
Check,
|
|
14
14
|
BookMarked,
|
|
15
15
|
Hash,
|
|
16
|
-
FolderGit2,
|
|
17
16
|
Clock,
|
|
18
17
|
FileText,
|
|
19
18
|
} from "lucide-react";
|
|
20
19
|
import { PageHeader } from "@/components/common/page-header";
|
|
20
|
+
import { LearningHostProvenance } from "@/components/common/host-badge";
|
|
21
21
|
import { EmptyState } from "@/components/common/empty-state";
|
|
22
22
|
import { DeleteLearningDangerZone } from "@/components/common/delete-learning-danger-zone";
|
|
23
23
|
import { Button } from "@/components/ui/button";
|
|
@@ -26,6 +26,7 @@ import { Badge } from "@/components/ui/badge";
|
|
|
26
26
|
import { Separator } from "@/components/ui/separator";
|
|
27
27
|
import { reflexio } from "@/lib/reflexio-client";
|
|
28
28
|
import { formatTimestamp, truncateId } from "@/lib/format";
|
|
29
|
+
import { useRequestHostAttribution } from "@/lib/host-attribution";
|
|
29
30
|
import { cn } from "@/lib/utils";
|
|
30
31
|
import { statusLabel } from "@/lib/status";
|
|
31
32
|
import type { StatusLabel } from "@/lib/status";
|
|
@@ -66,6 +67,7 @@ export default function ProjectSkillDetailPage({
|
|
|
66
67
|
trigger: "",
|
|
67
68
|
rationale: "",
|
|
68
69
|
});
|
|
70
|
+
const attribution = useRequestHostAttribution();
|
|
69
71
|
|
|
70
72
|
useEffect(() => {
|
|
71
73
|
let cancelled = false;
|
|
@@ -229,10 +231,6 @@ export default function ProjectSkillDetailPage({
|
|
|
229
231
|
|
|
230
232
|
{playbook && (
|
|
231
233
|
<div className="flex items-center gap-2 flex-wrap">
|
|
232
|
-
<Badge variant="outline" className="gap-1.5">
|
|
233
|
-
<FolderGit2 className="h-3 w-3" />
|
|
234
|
-
{playbook.agent_version || "default"}
|
|
235
|
-
</Badge>
|
|
236
234
|
<StatusBadge status={status!} />
|
|
237
235
|
{displayName(playbook.playbook_name) && (
|
|
238
236
|
<Badge variant="secondary" className="font-mono text-[10px]">
|
|
@@ -332,7 +330,7 @@ export default function ProjectSkillDetailPage({
|
|
|
332
330
|
value={formatTimestamp(playbook.created_at)}
|
|
333
331
|
/>
|
|
334
332
|
<Meta
|
|
335
|
-
label="
|
|
333
|
+
label="Agent version"
|
|
336
334
|
value={playbook.agent_version || "default"}
|
|
337
335
|
mono
|
|
338
336
|
/>
|
|
@@ -350,8 +348,19 @@ export default function ProjectSkillDetailPage({
|
|
|
350
348
|
display={truncateId(playbook.request_id, 8, 4)}
|
|
351
349
|
/>
|
|
352
350
|
)}
|
|
351
|
+
{attribution && playbook.request_id && (
|
|
352
|
+
<Meta
|
|
353
|
+
label="Origin"
|
|
354
|
+
value={
|
|
355
|
+
<LearningHostProvenance
|
|
356
|
+
attribution={attribution}
|
|
357
|
+
requestId={playbook.request_id}
|
|
358
|
+
/>
|
|
359
|
+
}
|
|
360
|
+
/>
|
|
361
|
+
)}
|
|
353
362
|
{playbook.source && (
|
|
354
|
-
<Meta label="
|
|
363
|
+
<Meta label="Integration" value={playbook.source} mono />
|
|
355
364
|
)}
|
|
356
365
|
</dl>
|
|
357
366
|
</div>
|
|
@@ -499,7 +508,7 @@ function Meta({
|
|
|
499
508
|
}: {
|
|
500
509
|
icon?: React.ComponentType<{ className?: string }>;
|
|
501
510
|
label: string;
|
|
502
|
-
value:
|
|
511
|
+
value: React.ReactNode;
|
|
503
512
|
mono?: boolean;
|
|
504
513
|
}) {
|
|
505
514
|
return (
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
Check,
|
|
14
14
|
BookMarked,
|
|
15
15
|
Hash,
|
|
16
|
-
FolderGit2,
|
|
17
16
|
Clock,
|
|
18
17
|
FileText,
|
|
19
18
|
} from "lucide-react";
|
|
@@ -298,10 +297,6 @@ export default function SharedSkillDetailPage({
|
|
|
298
297
|
|
|
299
298
|
{playbook && (
|
|
300
299
|
<div className="flex items-center gap-2 flex-wrap">
|
|
301
|
-
<Badge variant="outline" className="gap-1.5">
|
|
302
|
-
<FolderGit2 className="h-3 w-3" />
|
|
303
|
-
{playbook.agent_version || "default"}
|
|
304
|
-
</Badge>
|
|
305
300
|
{editing ? (
|
|
306
301
|
<ReviewStatusBadge
|
|
307
302
|
status={playbook.playbook_status}
|
|
@@ -472,7 +467,7 @@ export default function SharedSkillDetailPage({
|
|
|
472
467
|
value={formatTimestamp(playbook.created_at)}
|
|
473
468
|
/>
|
|
474
469
|
<Meta
|
|
475
|
-
label="
|
|
470
|
+
label="Agent version"
|
|
476
471
|
value={playbook.agent_version || "default"}
|
|
477
472
|
mono
|
|
478
473
|
/>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Terminal } from "lucide-react";
|
|
2
|
+
import { Badge } from "@/components/ui/badge";
|
|
3
|
+
import type { RequestHostAttribution } from "@/lib/host-attribution";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
import type { Host } from "@/lib/types";
|
|
6
|
+
|
|
7
|
+
// Exact product colors from the current official app assets. Label colors meet
|
|
8
|
+
// WCAG AA for the badge's small text: Claude 5.65:1, Codex 5.39:1, OpenCode 18.93:1.
|
|
9
|
+
const HOST_META: Record<
|
|
10
|
+
Exclude<Host, "unknown">,
|
|
11
|
+
{ label: string; badgeClass: string; dotClass: string }
|
|
12
|
+
> = {
|
|
13
|
+
"claude-code": {
|
|
14
|
+
label: "Claude Code",
|
|
15
|
+
badgeClass: "border-[#D97757] bg-[#D97757] text-[#2A120B]",
|
|
16
|
+
dotClass: "bg-[#D97757]",
|
|
17
|
+
},
|
|
18
|
+
codex: {
|
|
19
|
+
label: "Codex",
|
|
20
|
+
badgeClass: "border-[#0169CC] bg-[#0169CC] text-white",
|
|
21
|
+
dotClass: "bg-[#0169CC]",
|
|
22
|
+
},
|
|
23
|
+
opencode: {
|
|
24
|
+
label: "OpenCode",
|
|
25
|
+
badgeClass:
|
|
26
|
+
"border-[#131010] bg-[#131010] text-white dark:border-white/30",
|
|
27
|
+
dotClass: "bg-[#131010] dark:bg-white",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function HostBadge({
|
|
32
|
+
host,
|
|
33
|
+
display = "badge",
|
|
34
|
+
size = "md",
|
|
35
|
+
className,
|
|
36
|
+
}: {
|
|
37
|
+
host: Host | null;
|
|
38
|
+
display?: "badge" | "provenance";
|
|
39
|
+
size?: "md" | "sm";
|
|
40
|
+
className?: string;
|
|
41
|
+
}) {
|
|
42
|
+
const knownHost = host && host !== "unknown" ? host : null;
|
|
43
|
+
const meta = knownHost ? HOST_META[knownHost] : null;
|
|
44
|
+
const label = meta?.label ?? "unknown host";
|
|
45
|
+
|
|
46
|
+
if (display === "provenance") {
|
|
47
|
+
return (
|
|
48
|
+
<span
|
|
49
|
+
className={cn(
|
|
50
|
+
"inline-flex items-center gap-1 text-[11px] text-muted-foreground",
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
title={
|
|
54
|
+
knownHost
|
|
55
|
+
? `This learning was generated from a ${label} session; it is not limited to that host.`
|
|
56
|
+
: "The session host for this learning's generation request was not recorded."
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
<span>Generated via</span>
|
|
60
|
+
<span
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
className={cn(
|
|
63
|
+
"h-1.5 w-1.5 shrink-0 rounded-full",
|
|
64
|
+
meta
|
|
65
|
+
? meta.dotClass
|
|
66
|
+
: "border border-dashed border-muted-foreground/70",
|
|
67
|
+
)}
|
|
68
|
+
/>
|
|
69
|
+
<span className="font-medium text-foreground/75">{label}</span>
|
|
70
|
+
</span>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const compact = size === "sm";
|
|
75
|
+
const sizeClass = compact ? "h-4 px-1.5 text-[10px]" : "h-5";
|
|
76
|
+
const badgeLabel = knownHost ? label : "Host unknown";
|
|
77
|
+
return (
|
|
78
|
+
<Badge
|
|
79
|
+
variant="outline"
|
|
80
|
+
className={cn(
|
|
81
|
+
"gap-1",
|
|
82
|
+
meta
|
|
83
|
+
? meta.badgeClass
|
|
84
|
+
: "border-dashed text-muted-foreground",
|
|
85
|
+
sizeClass,
|
|
86
|
+
className,
|
|
87
|
+
)}
|
|
88
|
+
title={knownHost ? `Session host: ${label}` : "Session host was not recorded"}
|
|
89
|
+
>
|
|
90
|
+
<Terminal
|
|
91
|
+
className={cn(
|
|
92
|
+
knownHost ? "text-current" : "text-muted-foreground",
|
|
93
|
+
compact ? "h-2.5 w-2.5" : "h-3 w-3",
|
|
94
|
+
)}
|
|
95
|
+
/>
|
|
96
|
+
{badgeLabel}
|
|
97
|
+
</Badge>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function LearningHostProvenance({
|
|
102
|
+
attribution,
|
|
103
|
+
requestId,
|
|
104
|
+
}: {
|
|
105
|
+
attribution: RequestHostAttribution;
|
|
106
|
+
requestId: string;
|
|
107
|
+
}) {
|
|
108
|
+
if (attribution.unavailable) {
|
|
109
|
+
return <span className="text-muted-foreground">Unavailable</span>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<HostBadge
|
|
114
|
+
host={attribution.hosts.get(requestId) ?? null}
|
|
115
|
+
display="provenance"
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Sparkles } from "lucide-react";
|
|
2
|
+
import { Badge } from "@/components/ui/badge";
|
|
3
|
+
import { formatRelative } from "@/lib/format";
|
|
4
|
+
import type { PlaybookApplicationStat } from "@/lib/types";
|
|
5
|
+
|
|
6
|
+
export function LearningApplicationBadge({
|
|
7
|
+
stat,
|
|
8
|
+
}: {
|
|
9
|
+
stat: PlaybookApplicationStat | undefined;
|
|
10
|
+
}) {
|
|
11
|
+
if (!stat || stat.applied_count === 0) {
|
|
12
|
+
return (
|
|
13
|
+
<Badge
|
|
14
|
+
variant="outline"
|
|
15
|
+
className="h-5 text-[10px] text-muted-foreground"
|
|
16
|
+
title="No citations recorded yet for this learning. It will count once an assistant reply cites it."
|
|
17
|
+
>
|
|
18
|
+
Never applied
|
|
19
|
+
</Badge>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const last = formatRelative(stat.last_applied_at);
|
|
24
|
+
return (
|
|
25
|
+
<Badge
|
|
26
|
+
variant="outline"
|
|
27
|
+
className="h-5 gap-1 border-amber-500/45 bg-amber-500/10 text-[10px] text-amber-700 dark:text-amber-300"
|
|
28
|
+
title={`Last applied ${last}`}
|
|
29
|
+
>
|
|
30
|
+
<Sparkles className="h-2.5 w-2.5 text-amber-500" />
|
|
31
|
+
Applied {stat.applied_count}×{stat.last_applied_at ? ` · ${last}` : ""}
|
|
32
|
+
</Badge>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -7,7 +7,7 @@ export function PageHeader({
|
|
|
7
7
|
className,
|
|
8
8
|
}: {
|
|
9
9
|
title: string;
|
|
10
|
-
description?:
|
|
10
|
+
description?: React.ReactNode;
|
|
11
11
|
actions?: React.ReactNode;
|
|
12
12
|
className?: string;
|
|
13
13
|
}) {
|
|
@@ -21,9 +21,9 @@ export function PageHeader({
|
|
|
21
21
|
<div className="min-w-[min(18rem,100%)] flex-1">
|
|
22
22
|
<h1 className="text-2xl font-semibold">{title}</h1>
|
|
23
23
|
{description && (
|
|
24
|
-
<
|
|
24
|
+
<div className="text-sm text-muted-foreground mt-1 max-w-3xl">
|
|
25
25
|
{description}
|
|
26
|
-
</
|
|
26
|
+
</div>
|
|
27
27
|
)}
|
|
28
28
|
</div>
|
|
29
29
|
{actions && (
|
|
@@ -27,6 +27,10 @@ const BOOL_KEYS = new Set([
|
|
|
27
27
|
"CLAUDE_SMART_READ_ONLY",
|
|
28
28
|
]);
|
|
29
29
|
|
|
30
|
+
function defaultReflexioUrl(): string {
|
|
31
|
+
return `http://localhost:${process.env.BACKEND_PORT || "8071"}/`;
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
function envPath(): string {
|
|
31
35
|
return path.join(os.homedir(), ".reflexio", ".env");
|
|
32
36
|
}
|
|
@@ -49,7 +53,7 @@ function parseLine(line: string): { key: string; value: string } | null {
|
|
|
49
53
|
|
|
50
54
|
export async function readConfig(): Promise<ClaudeSmartConfig> {
|
|
51
55
|
const defaults: ClaudeSmartConfig = {
|
|
52
|
-
REFLEXIO_URL:
|
|
56
|
+
REFLEXIO_URL: defaultReflexioUrl(),
|
|
53
57
|
REFLEXIO_API_KEY: "",
|
|
54
58
|
CLAUDE_SMART_USE_LOCAL_CLI: false,
|
|
55
59
|
CLAUDE_SMART_USE_LOCAL_EMBEDDING: false,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import type { Host, SessionSummary } from "./types";
|
|
3
|
+
|
|
4
|
+
export interface RequestHostAttribution {
|
|
5
|
+
hosts: ReadonlyMap<string, Host | null>;
|
|
6
|
+
unavailable: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function hostsByRequestId(
|
|
10
|
+
sessions: SessionSummary[],
|
|
11
|
+
): Map<string, Host | null> {
|
|
12
|
+
const result = new Map<string, Host | null>();
|
|
13
|
+
for (const session of sessions) {
|
|
14
|
+
for (const requestId of session.request_ids) {
|
|
15
|
+
result.set(requestId, session.host);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useRequestHostAttribution(): RequestHostAttribution | null {
|
|
22
|
+
const [attribution, setAttribution] =
|
|
23
|
+
useState<RequestHostAttribution | null>(null);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
let cancelled = false;
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timeout = setTimeout(() => controller.abort(), 5_000);
|
|
29
|
+
|
|
30
|
+
fetch("/api/sessions", {
|
|
31
|
+
cache: "no-store",
|
|
32
|
+
signal: controller.signal,
|
|
33
|
+
})
|
|
34
|
+
.then(async (response) => {
|
|
35
|
+
if (!response.ok) throw new Error(`sessions ${response.status}`);
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
return (data.sessions ?? []) as SessionSummary[];
|
|
38
|
+
})
|
|
39
|
+
.then((sessions) => {
|
|
40
|
+
if (!cancelled) {
|
|
41
|
+
setAttribution({
|
|
42
|
+
hosts: hostsByRequestId(sessions),
|
|
43
|
+
unavailable: false,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.catch(() => {
|
|
48
|
+
if (!cancelled) {
|
|
49
|
+
setAttribution({ hosts: new Map(), unavailable: true });
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.finally(() => clearTimeout(timeout));
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
cancelled = true;
|
|
56
|
+
controller.abort();
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
};
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
return attribution;
|
|
62
|
+
}
|