Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 85 additions & 0 deletions autobot-frontend/src/components/autoresearch/ApprovalCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<!-- AutoBot - AI-Powered Automation Platform -->
<!-- Copyright (c) 2025 mrveiss -->

<script setup lang="ts">
import { ref } from 'vue'

interface ApprovalDetails {
sessionId: string
experimentId: string
topic?: string
iteration?: number
metrics?: {
baseline_val_bpb?: number
result_val_bpb?: number
improvement?: number
improvement_pct?: number
}
}

const props = defineProps<{
approval: ApprovalDetails
}>()

const emit = defineEmits<{
approve: [sessionId: string, experimentId: string]
reject: [sessionId: string, experimentId: string]
}>()

const deciding = ref(false)

async function handleApprove() {
deciding.value = true
emit('approve', props.approval.sessionId, props.approval.experimentId)
}

async function handleReject() {
deciding.value = true
emit('reject', props.approval.sessionId, props.approval.experimentId)
}
</script>

<template>
<div class="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-950">
<div class="mb-2 flex items-center gap-2">
<span class="inline-block h-2 w-2 rounded-full bg-amber-500"></span>
<span class="text-sm font-medium text-amber-800 dark:text-amber-200">
Approval Required
</span>
</div>

<div v-if="approval.metrics" class="mb-3 grid grid-cols-2 gap-2 text-sm">
<div>
<span class="text-neutral-500">Baseline val_bpb:</span>
<span class="ml-1 font-mono">{{ approval.metrics.baseline_val_bpb?.toFixed(4) ?? '---' }}</span>
</div>
<div>
<span class="text-neutral-500">Result val_bpb:</span>
<span class="ml-1 font-mono">{{ approval.metrics.result_val_bpb?.toFixed(4) ?? '---' }}</span>
</div>
<div v-if="approval.metrics.improvement_pct != null" class="col-span-2">
<span class="text-neutral-500">Improvement:</span>
<span class="ml-1 font-mono text-green-600 dark:text-green-400">
{{ approval.metrics.improvement_pct.toFixed(2) }}%
</span>
</div>
</div>

<div class="flex gap-2">
<button
:disabled="deciding"
class="rounded-md bg-green-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-green-700 disabled:opacity-50"
@click="handleApprove"
>
Approve
</button>
<button
:disabled="deciding"
class="rounded-md bg-red-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-50"
@click="handleReject"
>
Reject
</button>
</div>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!-- AutoBot - AI-Powered Automation Platform -->
<!-- Copyright (c) 2025 mrveiss -->

<script setup lang="ts">
import { computed } from 'vue'
import type { Experiment } from '@/composables/useAutoResearch'

const props = defineProps<{
experiment: Experiment
}>()

const stateLabel: Record<string, string> = {
pending: 'Pending',
running: 'Running',
completed: 'Completed',
failed: 'Failed',
kept: 'Kept',
discarded: 'Discarded',
}

const stateBadgeClass = computed(() => {
const classes: Record<string, string> = {
pending: 'bg-neutral-100 text-neutral-600',
running: 'bg-blue-100 text-blue-700',
completed: 'bg-green-100 text-green-700',
failed: 'bg-red-100 text-red-700',
kept: 'bg-emerald-100 text-emerald-700',
discarded: 'bg-orange-100 text-orange-700',
}
return classes[props.experiment.state] ?? 'bg-neutral-100 text-neutral-600'
})
</script>

<template>
<div class="flex items-center gap-3 rounded-md border p-3 text-sm dark:border-neutral-700">
<span
:class="stateBadgeClass"
class="rounded-full px-2 py-0.5 text-xs font-medium"
>
{{ stateLabel[experiment.state] ?? experiment.state }}
</span>

<span class="flex-1 truncate text-neutral-700 dark:text-neutral-300">
{{ experiment.hypothesis || 'AutoResearch experiment' }}
</span>

<span
v-if="experiment.result?.val_bpb != null"
class="font-mono text-xs text-neutral-500"
>
{{ experiment.result.val_bpb.toFixed(4) }}
</span>

<router-link
to="/experiments"
class="text-xs text-blue-600 hover:underline dark:text-blue-400"
>
Details
</router-link>
</div>
</template>
102 changes: 102 additions & 0 deletions autobot-frontend/src/components/autoresearch/ExperimentTimeline.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<!-- AutoBot - AI-Powered Automation Platform -->
<!-- Copyright (c) 2025 mrveiss -->

<script setup lang="ts">
import { computed } from 'vue'
import type { Experiment } from '@/composables/useAutoResearch'
import ApprovalCard from './ApprovalCard.vue'

const props = defineProps<{
experiments: Experiment[]
pendingApprovals: Array<{
session_id: string
experiment_id: string
details: Record<string, unknown>
}>
}>()

const emit = defineEmits<{
approve: [sessionId: string, experimentId: string]
reject: [sessionId: string, experimentId: string]
}>()

const stateColors: Record<string, string> = {
pending: 'bg-neutral-400',
running: 'bg-blue-500',
completed: 'bg-green-500',
failed: 'bg-red-500',
kept: 'bg-emerald-600',
discarded: 'bg-orange-500',
}

const sortedExperiments = computed(() =>
[...props.experiments].sort((a, b) => b.created_at - a.created_at),
)

function formatTime(timestamp: number): string {
return new Date(timestamp * 1000).toLocaleString()
}

function getApproval(experimentId: string) {
return props.pendingApprovals.find((a) => a.experiment_id === experimentId)
}
</script>

<template>
<div class="space-y-3">
<div v-if="sortedExperiments.length === 0" class="py-8 text-center text-neutral-500">
No experiments yet
</div>

<div
v-for="exp in sortedExperiments"
:key="exp.id"
class="rounded-lg border border-neutral-200 p-4 dark:border-neutral-700"
>
<div class="mb-2 flex items-center justify-between">
<div class="flex items-center gap-2">
<span
:class="stateColors[exp.state] ?? 'bg-neutral-400'"
class="inline-block h-2 w-2 rounded-full"
></span>
<span class="text-sm font-medium capitalize">{{ exp.state }}</span>
</div>
<span class="text-xs text-neutral-500">{{ formatTime(exp.created_at) }}</span>
</div>

<p class="mb-2 text-sm text-neutral-700 dark:text-neutral-300">
{{ exp.hypothesis || 'No hypothesis' }}
</p>

<div v-if="exp.result" class="flex gap-4 text-xs text-neutral-500">
<span v-if="exp.result.val_bpb != null">
val_bpb: <span class="font-mono">{{ exp.result.val_bpb.toFixed(4) }}</span>
</span>
<span v-if="exp.result.wall_time_seconds > 0">
{{ exp.result.wall_time_seconds.toFixed(0) }}s
</span>
<span v-if="exp.result.tokens_per_second != null">
{{ exp.result.tokens_per_second.toFixed(0) }} tok/s
</span>
</div>

<!-- Inline approval card -->
<ApprovalCard
v-if="getApproval(exp.id)"
:approval="{
sessionId: getApproval(exp.id)!.session_id,
experimentId: exp.id,
metrics: exp.result
? {
baseline_val_bpb: exp.baseline_val_bpb ?? undefined,
result_val_bpb: exp.result.val_bpb ?? undefined,
}
: undefined,
}"
class="mt-3"
@approve="(sid: string, eid: string) => emit('approve', sid, eid)"
@reject="(sid: string, eid: string) => emit('reject', sid, eid)"
/>
</div>
</div>
</template>
79 changes: 79 additions & 0 deletions autobot-frontend/src/components/autoresearch/InsightsPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!-- AutoBot - AI-Powered Automation Platform -->
<!-- Copyright (c) 2025 mrveiss -->

<script setup lang="ts">
import { ref } from 'vue'
import type { ExperimentInsight } from '@/composables/useAutoResearch'

defineProps<{
insights: ExperimentInsight[]
}>()

const emit = defineEmits<{
search: [query: string]
}>()

const searchQuery = ref('')

function handleSearch() {
if (searchQuery.value.trim()) {
emit('search', searchQuery.value.trim())
}
}

function confidenceColor(confidence: number): string {
if (confidence >= 0.8) return 'text-green-600 dark:text-green-400'
if (confidence >= 0.5) return 'text-amber-600 dark:text-amber-400'
return 'text-red-600 dark:text-red-400'
}
</script>

<template>
<div>
<h3 class="mb-3 text-lg font-semibold">Experiment Insights</h3>

<!-- Search -->
<div class="mb-4 flex gap-2">
<input
v-model="searchQuery"
placeholder="Search insights..."
class="flex-1 rounded-md border px-3 py-1.5 text-sm dark:border-neutral-600 dark:bg-neutral-800"
@keyup.enter="handleSearch"
/>
<button
class="rounded-md bg-blue-600 px-4 py-1.5 text-sm font-medium text-white hover:bg-blue-700"
@click="handleSearch"
>
Search
</button>
</div>

<!-- Insights list -->
<div v-if="insights.length === 0" class="py-4 text-center text-sm text-neutral-500">
No insights yet. Run experiments and trigger synthesis.
</div>

<div v-else class="space-y-2">
<div
v-for="insight in insights"
:key="insight.id"
class="rounded-md border p-3 dark:border-neutral-700"
>
<div class="mb-1 flex items-center justify-between">
<span :class="confidenceColor(insight.confidence)" class="text-xs font-medium">
{{ (insight.confidence * 100).toFixed(0) }}% confidence
</span>
<span class="text-xs text-neutral-500">
{{ insight.related_hyperparams.join(', ') }}
</span>
</div>
<p class="text-sm text-neutral-700 dark:text-neutral-300">
{{ insight.statement }}
</p>
<div class="mt-1 text-xs text-neutral-400">
Based on {{ insight.supporting_experiments.length }} experiment(s)
</div>
</div>
</div>
</div>
</template>
Loading
Loading