7 Commits

Author SHA1 Message Date
AI Agent
ab88c56320 fix: resolve #8 - сделай новую страницу
All checks were successful
Bun CI/CD / test (pull_request) Successful in 9s
Bun CI/CD / deploy-staging (pull_request) Has been skipped
Bun CI/CD / deploy-prod (pull_request) Has been skipped
2026-01-03 19:14:33 +00:00
51a80ccb6f fix
All checks were successful
Bun CI/CD / test (push) Successful in 9s
Bun CI/CD / deploy-staging (push) Has been skipped
Bun CI/CD / deploy-prod (push) Successful in 1s
2026-01-03 21:15:45 +03:00
ef0a082d18 message
All checks were successful
Bun CI/CD / test (push) Successful in 9s
Bun CI/CD / deploy-staging (push) Has been skipped
Bun CI/CD / deploy-prod (push) Successful in 1s
2026-01-03 21:12:18 +03:00
e343d27001 chore: another shot
All checks were successful
Bun CI/CD / test (push) Successful in 8s
Bun CI/CD / deploy-staging (push) Has been skipped
Bun CI/CD / deploy-prod (push) Successful in 1s
2026-01-03 21:07:07 +03:00
4912f9a9b1 chore: deepsek ftw
All checks were successful
Bun CI/CD / test (push) Successful in 9s
Bun CI/CD / deploy-staging (push) Has been skipped
Bun CI/CD / deploy-prod (push) Successful in 1s
2026-01-03 21:05:54 +03:00
d47522543d chore: fix solver
All checks were successful
Bun CI/CD / test (push) Successful in 8s
Bun CI/CD / deploy-staging (push) Has been skipped
Bun CI/CD / deploy-prod (push) Successful in 1s
2026-01-03 21:03:27 +03:00
dff87b6c7a chore: update solver
All checks were successful
Bun CI/CD / test (push) Successful in 18s
Bun CI/CD / deploy-staging (push) Has been skipped
Bun CI/CD / deploy-prod (push) Successful in 0s
2026-01-03 20:58:20 +03:00
8 changed files with 6599 additions and 54 deletions

View File

@@ -2,19 +2,106 @@
name: AI Issue Solver
on:
# Ручной запуск
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to solve'
required: true
type: number
# Автоматический запуск по метке
issues:
types: [labeled]
jobs:
solve:
if: github.event.label.name == 'ai-solve'
# Запускать при ручном триггере ИЛИ при добавлении метки ai-solve
if: |
(gitea.event_name == 'workflow_dispatch') ||
(gitea.event_name == 'issues' && contains(gitea.event.labels.*.name, 'ai-solve'))
runs-on: opencode
steps:
- name: Debug environment
run: |
echo "=== Debug Info ==="
echo "Gitea context:"
echo "event_name: ${{ gitea.event_name }}"
echo "repository: ${{ gitea.repository }}"
echo "repository_owner: ${{ gitea.repository_owner }}"
echo "event.issue: ${{ toJSON(gitea.event.issue) }}"
echo "event.inputs: ${{ toJSON(gitea.event.inputs) }}"
echo "event.labels: ${{ toJSON(gitea.event.labels) }}"
echo "=== End Debug ==="
- name: Setup issue data
id: setup
run: |
echo "Determining issue number based on event type..."
if [[ "${{ gitea.event_name }}" == "workflow_dispatch" ]]; then
# Ручной запуск - берем из inputs
ISSUE_NUMBER="${{ gitea.event.inputs.issue_number }}"
echo "Manual run - Issue number: $ISSUE_NUMBER"
elif [[ "${{ gitea.event_name }}" == "issues" ]]; then
# Автоматический запуск - берем из события issue
ISSUE_NUMBER="${{ gitea.event.issue.number }}"
echo "Auto run - Issue number: $ISSUE_NUMBER"
else
echo "Unknown event type: ${{ gitea.event_name }}"
exit 1
fi
echo "issue_number=$ISSUE_NUMBER" >> $GITHUB_OUTPUT
echo "event_type=${{ gitea.event_name }}" >> $GITHUB_OUTPUT
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GGITEA_TOKEN }}
- name: Solve Issue
env:
GITEA_URL: ${{ secrets.GITEA_URL }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: solve-issue
GGITEA_URL: ${{ secrets.GGITEA_URL }}
GGITEA_TOKEN: ${{ secrets.GGITEA_TOKEN }}
REPO_OWNER: ${{ gitea.repository_owner }}
REPO_NAME: ${{ gitea.repository }}
ISSUE_NUMBER: ${{ steps.setup.outputs.issue_number }}
EVENT_TYPE: ${{ steps.setup.outputs.event_type }}
run: |
echo "=== Starting AI Issue Solver ==="
echo "Event type: $EVENT_TYPE"
echo "Repository: $REPO_OWNER/$REPO_NAME"
echo "Issue number: $ISSUE_NUMBER"
# Проверяем наличие команды solve-issue
if ! command -v solve-issue >/dev/null 2>&1; then
echo "❌ ERROR: solve-issue command not found in PATH!"
echo "Current PATH: $PATH"
echo "Trying to find solve-issue..."
find / -name "solve-issue" -type f 2>/dev/null | head -5
exit 1
fi
echo "✅ solve-issue found at: $(which solve-issue)"
echo "📋 Command info:"
solve-issue --help || echo "No help available"
echo "🚀 Executing solve-issue..."
# Запускаем с таймаутом на случай зависания
timeout 600 solve-issue
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo "✅ solve-issue completed successfully"
elif [ $EXIT_CODE -eq 124 ]; then
echo "⏰ solve-issue timed out after 10 minutes"
exit 1
else
echo "❌ solve-issue failed with exit code: $EXIT_CODE"
exit $EXIT_CODE
fi
echo "=== AI Issue Solver finished ==="

View File

@@ -0,0 +1,54 @@
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { useAuth } from '../contexts/AuthContext'
export default function Navigation() {
const { isAuthenticated, logout } = useAuth()
const pathname = usePathname()
if (pathname === '/login' || pathname === '/dashboard') {
return null
}
return (
<nav className="bg-white shadow-sm dark:bg-black">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex items-center">
<Link href="/" className="text-xl font-bold text-black dark:text-zinc-50">
Agent Manager
</Link>
</div>
<div className="flex items-center space-x-4">
{isAuthenticated ? (
<div className="flex items-center space-x-4">
<Link
href="/dashboard"
className="rounded-md bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600"
>
Dashboard
</Link>
<button
onClick={logout}
className="rounded-md bg-red-500 px-4 py-2 text-white transition-colors hover:bg-red-600"
>
Logout
</button>
</div>
) : (
<Link
href="/login"
className="rounded-md bg-green-500 px-4 py-2 text-white transition-colors hover:bg-green-600"
>
Login
</Link>
)}
</div>
</div>
</div>
</nav>
)
}

View File

@@ -0,0 +1,57 @@
'use client'
import React, { createContext, useContext, useState } from 'react'
interface User {
username: string
}
interface AuthContextType {
user: User | null
login: (username: string, password: string) => boolean
logout: () => void
isAuthenticated: boolean
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(() => {
if (typeof window !== 'undefined') {
const savedUser = localStorage.getItem('user')
return savedUser ? JSON.parse(savedUser) : null
}
return null
})
const login = (username: string, password: string): boolean => {
if (username === 'admin' && password === 'Mallory122907Fucku2u2') {
const userData = { username }
setUser(userData)
localStorage.setItem('user', JSON.stringify(userData))
return true
}
return false
}
const logout = () => {
setUser(null)
localStorage.removeItem('user')
}
const isAuthenticated = !!user
return (
<AuthContext.Provider value={{ user, login, logout, isAuthenticated }}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}

185
app/dashboard/page.tsx Normal file
View File

@@ -0,0 +1,185 @@
'use client'
import { useState } from 'react'
import { useAuth } from './contexts/AuthContext'
interface Agent {
id: string
name: string
status: 'active' | 'inactive' | 'error'
lastRun: string
dataCollected: number
}
export default function DashboardPage() {
const { user, logout } = useAuth()
const [agents, setAgents] = useState<Agent[]>([
{
id: '1',
name: 'News Collector',
status: 'active',
lastRun: '2024-01-03 10:30:00',
dataCollected: 1250
},
{
id: '2',
name: 'Social Media Monitor',
status: 'inactive',
lastRun: '2024-01-02 15:45:00',
dataCollected: 890
},
{
id: '3',
name: 'Market Data Scraper',
status: 'error',
lastRun: '2024-01-01 09:00:00',
dataCollected: 567
}
])
const toggleAgentStatus = (id: string) => {
setAgents(prev => prev.map(agent => {
if (agent.id === id) {
const newStatus = agent.status === 'active' ? 'inactive' : 'active'
return { ...agent, status: newStatus }
}
return agent
}))
}
const getStatusColor = (status: Agent['status']) => {
switch (status) {
case 'active': return 'text-green-500'
case 'inactive': return 'text-gray-500'
case 'error': return 'text-red-500'
}
}
const getStatusBg = (status: Agent['status']) => {
switch (status) {
case 'active': return 'bg-green-100 dark:bg-green-900'
case 'inactive': return 'bg-gray-100 dark:bg-gray-900'
case 'error': return 'bg-red-100 dark:bg-red-900'
}
}
return (
<div className="min-h-screen bg-zinc-50 font-sans dark:bg-black">
<header className="bg-white shadow-sm dark:bg-black">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-4">
<h1 className="text-2xl font-bold text-black dark:text-zinc-50">
Agent Dashboard
</h1>
<div className="flex items-center space-x-4">
<span className="text-black dark:text-zinc-50">
Welcome, {user?.username}
</span>
<button
onClick={logout}
className="rounded-md bg-red-500 px-4 py-2 text-white transition-colors hover:bg-red-600"
>
Logout
</button>
</div>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="mb-8">
<h2 className="text-xl font-semibold text-black dark:text-zinc-50 mb-4">
Information Collection Agents
</h2>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{agents.map((agent) => (
<div
key={agent.id}
className="rounded-lg bg-white p-6 shadow-md dark:bg-black"
>
<div className="flex justify-between items-start mb-4">
<h3 className="text-lg font-medium text-black dark:text-zinc-50">
{agent.name}
</h3>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusBg(agent.status)} ${getStatusColor(agent.status)}`}>
{agent.status}
</span>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Last Run:</span>
<span className="text-black dark:text-zinc-50">{agent.lastRun}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Data Collected:</span>
<span className="text-black dark:text-zinc-50">{agent.dataCollected}</span>
</div>
</div>
<div className="mt-4 flex space-x-2">
<button
onClick={() => toggleAgentStatus(agent.id)}
className={`flex-1 rounded-md px-3 py-2 text-sm font-medium transition-colors ${
agent.status === 'active'
? 'bg-red-500 text-white hover:bg-red-600'
: 'bg-green-500 text-white hover:bg-green-600'
}`}
>
{agent.status === 'active' ? 'Stop' : 'Start'}
</button>
<button className="flex-1 rounded-md bg-blue-500 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-600">
Configure
</button>
</div>
</div>
))}
</div>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="rounded-lg bg-white p-6 shadow-md dark:bg-black">
<h3 className="text-lg font-medium text-black dark:text-zinc-50 mb-4">
Quick Actions
</h3>
<div className="space-y-3">
<button className="w-full rounded-md bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600">
Add New Agent
</button>
<button className="w-full rounded-md bg-green-500 px-4 py-2 text-white transition-colors hover:bg-green-600">
Run All Agents
</button>
<button className="w-full rounded-md bg-gray-500 px-4 py-2 text-white transition-colors hover:bg-gray-600">
Export Data
</button>
</div>
</div>
<div className="rounded-lg bg-white p-6 shadow-md dark:bg-black">
<h3 className="text-lg font-medium text-black dark:text-zinc-50 mb-4">
System Overview
</h3>
<div className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Total Agents:</span>
<span className="text-black dark:text-zinc-50">{agents.length}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Active Agents:</span>
<span className="text-green-500">
{agents.filter(a => a.status === 'active').length}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Total Data Points:</span>
<span className="text-black dark:text-zinc-50">
{agents.reduce((sum, a) => sum + a.dataCollected, 0)}
</span>
</div>
</div>
</div>
</div>
</main>
</div>
)
}

View File

@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { AuthProvider } from "./contexts/AuthContext";
import Navigation from "./components/Navigation";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -27,7 +29,10 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<AuthProvider>
<Navigation />
{children}
</AuthProvider>
</body>
</html>
);

69
app/login/page.tsx Normal file
View File

@@ -0,0 +1,69 @@
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { useAuth } from './contexts/AuthContext'
export default function LoginPage() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const { login } = useAuth()
const router = useRouter()
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
setError('')
if (login(username, password)) {
router.push('/dashboard')
} else {
setError('Invalid credentials')
}
}
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow-lg dark:bg-black">
<h2 className="mb-6 text-center text-2xl font-bold text-black dark:text-zinc-50">
Login
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="mb-2 block text-sm font-medium text-black dark:text-zinc-50">
Username
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full rounded-md border border-gray-300 px-3 py-2 text-black focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-zinc-50"
required
/>
</div>
<div>
<label className="mb-2 block text-sm font-medium text-black dark:text-zinc-50">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-md border border-gray-300 px-3 py-2 text-black focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-zinc-50"
required
/>
</div>
{error && (
<p className="text-center text-sm text-red-500">{error}</p>
)}
<button
type="submit"
className="w-full rounded-md bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600"
>
Login
</button>
</form>
</div>
</div>
)
}

View File

@@ -1,65 +1,40 @@
import Image from "next/image";
'use client'
import { useAuth } from './contexts/AuthContext'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
export default function Home() {
const { isAuthenticated } = useAuth()
const router = useRouter()
useEffect(() => {
if (isAuthenticated) {
router.push('/dashboard')
}
}, [isAuthenticated, router])
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
Agent Management System
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
Manage your information collection agents with our powerful dashboard.
Please login to access the agent management features.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-blue-500 px-5 text-white transition-colors hover:bg-blue-600 md:w-[158px]"
href="/login"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
Login to Dashboard
</a>
</div>
</main>
</div>
);
)
}

6113
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff