話題のプライベートなローカルManus代替AgenticSeekをWindowsで試してみる

## 結論
日本語記事が存在しなかったので人柱として記録
そこまで使えるような気がしないが、もしかしたら使えるかもしれない
大人しくCurorに20$課金
有志が今後色々試す or アプデに期待
賢さ(感覚):chatgpt thinking(free) >= claude-3.7 sonet thinking(Cursor) >>>> deepseek-r1:14b(AgenticSeek) > deepseek-r1:32b(AgenticSeek)
14bの方がなんか良い気がする(3090環境)

---

## 動作イメージ

http://localhost:3000/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284824/42b601eb-b1cc-458f-9d22-bc2f0775dd7b.png)

※当たり前だが普通にググったほうが早い

---

python -m uvicorn api:api --reload --host 0.0.0.0 --port 8000
```powershell
(agentic_seek_env) PS D:\Project\agenticSeek\agenticSeek> 
(agentic_seek_env) PS D:\Project\agenticSeek\agenticSeek> python -m uvicorn api:api --reload --host 0.0.0.0 --port 8000
INFO:     Will watch for changes in these directories: ['D:\\Project\\agenticSeek\\agenticSeek']
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [16692] using StatReload

DevTools listening on ws://127.0.0.1:53719/devtools/browser/2b9e6328-6561-4e1f-96a9-ea83930c550a
▉▁▁▁▁▁ Loading language utility...[nltk_data] Downloading package vader_lexicon to
[nltk_data]     C:\Users\user\AppData\Roaming\nltk_data...
[nltk_data]   Package vader_lexicon is already up-to-date!
▉▉▉▃▁▁ Loading zero-shot pipeline...Device set to use cpu
AgenticSeek is ready.
INFO:     Started server process [29888]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     192.168.1.11:53777 - "GET /health HTTP/1.1" 200 OK
INFO:     192.168.1.11:53775 - "GET /latest_answer HTTP/1.1" 404 Not Found
INFO:     192.168.1.11:53774 - "GET /screenshots/updated_screen.png?timestamp=1746649023706 HTTP/1.1" 200 OK
INFO:     192.168.1.11:53775 - "GET /health HTTP/1.1" 200 OK
INFO:     192.168.1.11:53777 - "GET /latest_answer HTTP/1.1" 404 Not Found
INFO:     192.168.1.11:53774 - "GET /screenshots/updated_screen.png?timestamp=1746649026715 HTTP/1.1" 200 OK
Created TensorFlow Lite XNNPACK delegate for CPU.
INFO:     192.168.1.11:53775 - "GET /latest_answer HTTP/1.1" 404 Not Found
INFO:     192.168.1.11:53777 - "GET /health HTTP/1.1" 200 OK
INFO:     192.168.1.11:53774 - "GET /screenshots/updated_screen.png?timestamp=1746649029706 HTTP/1.1" 200 OK
INFO:     192.168.1.11:53775 - "GET /latest_answer HTTP/1.1" 404 Not Found
INFO:     192.168.1.11:53777 - "GET /health HTTP/1.1" 200 OK
INFO:     192.168.1.11:53774 - "GET /screenshots/updated_screen.png?timestamp=1746649032703 HTTP/1.1" 200 OK
INFO:     192.168.1.11:53774 - "OPTIONS /query HTTP/1.1" 200 OK
INFO:     192.168.1.11:53777 - "GET /health HTTP/1.1" 200 OK
Selected agent: Browser (roles: web)
▉▉▉▉▇▅ Thinking...INFO:     192.168.1.11:53777 - "GET /health HTTP/1.1" 200 OK
INFO:     192.168.1.11:53776 - "GET /latest_answer HTTP/1.1" 200 OK
INFO:     192.168.1.11:53794 - "GET /screenshots/updated_screen.png?timestamp=1746649038540 HTTP/1.1" 200 OK
▃▇▉▇▉▅ Thinking...INFO:     192.168.1.11:53777 - "GET /latest_answer HTTP/1.1" 200 OK
INFO:     192.168.1.11:53776 - "GET /health HTTP/1.1" 200 OK
INFO:     192.168.1.11:53794 - "GET /screenshots/updated_screen.png?timestamp=1746649041548 HTTP/1.1" 200 OK

Search results:
Title: Yahoo!天気・災害 - 天気予報 / 防災情報 - Link: https://weather.yahoo.co.jp/weather/
Title: 東京都の天気 - Yahoo!天気・災害 - Link: https://weather.yahoo.co.jp/weather/jp/13/
Title: Jo bizpu ju eni doj You: "search: 今日の東京の天気を教えて" Busomuk. - Link: http://geb.mx/sopfa
Title: 東京（東京）の天気 - Yahoo!天気・災害 - Link: https://weather.yahoo.co.jp/weather/jp/13/4410.html
Title: 【ウェザーニュース】天気 - 台風・地震・防災情報｜予報精度 ... - Link: https://weathernews.jp/
Title: Fi zarta davuk You: "search: 今日の東京の天気を教えて" Sape ika ub. - Link: http://ucikenba.me/im
Title: 東京の天気 - ウェザーニュース - Link: https://weathernews.jp/onebox/tenki/tokyo/
Title: 港区の3時間天気 - 日本気象協会 tenki.jp - Link: https://tenki.jp/forecast/3/16/4410/13103/3hours.html

...(略
```

## Snipet集
```powershell
# 管理者権限 PowerShell
# Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

# PowerShell Pathを出力/設定
# $ENV:Path.Split(";")
# $ENV:Path+=";C:\Windows\System32\"

# Google Chrome Web Driver Download
# https://googlechromelabs.github.io/chrome-for-testing/#stable
# $ENV:Path+=";$env:LOCALAPPDATA\Programs\GoogleChromeWebDriver\"

# venv環境削除
& cmd.exe /c "rmdir /s /q agentic_seek_env"

# venv作成
& "$env:LOCALAPPDATA\Programs\Python\Python310\python.exe" -m venv agentic_seek_env

# venv activate
.\agentic_seek_env\Scripts\activate
# .\agentic_seek_env\Scripts\deactivate.batは動かないのでターミナルごと落とす

# package install
python -m pip install --upgrade pip
pip install pyreadline3
pip install pyaudio
pip3 install -r requirements.txt

# docker/docker-compose install check
# dockerデスクトップをダウンロードしておく
# https://www.docker.com/ja-jp/get-started/
docker -v
# docker-composeはpip install
# pip install docker-compose
docker-compose -v

# docker compose up -d
start ./start_services.cmd

# backend serve
# .\agentic_seek_env\Scripts\activate
python -m uvicorn api:api --reload --host 0.0.0.0 --port 8000

# run ollama
# https://ollama.com/library/deepseek-r1
& "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" pull deepseek-r1:14b
# & "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" pull deepseek-r1:32b
& "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" list
& "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" ps
& "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" run deepseek-r1:14b
& "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" ps
# & "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" run deepseek-r1:32b
# & "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" stop deepseek-r1:14b
# & "$env:LOCALAPPDATA\Programs\Ollama\ollama.exe" stop deepseek-r1:32b
```


## 環境構築
Windows環境だとそのままだとエラーになるのでファイルを編集する

---

api.py
```python
#!/usr/bin/env python3

import os, sys
import uvicorn
import aiofiles
import configparser
import asyncio
import time
from typing import List
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
import uuid

from sources.llm_provider import Provider
from sources.interaction import Interaction
from sources.agents import CasualAgent, CoderAgent, FileAgent, PlannerAgent, BrowserAgent
from sources.browser import Browser, create_driver
from sources.utility import pretty_print
from sources.logger import Logger
from sources.schemas import QueryRequest, QueryResponse


from celery import Celery

api = FastAPI(title="AgenticSeek API", version="0.1.0")
celery_app = Celery("tasks", broker="redis://127.0.0.1:6379/0", backend="redis://127.0.0.1:6379/0")
celery_app.conf.update(task_track_started=True)
logger = Logger("backend.log")
config = configparser.ConfigParser()
config.read('config.ini')

api.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

if not os.path.exists(".screenshots"):
    os.makedirs(".screenshots")
api.mount("/screenshots", StaticFiles(directory=".screenshots"), name="screenshots")

def initialize_system():
    stealth_mode = config.getboolean('BROWSER', 'stealth_mode')
    personality_folder = "jarvis" if config.getboolean('MAIN', 'jarvis_personality') else "base"
    languages = config["MAIN"]["languages"].split(' ')

    provider = Provider(
        provider_name=config["MAIN"]["provider_name"],
        model=config["MAIN"]["provider_model"],
        server_address=config["MAIN"]["provider_server_address"],
        is_local=config.getboolean('MAIN', 'is_local')
    )
    logger.info(f"Provider initialized: {provider.provider_name} ({provider.model})")

    browser = Browser(
        create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode),
        anticaptcha_manual_install=stealth_mode
    )
    logger.info("Browser initialized")

    agents = [
        CasualAgent(
            name=config["MAIN"]["agent_name"],
            prompt_path=f"prompts/{personality_folder}/casual_agent.txt",
            provider=provider, verbose=False
        ),
        CoderAgent(
            name="coder",
            prompt_path=f"prompts/{personality_folder}/coder_agent.txt",
            provider=provider, verbose=False
        ),
        FileAgent(
            name="File Agent",
            prompt_path=f"prompts/{personality_folder}/file_agent.txt",
            provider=provider, verbose=False
        ),
        BrowserAgent(
            name="Browser",
            prompt_path=f"prompts/{personality_folder}/browser_agent.txt",
            provider=provider, verbose=False, browser=browser
        ),
        PlannerAgent(
            name="Planner",
            prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
            provider=provider, verbose=False, browser=browser
        )
    ]
    logger.info("Agents initialized")

    interaction = Interaction(
        agents,
        tts_enabled=config.getboolean('MAIN', 'speak'),
        stt_enabled=config.getboolean('MAIN', 'listen'),
        recover_last_session=config.getboolean('MAIN', 'recover_last_session'),
        langs=languages
    )
    logger.info("Interaction initialized")
    return interaction

interaction = initialize_system()
is_generating = False
query_resp_history = []

@api.get("/screenshot")
async def get_screenshot():
    logger.info("Screenshot endpoint called")
    screenshot_path = ".screenshots/updated_screen.png"
    if os.path.exists(screenshot_path):
        return FileResponse(screenshot_path)
    logger.error("No screenshot available")
    return JSONResponse(
        status_code=404,
        content={"error": "No screenshot available"}
    )

@api.get("/health")
async def health_check():
    logger.info("Health check endpoint called")
    return {"status": "healthy", "version": "0.1.0"}

@api.get("/is_active")
async def is_active():
    logger.info("Is active endpoint called")
    return {"is_active": interaction.is_active}

@api.get("/latest_answer")
async def get_latest_answer():
    global query_resp_history
    if interaction.current_agent is None:
        return JSONResponse(status_code=404, content={"error": "No agent available"})
    uid = str(uuid.uuid4())
    if not any(q["answer"] == interaction.current_agent.last_answer for q in query_resp_history):
        query_resp = {
            "done": "false",
            "answer": interaction.current_agent.last_answer,
            "agent_name": interaction.current_agent.agent_name if interaction.current_agent else "None",
            "success": interaction.current_agent.success,
            "blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.get_last_blocks_result())} if interaction.current_agent else {},
            "status": interaction.current_agent.get_status_message if interaction.current_agent else "No status available",
            "uid": uid
        }
        interaction.current_agent.last_answer = ""
        query_resp_history.append(query_resp)
        return JSONResponse(status_code=200, content=query_resp)
    if query_resp_history:
        return JSONResponse(status_code=200, content=query_resp_history[-1])
    return JSONResponse(status_code=404, content={"error": "No answer available"})

async def think_wrapper(interaction, query):
    try:
        interaction.last_query = query
        logger.info("Agents request is being processed")
        success = await interaction.think()
        if not success:
            interaction.last_answer = "Error: No answer from agent"
            interaction.last_success = False
        else:
            interaction.last_success = True
        pretty_print(interaction.last_answer)
        interaction.speak_answer()
        return success
    except Exception as e:
        logger.error(f"Error in think_wrapper: {str(e)}")
        interaction.last_answer = f"Error: {str(e)}"
        interaction.last_success = False
        raise e

@api.post("/query", response_model=QueryResponse)
async def process_query(request: QueryRequest):
    global is_generating, query_resp_history
    logger.info(f"Processing query: {request.query}")
    query_resp = QueryResponse(
        done="false",
        answer="",
        agent_name="Unknown",
        success="false",
        blocks={},
        status="Ready",
        uid=str(uuid.uuid4())
    )
    if is_generating:
        logger.warning("Another query is being processed, please wait.")
        return JSONResponse(status_code=429, content=query_resp.jsonify())

    try:
        is_generating = True
        success = await think_wrapper(interaction, request.query)
        is_generating = False

        if not success:
            query_resp.answer = interaction.last_answer
            return JSONResponse(status_code=400, content=query_resp.jsonify())

        if interaction.current_agent:
            blocks_json = {f'{i}': block.jsonify() for i, block in enumerate(interaction.current_agent.get_blocks_result())}
        else:
            logger.error("No current agent found")
            blocks_json = {}
            query_resp.answer = "Error: No current agent"
            return JSONResponse(status_code=400, content=query_resp.jsonify())

        logger.info(f"Answer: {interaction.last_answer}")
        logger.info(f"Blocks: {blocks_json}")
        query_resp.done = "true"
        query_resp.answer = interaction.last_answer
        query_resp.agent_name = interaction.current_agent.agent_name
        query_resp.success = str(interaction.last_success)
        query_resp.blocks = blocks_json
        
        # Store the raw dictionary representation
        query_resp_dict = {
            "done": query_resp.done,
            "answer": query_resp.answer,
            "agent_name": query_resp.agent_name,
            "success": query_resp.success,
            "blocks": query_resp.blocks,
            "status": query_resp.status,
            "uid": query_resp.uid
        }
        query_resp_history.append(query_resp_dict)

        logger.info("Query processed successfully")
        return JSONResponse(status_code=200, content=query_resp.jsonify())
    except Exception as e:
        logger.error(f"An error occurred: {str(e)}")
        sys.exit(1)
    finally:
        logger.info("Processing finished")
        if config.getboolean('MAIN', 'save_session'):
            interaction.save_session()

if __name__ == "__main__":
    uvicorn.run(api, host="0.0.0.0", port=8000)
```

```
celery_app = Celery("tasks", broker="redis://127.0.0.1:6379/0", backend="redis://127.0.0.1:6379/0")
に変更
```

---

config.ini
```ini
[MAIN]
is_local = True
provider_name = ollama
# provider_model = deepseek-r1:32b
provider_model = deepseek-r1:14b
provider_server_address = 127.0.0.1:11434
agent_name = Name_of_your_AI
recover_last_session = False
save_session = False
speak = False
listen = False
work_dir = C:\<your-project-here>\agenticSeek
jarvis_personality = False
languages = en
[BROWSER]
headless_browser = True
stealth_mode = False
```

```
provider_model
を変更

work_dir
を変更
```

---

docker-compose.yml
```yaml
services:
  redis:
    container_name: redis
    image: docker.io/valkey/valkey:8-alpine
    command: valkey-server --save 30 1 --loglevel warning
    restart: unless-stopped
    volumes:
      - redis-data:/data
    cap_drop:
      - ALL
    cap_add:
      - SETGID
      - SETUID
      - DAC_OVERRIDE
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
        max-file: "1"
    networks:
      - agentic-seek-net

  searxng:
    container_name: searxng
    image: docker.io/searxng/searxng:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./searxng:/etc/searxng:rw
    environment:
      - SEARXNG_BASE_URL=http://localhost:8080/
      - SEARXNG_SECRET_KEY=<your-secret-key-here>
      - UWSGI_WORKERS=4
      - UWSGI_THREADS=4
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
        max-file: "1"
    depends_on:
      - redis
    networks:
      - agentic-seek-net

  frontend:
    container_name: frontend
    build:
      context: ./frontend
      dockerfile: Dockerfile.frontend
    ports:
      - "3000:3000"
    volumes:
      - ./frontend/agentic-seek-front/src:/app/src
      - ./screenshots:/app/screenshots
    environment:
      - NODE_ENV=development
      - CHOKIDAR_USEPOLLING=true
      - BACKEND_URL=http://backend:8000
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - agentic-seek-net
  
  # NOTE: backend service is not working yet due to issue with chromedriver on docker.
  # Therefore backend is run on host machine.
  # Open to pull requests to fix this.

  #backend:
  #  container_name: backend
  #  build:
  #    context: ./
  #    dockerfile: Dockerfile.backend
  #  stdin_open: true
  #  tty: true
  #  shm_size: 8g
  #  ports:
  #    - "8000:8000"
  #  volumes:
  #    - ./:/app
  #  environment:
  #    - NODE_ENV=development
  #    - REDIS_URL=redis://redis:6379/0
  #    - SEARXNG_URL=http://searxng:8080
  #    - OLLAMA_URL=http://localhost:11434
  #    - LM_STUDIO_URL=http://localhost:1234
  #  extra_hosts:
  #    - "host.docker.internal:host-gateway"
  #  depends_on:
  #    - redis
  #    - searxng
  #  networks:
  #    - agentic-seek-net
  
volumes:
  redis-data:
  chrome_profiles:

networks:
  agentic-seek-net:
    driver: bridge
```

```
    extra_hosts:
      - "host.docker.internal:host-gateway"
を追加

version: '3'
を削除
```

---

requirements.txt
```text
kokoro==0.9.4
certifi==2025.4.26
fastapi>=0.115.12
flask>=3.1.0
celery>=5.5.1
aiofiles>=24.1.0
uvicorn>=0.34.0
pydantic>=2.10.6
pydantic_core>=2.27.2
setuptools>=75.6.0
sacremoses>=0.0.53
requests>=2.31.0
numpy>=1.24.4
colorama>=0.4.6
python-dotenv>=1.0.0
soundfile>=0.13.1
transformers>=4.46.3
torch>=2.4.1
python-dotenv>=1.0.0
ollama>=0.4.7
scipy>=1.9.3
soundfile>=0.13.1
protobuf>=3.20.3
termcolor>=2.4.0
pypdf>=5.4.0
ipython>=8.13.0
pyaudio>=0.2.14
librosa>=0.10.2.post1
selenium>=4.27.1
markdownify>=1.1.0
text2emotion>=0.0.5
adaptive-classifier>=0.0.10
langid>=1.1.6
chromedriver-autoinstaller>=0.6.4
httpx>=0.27,<0.29
anyio>=3.5.0,<5
distro>=1.7.0,<2
jiter>=0.4.0,<1
fake_useragent>=2.1.0
selenium_stealth>=1.0.6
undetected-chromedriver>=3.5.5
sentencepiece>=0.2.0
tqdm>4
openai
sniffio
ordered_set
pypinyin
```

```
playsound>=1.3.0
を削除
```

---

start_services.cmd
```bat
@echo off

docker compose up
if %ERRORLEVEL% neq 0 (
    echo Error: Failed to start containers. Check Docker logs with 'docker compose logs'.
    echo Possible fixes: Ensure Docker Desktop is running or check if port 8080 is free.
    exit /b 1
)

timeout /t 10 /nobreak >nul
```

```
docker compose up
に変更
```

---

app.js
```react
import React, { useState, useEffect, useRef } from 'react';
import axios from 'axios';
import './App.css';
import { colors } from './colors';

function App() {
    const [query, setQuery] = useState('');
    const [messages, setMessages] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(null);
    const [currentView, setCurrentView] = useState('blocks');
    const [responseData, setResponseData] = useState(null);
    const [isOnline, setIsOnline] = useState(false);
    const [status, setStatus] = useState('Agents ready');
    const messagesEndRef = useRef(null);

    useEffect(() => {
        const intervalId = setInterval(() => {
            checkHealth();
            fetchLatestAnswer();
            fetchScreenshot();
        }, 3000);
        return () => clearInterval(intervalId);
    }, [messages]);

    const checkHealth = async () => {
        try {
            await axios.get('http://host.docker.internal:8000/health');
            setIsOnline(true);
            console.log('System is online');
        } catch {
            setIsOnline(false);
            console.log('System is offline');
        }
    };

    const fetchScreenshot = async () => {
        try {
            const timestamp = new Date().getTime();
            const res = await axios.get(`http://host.docker.internal:8000/screenshots/updated_screen.png?timestamp=${timestamp}`, {
                responseType: 'blob'
            });
            console.log('Screenshot fetched successfully');
            const imageUrl = URL.createObjectURL(res.data);
            setResponseData((prev) => {
                if (prev?.screenshot && prev.screenshot !== 'placeholder.png') {
                    URL.revokeObjectURL(prev.screenshot);
                }
                return {
                    ...prev,
                    screenshot: imageUrl,
                    screenshotTimestamp: new Date().getTime()
                };
            });
        } catch (err) {
            console.error('Error fetching screenshot:', err);
            setResponseData((prev) => ({
                ...prev,
                screenshot: 'placeholder.png',
                screenshotTimestamp: new Date().getTime()
            }));
        }
    };

    const normalizeAnswer = (answer) => {
        return answer
            .trim()
            .toLowerCase()
            .replace(/\s+/g, ' ')
            .replace(/[.,!?]/g, '')
    };

    const scrollToBottom = () => {
        messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
    };

    const fetchLatestAnswer = async () => {
        try {
            const res = await axios.get('http://host.docker.internal:8000/latest_answer');
            const data = res.data;

            updateData(data);
            if (!data.answer || data.answer.trim() === '') {
                return;
            }
            const normalizedNewAnswer = normalizeAnswer(data.answer);
            const answerExists = messages.some(
                (msg) => normalizeAnswer(msg.content) === normalizedNewAnswer
            );
            if (!answerExists) {
                setMessages((prev) => [
                    ...prev,
                    {
                        type: 'agent',
                        content: data.answer,
                        agentName: data.agent_name,
                        status: data.status,
                        uid: data.uid,
                    },
                ]);
                setStatus(data.status);
                scrollToBottom();
            } else {
                console.log('Duplicate answer detected, skipping:', data.answer);
            }
        } catch (error) {
            console.error('Error fetching latest answer:', error);
        }
    };

    const updateData = (data) => {
        setResponseData((prev) => ({
            ...prev,
            blocks: data.blocks || prev.blocks || null,
            done: data.done,
            answer: data.answer,
            agent_name: data.agent_name,
            status: data.status,
            uid: data.uid,
        }));
    };

    const handleSubmit = async (e) => {
        e.preventDefault();
        checkHealth();
        if (!query.trim()) {
            console.log('Empty query');
            return;
        }
        setMessages((prev) => [...prev, { type: 'user', content: query }]);
        setIsLoading(true);
        setError(null);

        try {
            console.log('Sending query:', query);
            setQuery('waiting for response...');
            const res = await axios.post('http://host.docker.internal:8000/query', {
                query,
                tts_enabled: false
            });
            setQuery('Enter your query...');
            console.log('Response:', res.data);
            const data = res.data;
            updateData(data);
        } catch (err) {
            console.error('Error:', err);
            setError('Failed to process query.');
            setMessages((prev) => [
                ...prev,
                { type: 'error', content: 'Error: Unable to get a response.' },
            ]);
        } finally {
            console.log('Query completed');
            setIsLoading(false);
            setQuery('');
        }
    };

    const handleGetScreenshot = async () => {
        try {
            setCurrentView('screenshot');
        } catch (err) {
            setError('Browser not in use');
        }
    };

    return (
        <div className="app">
            <header className="header">
                <h1>AgenticSeek</h1>
            </header>
            <main className="main">
                <div className="app-sections">


                    <div className="chat-section">
                        <h2>Chat Interface</h2>
                        <div className="messages">
                            {messages.length === 0 ? (
                                <p className="placeholder">No messages yet. Type below to start!</p>
                            ) : (
                                messages.map((msg, index) => (
                                    <div
                                        key={index}
                                        className={`message ${
                                            msg.type === 'user'
                                                ? 'user-message'
                                                : msg.type === 'agent'
                                                ? 'agent-message'
                                                : 'error-message'
                                        }`}
                                    >
                                        {msg.type === 'agent' && (
                                            <span className="agent-name">{msg.agentName}</span>
                                        )}
                                        <p>{msg.content}</p>
                                    </div>
                                ))
                            )}
                            <div ref={messagesEndRef} />
                        </div>
                        {isOnline && <div className="loading-animation">{status}</div>}
                        {!isLoading && !isOnline && <p className="loading-animation">System offline. Deploy backend first.</p>}
                        <form onSubmit={handleSubmit} className="input-form">
                            <input
                                type="text"
                                value={query}
                                onChange={(e) => setQuery(e.target.value)}
                                placeholder="Type your query..."
                                disabled={isLoading}
                            />
                            <button type="submit" disabled={isLoading}>
                                Send
                            </button>
                        </form>
                    </div>

                    <div className="computer-section">
                        <h2>Computer View</h2>
                        <div className="view-selector">
                            <button
                                className={currentView === 'blocks' ? 'active' : ''}
                                onClick={() => setCurrentView('blocks')}
                            >
                                Editor View
                            </button>
                            <button
                                className={currentView === 'screenshot' ? 'active' : ''}
                                onClick={responseData?.screenshot ? () => setCurrentView('screenshot') : handleGetScreenshot}
                            >
                                Browser View
                            </button>
                        </div>
                        <div className="content">
                            {error && <p className="error">{error}</p>}
                            {currentView === 'blocks' ? (
                                <div className="blocks">
                                    {responseData && responseData.blocks && Object.values(responseData.blocks).length > 0 ? (
                                        Object.values(responseData.blocks).map((block, index) => (
                                            <div key={index} className="block">
                                                <p className="block-tool">Tool: {block.tool_type}</p>
                                                <pre>{block.block}</pre>
                                                <p className="block-feedback">Feedback: {block.feedback}</p>
                                                <p className="block-success">
                                                    Success: {block.success ? 'Yes' : 'No'}
                                                </p>
                                            </div>
                                        ))
                                    ) : (
                                        <div className="block">
                                            <p className="block-tool">Tool: No tool in use</p>
                                            <pre>No file opened</pre>
                                        </div>
                                    )}
                                </div>
                            ) : (
                                <div className="screenshot">
                                    <img
                                        src={responseData?.screenshot || 'placeholder.png'}
                                        alt="Screenshot"
                                        onError={(e) => {
                                            e.target.src = 'placeholder.png';
                                            console.error('Failed to load screenshot');
                                        }}
                                        key={responseData?.screenshotTimestamp || 'default'}
                                    />
                                </div>
                            )}
                        </div>
                    </div>
                </div>
            </main>
        </div>
    );
}

export default App;
```

```react
            await axios.get('http://host.docker.internal:8000/health');
に変更

            const res = await axios.get(`http://host.docker.internal:8000/screenshots/updated_screen.png?timestamp=${timestamp}`, {
に変更

            const res = await axios.get('http://host.docker.internal:8000/latest_answer');
に変更

            const res = await axios.post('http://host.docker.internal:8000/query', {
に変更
```

---

# 余談
GWは色々捗りますね～