ローカルファイルをAIに分析させる「Local Navigator」


画像

【このツールの概要】

・指定したフォルダ内のファイルを
 AIに分析させます。

【手動モードの操作説明】

・手動モードの場合は、
 まず入力欄に要望を書いて
 確定ボタンを押して下さい。

・するとプロンプトができますので、
 このプロンプトをクリックすると
 コピーされますので、これを
 ブラウザのプロンプト入力欄に貼り付けて
 AIから解答を得ます。

・そしてまたそれを
 本ツール側の入力欄に貼り付けて
 ボタンを押します。

・このようにしていくと、分析結果が出ます。

・ダウンロードされる方はこちら。↓

 


・ソースコードはこちら。↓

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>NeuroContext Navigator v1.0</title>
    <style>
        :root {
            --bg: #050505;
            --panel: rgba(12, 16, 20, 0.95);
            --accent: #00ffcc;
            --text: #e0f0ff;
            --border: #334455;
            --danger: #ff4466;
            --warn: #ffcc00;
        }
        body { margin: 0; background: var(--bg); color: var(--text); font-family: 'Segoe UI', 'Roboto', monospace; overflow: hidden; height: 100vh; display: flex; flex-direction: column; }
        
        /* Layout */
        #header {
            height: 50px; background: rgba(10, 12, 16, 0.9); border-bottom: 1px solid var(--border);
            display: flex; align-items: center; padding: 0 20px; justify-content: space-between;
            box-shadow: 0 2px 20px rgba(0, 255, 204, 0.1); z-index: 10;
        }
        .logo { font-weight: bold; letter-spacing: 2px; color: var(--accent); display: flex; align-items: center; gap: 10px; }
        .logo::before { content: '??'; filter: drop-shadow(0 0 5px var(--accent)); }

        #main-area { flex: 1; display: flex; overflow: hidden; position: relative; }
        
        /* Left Panel (Controls & Status) */
        #left-panel {
            width: 300px; background: var(--panel); border-right: 1px solid var(--border);
            padding: 20px; display: flex; flex-direction: column; gap: 20px;
            backdrop-filter: blur(10px); z-index: 5;
        }

        /* Right Panel (Chat & Interaction) */
        #right-panel { flex: 1; display: flex; flex-direction: column; position: relative; }
        #chat-history { 
            flex: 1; overflow-y: auto; padding: 40px; scroll-behavior: smooth; 
            background: radial-gradient(circle at center, #0a0f14 0%, #000 100%);
        }
        
        #input-area {
            height: 120px; background: var(--panel); border-top: 1px solid var(--border);
            padding: 20px; display: flex; gap: 10px; align-items: flex-start;
        }

        /* Elements */
        .btn {
            background: rgba(0, 255, 204, 0.1); border: 1px solid var(--border); color: var(--accent);
            padding: 10px 20px; cursor: pointer; font-family: monospace; transition: 0.2s;
            text-transform: uppercase; font-size: 11px; letter-spacing: 1px;
        }
        .btn:hover { background: var(--accent); color: #000; box-shadow: 0 0 15px var(--accent); }
        .btn:disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(100%); }
        .btn-danger { border-color: var(--danger); color: var(--danger); background: rgba(255, 68, 102, 0.1); }

        input, textarea, select {
            background: #080a0c; border: 1px solid var(--border); color: var(--text);
            padding: 10px; font-family: monospace; width: 100%; box-sizing: border-box; outline: none;
        }
        input:focus, textarea:focus { border-color: var(--accent); }
        textarea { resize: none; height: 100%; font-size: 14px; }

        /* Chat Bubbles */
        .msg { margin-bottom: 20px; max-width: 80%; line-height: 1.6; font-size: 13px; animation: fadeIn 0.3s ease; }
        .msg.user { margin-left: auto; text-align: right; }
        .msg.user .bubble { background: rgba(0, 255, 204, 0.1); border: 1px solid var(--border); border-radius: 8px 0 8px 8px; color: #fff; }
        .msg.ai .bubble { background: rgba(255, 255, 255, 0.05); border: 1px solid #333; border-radius: 0 8px 8px 8px; color: #ccc; }
        .msg.system .bubble { background: rgba(255, 204, 0, 0.1); border: 1px solid var(--warn); color: var(--warn); width: 100%; max-width: 100%; text-align: left; }
        .bubble { padding: 15px; display: inline-block; white-space: pre-wrap; position: relative; }

        /* Code Block / Copy Box */
        .copy-box {
            background: #000; border: 1px solid #333; padding: 10px; margin-top: 10px;
            font-size: 11px; overflow-x: auto; color: #00ffcc; cursor: pointer; position: relative;
        }
        .copy-box:hover { border-color: var(--accent); }
        .copy-box::after { content: 'CLICK TO COPY'; position: absolute; top:0; right:0; background:var(--accent); color:#000; font-size:9px; padding:2px 5px; }

        /* Status Indicators */
        .status-row { display: flex; justify-content: space-between; font-size: 11px; color: #666; margin-bottom: 5px; }
        .stat-val { color: var(--text); }

        /* Tabs */
        .tabs { display: flex; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
        .tab { flex: 1; text-align: center; padding: 10px; cursor: pointer; color: #666; font-size: 12px; }
        .tab.active { color: var(--accent); border-bottom: 2px solid var(--accent); background: linear-gradient(to top, rgba(0,255,204,0.1), transparent); }

        @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

        /* Helper for file tree preview */
        #file-tree-preview { height: 150px; overflow-y: auto; font-size: 10px; color: #888; border: 1px solid #222; padding: 5px; margin-top: 10px; }
    </style>
</head>
<body>

    <div id="header">
        <div class="logo">NEURO CONTEXT NAVIGATOR</div>
        <div style="font-size: 11px; color: #666;">LOCAL FILE ACCESS // AI ORCHESTRATION</div>
    </div>

    <div id="main-area">
        <div id="left-panel">
            <div class="status-row"><span>STATUS</span><span class="stat-val" id="sys-status">IDLE</span></div>
            <div class="status-row"><span>TARGET</span><span class="stat-val" id="target-folder">NONE</span></div>
            
            <button class="btn" onclick="nav.selectFolder()">?? SELECT FOLDER</button>
            <div id="file-tree-preview">No folder selected.</div>

            <div style="height: 1px; background: var(--border); margin: 10px 0;"></div>

            <div class="tabs">
                <div class="tab active" onclick="nav.setMode('manual')" id="tab-manual">MANUAL</div>
                <div class="tab" onclick="nav.setMode('auto')" id="tab-auto">AUTO API</div>
            </div>

            <div id="manual-controls">
                <p style="font-size:11px; color:#888;">
                    <strong>Manual Mode:</strong><br>
                    1. Generate Prompt (Map + Question)<br>
                    2. Copy & Paste to Gemini/ChatGPT<br>
                    3. Paste AI's "READ_REQUEST" here<br>
                    4. Repeat until solved.
                </p>
            </div>

            <div id="auto-controls" style="display:none;">
                <input type="password" id="api-key" placeholder="Gemini API Key (Auto Mode)">
                <p style="font-size:11px; color:#888; margin-top:10px;">
                    Directly calls Google Gemini API to iterate through files automatically.
                </p>
            </div>
        </div>

        <div id="right-panel">
            <div id="chat-history">
                <div class="msg system"><div class="bubble">Welcome. Select a local folder to begin context navigation.</div></div>
            </div>
            <div id="input-area">
                <textarea id="user-input" placeholder="Enter your requirement here... (e.g., 'Analyze the authentication logic in this project')"></textarea>
                <div style="display:flex; flex-direction:column; gap:10px;">
                    <button class="btn" onclick="nav.send()" style="height: 100%;">EXECUTE</button>
                    <button class="btn btn-danger" onclick="nav.reset()" style="height: 40px;">RESET</button>
                </div>
            </div>
        </div>
    </div>

    <script>
        // --- CORE LOGIC ---
        const nav = {
            dirHandle: null,
            fileMap: [],
            fileHandleMap: {}, // Path -> Handle
            mode: 'manual', // 'manual' or 'auto'
            step: 'initial', // 'initial' or 'followup'
            history: [], // For Auto API

            init: () => {
                console.log("NeuroContext Navigator Initialized");
            },

            setMode: (m) => {
                nav.mode = m;
                document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
                document.getElementById(`tab-${m}`).classList.add('active');
                document.getElementById('manual-controls').style.display = m === 'manual' ? 'block' : 'none';
                document.getElementById('auto-controls').style.display = m === 'auto' ? 'block' : 'none';
            },

            selectFolder: async () => {
                try {
                    nav.dirHandle = await window.showDirectoryPicker();
                    document.getElementById('target-folder').innerText = nav.dirHandle.name.toUpperCase();
                    document.getElementById('sys-status').innerText = "SCANNING...";
                    
                    nav.fileMap = [];
                    nav.fileHandleMap = {};
                    await nav.scanDir(nav.dirHandle, "");
                    
                    document.getElementById('file-tree-preview').innerText = nav.fileMap.join('\n');
                    document.getElementById('sys-status').innerText = "READY";
                    nav.addMsg("System", `Target set: ${nav.dirHandle.name} (${nav.fileMap.length} files mapped).`);
                } catch (e) {
                    console.error(e);
                    alert("Folder access cancelled or failed.");
                }
            },

            scanDir: async (dirHandle, path) => {
                for await (const entry of dirHandle.values()) {
                    const relativePath = path ? `${path}/${entry.name}` : entry.name;
                    
                    // Filter out common clutter
                    if (['node_modules', '.git', 'bin', 'obj', '.vs'].includes(entry.name)) continue;

                    if (entry.kind === 'file') {
                        // Extension filter (Optional, allowing text-based files)
                        const ext = entry.name.split('.').pop().toLowerCase();
                        if (['exe', 'dll', 'png', 'jpg', 'zip'].includes(ext)) continue;

                        nav.fileMap.push(relativePath);
                        nav.fileHandleMap[relativePath] = entry;
                    } else if (entry.kind === 'directory') {
                        await nav.scanDir(entry, relativePath);
                    }
                }
            },

            reset: () => {
                nav.step = 'initial';
                document.getElementById('chat-history').innerHTML = '';
                nav.addMsg("System", "Session reset.");
                nav.history = [];
            },

            send: async () => {
                const inputEl = document.getElementById('user-input');
                const text = inputEl.value.trim();
                if (!text) return;
                
                if (!nav.dirHandle) { alert("Please select a folder first."); return; }

                inputEl.value = "";
                
                if (nav.mode === 'manual') {
                    await nav.processManual(text);
                } else {
                    await nav.processAuto(text);
                }
            },

            // --- MANUAL MODE LOGIC ---
            processManual: async (userInput) => {
                // If we are in 'initial' step, user input is the QUESTION.
                // If we are in 'followup' step, user input is the AI RESPONSE (containing READ_REQUEST).
                
                if (nav.step === 'initial') {
                    nav.addMsg("User", userInput);
                    const prompt = nav.buildSystemPrompt() + `\n\n[FILE MAP]:\n${nav.fileMap.join('\n')}\n\n[USER QUESTION]:\n${userInput}`;
                    nav.addMsg("System", "Copy this prompt and send it to your AI:", prompt);
                    nav.step = 'followup';
                    document.getElementById('user-input').placeholder = "Paste the AI's response here (specifically if it asks for READ_REQUEST)...";
                
                } else if (nav.step === 'followup') {
                    // Parse AI response for READ_REQUEST
                    nav.addMsg("User", "(Pasted AI Response)");
                    
                    const requests = userInput.match(/READ_REQUEST:\s*(.+)/g);
                    
                    if (requests && requests.length > 0) {
                        let contentBlock = "";
                        for (let req of requests) {
                            const path = req.replace("READ_REQUEST:", "").trim();
                            const content = await nav.readFileContent(path);
                            contentBlock += `\n--- START FILE: ${path} ---\n${content}\n--- END FILE: ${path} ---\n`;
                        }
                        
                        const nextPrompt = `[SYSTEM] Here are the file contents you requested:\n${contentBlock}\n\nAnalyze this and answer the original question, or request more files using 'READ_REQUEST: path'.`;
                        nav.addMsg("System", "Files read. Copy this next prompt:", nextPrompt);
                    } else {
                        nav.addMsg("System", "No READ_REQUEST found. Assuming this is the final answer or conversational text.");
                        nav.addMsg("AI", userInput);
                        nav.step = 'initial'; // Reset logic loop
                        document.getElementById('user-input').placeholder = "Enter new requirement...";
                    }
                }
            },

            // --- AUTO MODE LOGIC (Gemini) ---
            processAuto: async (userInput) => {
                const apiKey = document.getElementById('api-key').value;
                if (!apiKey) { alert("API Key required for Auto Mode."); return; }
                
                nav.addMsg("User", userInput);
                document.getElementById('sys-status').innerText = "AI THINKING...";

                // 1. First prompt with Map
                if (nav.history.length === 0) {
                    const sys = nav.buildSystemPrompt();
                    nav.history.push({ role: "user", parts: [{ text: sys + `\n\n[FILE MAP]:\n${nav.fileMap.join('\n')}\n\n[USER]: ${userInput}` }] });
                } else {
                     nav.history.push({ role: "user", parts: [{ text: userInput }] });
                }

                try {
                    let loopCount = 0;
                    let keepGoing = true;

                    while (keepGoing && loopCount < 5) {
                        const response = await nav.callGemini(apiKey, nav.history);
                        const aiText = response.candidates[0].content.parts[0].text;
                        nav.history.push({ role: "model", parts: [{ text: aiText }] });

                        // Check for READ_REQUEST
                        const requests = aiText.match(/READ_REQUEST:\s*(.+)/g);
                        
                        if (requests && requests.length > 0) {
                             nav.addMsg("AI", `I need to read ${requests.length} files...`);
                             let contentBlock = "";
                             for (let req of requests) {
                                const path = req.replace("READ_REQUEST:", "").trim();
                                const content = await nav.readFileContent(path);
                                contentBlock += `\n--- FILE: ${path} ---\n${content}\n`;
                             }
                             // Feed back to AI
                             nav.history.push({ role: "user", parts: [{ text: `[SYSTEM] File Contents:\n${contentBlock}` }] });
                             loopCount++;
                        } else {
                            nav.addMsg("AI", aiText);
                            keepGoing = false;
                        }
                    }

                } catch (e) {
                    nav.addMsg("System", "API Error: " + e.message);
                }
                document.getElementById('sys-status').innerText = "READY";
            },

            callGemini: async (key, history) => {
                const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${key}`;
                const res = await fetch(url, {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({ contents: history })
                });
                return await res.json();
            },

            // --- HELPERS ---
            buildSystemPrompt: () => {
                return `You are a Context Navigator AI.
Goal: Answer the user's question about their codebase/files.
Constraint: You initially only see a FILE MAP (list of paths). You CANNOT see file contents unless you request them.

Protocol:
1. Analyze the User Question and the File Map.
2. If you need to read a file, output EXACTLY: "READ_REQUEST: path/to/file.ext" (one per line).
3. Stop and wait. The user (or system) will provide the content in the next message.
4. Once you have enough info, provide the final answer.
`;
            },

            readFileContent: async (path) => {
                try {
                    const handle = nav.fileHandleMap[path];
                    if (!handle) return `[ERROR] File not found in map: ${path}`;
                    const file = await handle.getFile();
                    return await file.text();
                } catch (e) {
                    return `[ERROR] Reading file: ${e.message}`;
                }
            },

            addMsg: (role, text, codeToCopy = null) => {
                const div = document.createElement('div');
                div.className = `msg ${role.toLowerCase()}`;
                let html = `<div class="bubble"><strong>${role}:</strong><br>${text.replace(/\n/g, '<br>')}</div>`;
                
                if (codeToCopy) {
                    html += `<div class="copy-box" onclick="nav.copyText(this)">${codeToCopy.replace(/</g, '&lt;')}</div>`;
                }
                div.innerHTML = html;
                document.getElementById('chat-history').appendChild(div);
                document.getElementById('chat-history').scrollTop = document.getElementById('chat-history').scrollHeight;
            },

            copyText: (el) => {
                const text = el.innerText;
                navigator.clipboard.writeText(text);
                const original = el.style.borderColor;
                el.style.borderColor = "#fff";
                setTimeout(() => el.style.borderColor = original, 200);
            }
        };

        window.nav = nav;
        nav.init();
    </script>
</body>
</html>

いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
ローカルファイルをAIに分析させる「Local Navigator」|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1