Full Code of reid41/QA-Pilot for AI

main 747a9ecb61a3 cached
35 files
173.1 KB
41.8k tokens
62 symbols
1 requests
Download .txt
Repository: reid41/QA-Pilot
Branch: main
Commit: 747a9ecb61a3
Files: 35
Total size: 173.1 KB

Directory structure:
gitextract_ot3ibsoy/

├── LICENSE
├── README.md
├── app.py
├── check_postgresql_connection.py
├── config/
│   ├── config.ini
│   └── prompt_templates.ini
├── llamacpp_models/
│   └── model_dir
├── parser.go
├── qa_model_apis.py
├── qa_pilot_run.py
├── requirements.txt
├── svelte-app/
│   ├── .gitignore
│   ├── README.md
│   ├── package.json
│   ├── public/
│   │   ├── global.css
│   │   └── index.html
│   ├── rollup.config.js
│   ├── scripts/
│   │   └── setupTypeScript.js
│   └── src/
│       ├── ApiKeyModal.svelte
│       ├── App.svelte
│       ├── Chat.svelte
│       ├── ConfigEditor.svelte
│       ├── DeleteConfirmationModal.svelte
│       ├── DeleteTemplateModal.svelte
│       ├── LlamaCppModelsModal.svelte
│       ├── NewSourceModal.svelte
│       ├── PromptTemplatesModal.svelte
│       ├── config.js
│       ├── global.css
│       └── main.js
├── templates/
│   ├── go_index.html
│   └── index.html
└── utils/
    ├── codegraph.py
    ├── go_codegraph.py
    └── helper.py

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
<p align="center">
  <img src="https://github.com/reid41/QA-Pilot/assets/25558653/4b45b525-5fac-4a3c-94e9-46364bdb36c3" alt="qa-pilot">
</p>

QA-Pilot is an interactive chat project that leverages online/local LLM for rapid understanding and navigation of GitHub code repository.

### Features

* Chat with github public repository with git clone way
* Store the chat history 
* Easy to set the configuration
* Multiple chat sessions
* Locate the session quicly with search function
* Integrate with `codegraph` to view the python file
* Support the different LLM models
    * ollama(deepseek, llama3.1, phi3, llama3, gemma2)
    * openai(gpt-4o, gpt-4-turbo, gpt-4, and gpt-3.5-turbo)
    * mistralai(mistral-tiny, mistral-tiny, mistral-small-latest, mistral-medium-latest, mistral-large-latest, codestral-lates)
    * localai(gpt-4, more)
    * zhipuai(glm-4-0520, glm-4, glm-4-air, glm-4-airx,  glm-4-flash)
    * anthropic(claude-3-opus-20240229, claude-3-sonnet-20240229, claude-3-haiku-20240307, claude-3-5-sonnet-20240620)
    * llamacpp
    * nvidia(meta/llama3-70b-instruct, more)
    * tongyi(qwen-turbo, qwen-plus, qwen-max, more)
    * moonshot(moonshot-v1-8k, moonshot-v1-32k, moonshot-v1-128k)


### Release

* 2024-07-03 update langchain to `0.2.6` version and add `moonshot` API support

* 2024-06-30  add `Go Codegraph`

* 2024-06-27  add `nvidia/tongyi` API support

* 2024-06-19  add `llamacpp` API support, improve the `settings` list in the sidebar and add upload model function for `llamacpp`, add `prompt templates` setting

* 2024-06-15  add `anthropic` API support, refactor some functions, and fix chat show messages

* 2024-06-12  add `zhipuai` API support

* 2024-06-10 Convert `flask` to `fastapi` and add `localai` API support

* 2024-06-07 Add `rr:` option and use `FlashRank` for the search 

* 2024-06-05 Upgrade `langchain` to `v0.2` and add `ollama embeddings`

* 2024-05-26 Release v2.0.1: Refactoring to replace `Streamlit` fontend with `Svelte` to improve the performance.

### Disclaimer

* This is a test project to validate the feasibility of a fully local solution for question answering using LLMs and Vector embeddings. It is not production ready, and it is not meant to be used in production. 
* `Do not use models for analyzing your critical or production data!!`
* `Do not use models for analyzing customer data to ensure data privacy and security!!`
* `Do not use models for analyzing you private/sensitivity code respository!!`

#### QA-Pilot
![qa-demo-new](https://github.com/reid41/QA-Pilot/assets/25558653/8198730f-32ec-4664-a10c-43b3f40c99ad)


#### CodeGraph
![code-graph-new](https://github.com/reid41/QA-Pilot/assets/25558653/8c47ea00-d703-42b5-b43b-d40796e7de1d)

To deploy QA-Pilot, you can follow the below steps:

1. Clone the QA-Pilot repository:

```shell
git clone https://github.com/reid41/QA-Pilot.git
cd QA-Pilot
```

2. Install [conda](https://www.anaconda.com/download) for virtual environment management. Create and activate a new virtual environment.

```shell
conda create -n QA-Pilot python=3.10.14
conda activate QA-Pilot
```

3. Install the required dependencies:

```shell
pip install -r requirements.txt
```

4. Install the pytorch with cuda [pytorch](https://pytorch.org/get-started/locally/)

5. Setup providers

* For setup [ollama website](https://ollama.com/) and [ollama github](https://github.com/ollama/ollama) to manage the local LLM. 
e.g.

```shell
ollama pull <model_name>

ollama list
```

* For setup [localAI](https://localai.io/) and [LocalAI github](https://github.com/mudler/LocalAI) to manage the local LLM, set the localAI `base_url` in config/config.ini.
e.g.
```shell
docker run -p 8080:8080 --name local-ai -ti localai/localai:latest-aio-cpu
# Do you have a Nvidia GPUs? Use this instead
# CUDA 11
# docker run -p 8080:8080 --gpus all --name local-ai -ti localai/localai:latest-aio-gpu-nvidia-cuda-11
# CUDA 12
# docker run -p 8080:8080 --gpus all --name local-ai -ti localai/localai:latest-aio-gpu-nvidia-cuda-12

# quick check the service with http://<localAI host>:8080/
# quick check the models with http://<localAI host>:8080/models/
```

* For setup llamacpp with [llama-cpp-python](https://github.com/abetlen/llama-cpp-python#windows-remarks)
  - upload the model to `llamacpp_models` dir or upload from the `llamacpp models` under the `Settings`
  - set the model in `llamacpp_llm_models` section in `config/config.ini`

* For setup API key in `.env`
  - [OpenAI](https://platform.openai.com/docs/overview): OPENAI_API_KEY='<openai_api_key,>'
  - [MistralAI](https://docs.mistral.ai/): MISTRAL_API_KEY='<mistralai_api_key>'
  - [ZhipuAI](https://open.bigmodel.cn/): ZHIPUAI_API_KEY='<zhipuai_api_key,>'
  - [Anthropic](https://console.anthropic.com/settings/keys): ANTHROPIC_API_KEY='<anthropic_api_key>'
  - [Nvidia](https://build.nvidia.com/explore/discover): NVIDIA_API_KEY='<nvidia_api_key>'
  - [TongYi](https://help.aliyun.com/document_detail/611472.html?spm=a2c4g.2399481.0.0): DASHSCOPE_API_KEY='<tongyi_api_key>'
  - [Moonshot](https://platform.moonshot.cn/): MOONSHOT_API_KEY='<moonshot_api_key>'

* For `Go codegraph`, make sure setup [GO](https://go.dev/doc/install) env, compile go file and test
```shell
go build -o parser parser.go

# test
./parser /path/test.go
```

6. Set the related parameters in `config/config.ini`, e.g. `model provider`, `model`, `variable`, `Ollama API url` and setup the [Postgresql](https://www.postgresql.org/download/) env
```shell
# create the db, e.g.
CREATE DATABASE qa_pilot_chatsession_db;
CREATE USER qa_pilot_user WITH ENCRYPTED PASSWORD 'qa_pilot_p';
GRANT ALL PRIVILEGES ON DATABASE qa_pilot_chatsession_db TO qa_pilot_user;

# set the connection
cat config/config.ini
[database]
db_name = qa_pilot_chatsession_db
db_user = qa_pilot_user
db_password = qa_pilot_p
db_host = localhost
db_port = 5432


# set the arg in script and test connection
python check_postgresql_connection.py
```

7. Download and install [node.js](https://nodejs.org/en/download/package-manager) and Set up the fontend env in one terminal
```shell
# make sure the backend server host ip is correct, localhost is by default
cat svelte-app/src/config.js
export const API_BASE_URL = 'http://localhost:5000';

# install deps
cd svelte-app
npm install

npm run dev
```

8. Run the backend QA-Pilot in another terminal:

```shell
python qa_pilot_run.py
```

### Tips
* Do not use url and upload at the same time.
* The remove button cannot really remove the local chromadb, need to remove it manually when stop it.
* Switch to `New Source Button` to add a new project
* Use `rsd:` to start the input and get the source document
* Use `rr:` to start the input and use the `FlashrankRerank` for the search
* Click `Open Code Graph` in `QA-Pilot` to view the code(make sure the the already in the project session and loaded before click), curretly support `python` and `go`



================================================
FILE: app.py
================================================
from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.templating import Jinja2Templates
import configparser
import os
from dotenv import load_dotenv, set_key
from utils.helper import (
    DataHandler,
    remove_directory,
    encode_kwargs,
    model_kwargs,
)
import psycopg2
from psycopg2 import sql
import ast 
from qa_model_apis import (
    get_chat_model,
    get_embedding_model,
)
from utils.codegraph import (
    parse_python_code,
    read_current_repo_path,
    build_file_tree,
)

from utils.go_codegraph import(
    parse_go_code,
    go_build_file_tree,
)

app = FastAPI()

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

config_path = os.path.join('config', 'config.ini')
prompt_templates_path = os.path.join('config', 'prompt_templates.ini')
config = configparser.ConfigParser()
config.read(config_path)

templates = Jinja2Templates(directory="templates")

DB_NAME = config['database']['db_name']
DB_USER = config['database']['db_user']
DB_PASSWORD = config['database']['db_password']
DB_HOST = config['database']['db_host']
DB_PORT = config['database']['db_port']

# for analyse code
current_session = None

current_model_info = {
    "provider": None,
    "model": None,
    "eb_provider": None,
    "eb_model": None,
    "chat_model": None,
    "embedding_model": None
}

def init_db():
    conn = psycopg2.connect(
        dbname=DB_NAME,
        user=DB_USER,
        password=DB_PASSWORD,
        host=DB_HOST,
        port=DB_PORT
    )
    cursor = conn.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS sessions (
            id BIGINT PRIMARY KEY,
            name TEXT NOT NULL,
            url TEXT NOT NULL
        )
    ''')
    conn.commit()
    # fix the first time to set the session
    cursor.execute('SELECT id, name, url FROM sessions LIMIT 1')
    session = cursor.fetchone()
    if session:
        global current_session
        current_session = {'id': session[0], 'name': session[1], 'url': session[2]}
        print("Default session set to:", current_session)
    conn.close()

def load_models_if_needed():
    selected_provider = config.get('model_providers', 'selected_provider')
    selected_model = config.get(f"{selected_provider}_llm_models", 'selected_model')
    eb_selected_provider = config.get('embedding_model_providers', 'selected_provider')
    eb_selected_model = config.get(f"{eb_selected_provider}_embedding_models", 'selected_model')
    
    if (current_model_info["provider"] != selected_provider or 
        current_model_info["model"] != selected_model or 
        current_model_info["eb_provider"] != eb_selected_provider or 
        current_model_info["eb_model"] != eb_selected_model):

        current_model_info["provider"] = selected_provider
        current_model_info["model"] = selected_model
        current_model_info["eb_provider"] = eb_selected_provider
        current_model_info["eb_model"] = eb_selected_model
        current_model_info["chat_model"] = get_chat_model(selected_provider, selected_model)
        current_model_info["embedding_model"] = get_embedding_model(eb_selected_provider, eb_selected_model, model_kwargs, encode_kwargs)
        print(f"Loaded new models: provider={selected_provider}, model={selected_model}")
    
    print(f"Loaded models: provider={selected_provider}, model={selected_model}")

def create_message_table(session_id):
    conn = psycopg2.connect(
        dbname=DB_NAME,
        user=DB_USER,
        password=DB_PASSWORD,
        host=DB_HOST,
        port=DB_PORT
    )
    cursor = conn.cursor()
    table_name = sql.Identifier(f'session_{session_id}')
    cursor.execute(sql.SQL('''
        CREATE TABLE IF NOT EXISTS {} (
            id BIGSERIAL PRIMARY KEY,
            sender TEXT NOT NULL,
            text TEXT NOT NULL
        )
    ''').format(table_name))
    conn.commit()
    conn.close()

init_db()

def load_config():
    config.read(config_path)
    return config

@app.get('/get_config')
async def get_config():
    config = load_config()
    config_dict = {section: dict(config.items(section)) for section in config.sections()}
    return JSONResponse(content=config_dict)

@app.post('/save_config')
async def save_config(request: Request):
    new_config = await request.json()
    config = load_config()
    for section, section_values in new_config.items():
        if not config.has_section(section):
            config.add_section(section)
        for key, value in section_values.items():
            config.set(section, key, value)
    with open(config_path, 'w') as configfile:
        config.write(configfile)
    return JSONResponse(content={"message": "Configuration saved successfully!"})

@app.post('/update_provider')
async def update_provider(request: Request):
    data = await request.json()
    selected_provider = data.get('selected_provider')
    config.set('model_providers', 'selected_provider', selected_provider)
    with open(config_path, 'w') as configfile:
        config.write(configfile)
    return JSONResponse(content={"message": "Provider updated successfully!"})

@app.post('/update_model')
async def update_model(request: Request):
    data = await request.json()
    selected_provider = data.get('selected_provider')
    selected_model = data.get('selected_model')
    config.set(f'{selected_provider}_llm_models', 'selected_model', selected_model)
    with open(config_path, 'w') as configfile:
        config.write(configfile)
    return JSONResponse(content={"message": "Model updated successfully!"})

@app.post('/load_repo')
async def load_repo(request: Request):
    data = await request.json()
    git_url = data.get('git_url')
    if not git_url:
        raise HTTPException(status_code=400, detail="Git URL is required")

    load_models_if_needed()
    chat_model = current_model_info["chat_model"]
    embedding_model = current_model_info["embedding_model"]
    data_handler = DataHandler(git_url, chat_model, embedding_model)
    try:
        data_handler.git_clone_repo()
        data_handler.load_into_db()
        return JSONResponse(content={"message": f"Repository {git_url} loaded successfully!"})
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post('/chat')
async def chat(request: Request):
    data = await request.json()
    load_models_if_needed()
    chat_model = current_model_info["chat_model"]
    embedding_model = current_model_info["embedding_model"]

    user_message = data.get('message')
    current_repo = data.get('current_repo')
    session_id = data.get('session_id')
    if not user_message or not current_repo or not session_id:
        raise HTTPException(status_code=400, detail="Message, current_repo and session_id are required")

    try:
        data_handler = DataHandler(current_repo, chat_model, embedding_model)
        data_handler.load_into_db()
        rsd = False
        rr = False

        # return source documents
        if user_message.startswith('rsd:'):
            user_message = user_message[4:].strip()
            rsd = True
        # use reranker
        elif user_message.startswith('rr:'):
            user_message = user_message[3:].strip()
            rr = True
        bot_response = data_handler.retrieval_qa(user_message, rsd=rsd, rr=rr)

        # Save user message and bot response to the session table
        conn = psycopg2.connect(
            dbname=DB_NAME,
            user=DB_USER,
            password=DB_PASSWORD,
            host=DB_HOST,
            port=DB_PORT
        )
        cursor = conn.cursor()
        table_name = sql.Identifier(f'session_{session_id}')
        cursor.execute(sql.SQL('INSERT INTO {} (sender, text) VALUES (%s, %s)').format(table_name), ('You', user_message))
        cursor.execute(sql.SQL('INSERT INTO {} (sender, text) VALUES (%s, %s)').format(table_name), ('QA-Pilot', bot_response))
        conn.commit()
        conn.close()

        return JSONResponse(content={"response": bot_response})
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get('/sessions')
async def get_sessions():
    conn = psycopg2.connect(
        dbname=DB_NAME,
        user=DB_USER,
        password=DB_PASSWORD,
        host=DB_HOST,
        port=DB_PORT
    )
    cursor = conn.cursor()
    cursor.execute('SELECT id, name, url FROM sessions')
    sessions = [{'id': row[0], 'name': row[1], 'url': row[2]} for row in cursor.fetchall()]
    conn.close()
    print(f"Fetched sessions from DB: {sessions}")
    return JSONResponse(content=sessions)

@app.post('/sessions')
async def save_sessions(request: Request):
    sessions = await request.json()
    print(f"Received sessions to save: {sessions}")
    conn = psycopg2.connect(
        dbname=DB_NAME,
        user=DB_USER,
        password=DB_PASSWORD,
        host=DB_HOST,
        port=DB_PORT
    )
    cursor = conn.cursor()
    for session in sessions:
        cursor.execute('INSERT INTO sessions (id, name, url) VALUES (%s, %s, %s) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, url = EXCLUDED.url',
                       (session['id'], session['name'], session['url']))
        create_message_table(session['id'])
    conn.commit()
    conn.close()
    print("Saved sessions to DB")
    return JSONResponse(content={"message": "Sessions saved successfully!"})

@app.get('/messages/{session_id}')
async def get_messages(session_id: int):
    conn = psycopg2.connect(
        dbname=DB_NAME,
        user=DB_USER,
        password=DB_PASSWORD,
        host=DB_HOST,
        port=DB_PORT
    )
    cursor = conn.cursor()
    table_name = sql.Identifier(f'session_{session_id}')
    cursor.execute(sql.SQL('SELECT sender, text FROM {}').format(table_name))
    messages = [{'sender': row[0], 'text': row[1]} for row in cursor.fetchall()]
    conn.close()
    print(f"Fetched messages from session {session_id}")
    return JSONResponse(content=messages)

@app.post('/update_current_session')
async def update_current_session(request: Request):
    global current_session
    current_session = await request.json()
    return JSONResponse(content={"message": "Current session updated successfully!"})

@app.delete('/sessions/{session_id}')
async def delete_session(session_id: int):
    print(f"Deleting session with ID: {session_id}")

    conn = psycopg2.connect(
        dbname=DB_NAME,
        user=DB_USER,
        password=DB_PASSWORD,
        host=DB_HOST,
        port=DB_PORT
    )
    cursor = conn.cursor()

    try:
        cursor.execute('SELECT name FROM sessions WHERE id = %s', (session_id,))
        session = cursor.fetchone()
        if session:
            session_name = session[0]
            print("anem", session_name)
        cursor.execute('DELETE FROM sessions WHERE id = %s', (session_id,))
        conn.commit()
        cursor.execute(sql.SQL('DROP TABLE IF EXISTS {}').format(sql.Identifier(f'session_{session_id}')))
        conn.commit()
        # remove the git clone project
        remove_project_path = os.path.join("projects", session_name)
        remove_directory(remove_project_path)
        print("Session deleted successfully")
        return JSONResponse(content={"message": "Session deleted successfully!"})
    except Exception as e:
        print(f"Error deleting session: {e}")
        raise HTTPException(status_code=500, detail=str(e))
    finally:
        conn.close()

# api key handling functions
@app.post('/check_api_key')
async def check_api_key(request: Request):
    data = await request.json()
    provider = data.get('provider')
    key_var = f"{provider.upper()}_API_KEY"
    
    load_dotenv()
    api_key = os.getenv(key_var)
    
    return JSONResponse(content={'exists': bool(api_key)})

@app.post('/save_api_key')
async def save_api_key(request: Request):
    data = await request.json()
    provider = data.get('provider')
    api_key = data.get('api_key')
    key_var = f"{provider.upper()}_API_KEY"
    
    dotenv_path = '.env'
    set_key(dotenv_path, key_var, api_key)
    load_dotenv()
    
    return JSONResponse(content={'message': 'API Key saved successfully!'})


# hanlde llamacpp models(list/upload/delete)
@app.get('/llamacpp_models')
async def list_llamacpp_models():
    upload_dir = "llamacpp_models"
    if not os.path.exists(upload_dir):
        return JSONResponse(content=[])
    models = os.listdir(upload_dir)
    return JSONResponse(content=models)


@app.post('/llamacpp_models', status_code=201)
async def upload_llamacpp_model(file: UploadFile = File(...), chunk: int = Form(...), totalChunks: int = Form(...)):
    try:
        upload_dir = "llamacpp_models"
        os.makedirs(upload_dir, exist_ok=True)
        chunk_dir = os.path.join(upload_dir, "chunks")
        os.makedirs(chunk_dir, exist_ok=True)
        chunk_file_path = os.path.join(chunk_dir, f"{file.filename}.part{chunk}")

        with open(chunk_file_path, "wb") as f:
            f.write(await file.read())

        # Check if all chunks are uploaded
        if len(os.listdir(chunk_dir)) == totalChunks:
            final_file_path = os.path.join(upload_dir, file.filename)
            with open(final_file_path, "wb") as final_file:
                for i in range(totalChunks):
                    chunk_file_path = os.path.join(chunk_dir, f"{file.filename}.part{i}")
                    with open(chunk_file_path, "rb") as chunk_file:
                        final_file.write(chunk_file.read())
                    os.remove(chunk_file_path)
            os.rmdir(chunk_dir)  # Remove the chunks directory

        return JSONResponse(content={"message": "Chunk uploaded successfully!"})
    except Exception as e:
        print(f"Error uploading model: {e}")
        raise HTTPException(status_code=500, detail="Failed to upload chunk")
    

@app.delete('/llamacpp_models/{model_name}')
async def delete_llamacpp_model(model_name: str):
    file_path = os.path.join("llamacpp_models", model_name)
    if os.path.exists(file_path):
        os.remove(file_path)
        return JSONResponse(content={"message": "Model deleted successfully!"})
    else:
        raise HTTPException(status_code=404, detail="Model not found")

# handle prompt templates
@app.get('/get_prompt_templates')
async def get_prompt_templates():
    prompt_templates_path = os.path.join('config', 'prompt_templates.ini')
    templates_config = configparser.ConfigParser()

    if not os.path.exists(prompt_templates_path):
        return JSONResponse(content={})

    with open(prompt_templates_path, 'r', encoding='utf-8') as file:
        file_content = file.read()
        # Ensure the content has a section header
        if '[qa_prompt_templates]' not in file_content:
            file_content = '[qa_prompt_templates]\n' + file_content

    try:
        templates_config.read_string(file_content)
    except configparser.ParsingError as e:
        print(f"Error parsing config: {e}")
        raise HTTPException(status_code=500, detail="Error parsing prompt templates")

    templates = {k: v.replace('\\n', '\n') for k, v in templates_config.items('qa_prompt_templates')}
    return JSONResponse(content=templates)


@app.post('/delete_prompt_template')
async def delete_prompt_template(request: Request):
    data = await request.json()
    template_name = data.get('template_name')

    prompt_templates_path = os.path.join('config', 'prompt_templates.ini')
    templates_config = configparser.ConfigParser()
    templates_config.read(prompt_templates_path)

    if template_name in templates_config['qa_prompt_templates']:
        templates_config.remove_option('qa_prompt_templates', template_name)
        with open(prompt_templates_path, 'w', encoding='utf-8') as configfile:
            templates_config.write(configfile)
        return JSONResponse(content={"message": "Template deleted successfully!"})
    else:
        raise HTTPException(status_code=404, detail="Template not found")


@app.post('/save_prompt_templates')
async def save_prompt_templates(request: Request):
    new_templates = await request.json()
    prompt_templates_path = os.path.join('config', 'prompt_templates.ini')
    templates_config = configparser.ConfigParser()
    templates_config['qa_prompt_templates'] = {k: v.replace('\n', '\\n') for k, v in new_templates.items()}

    with open(prompt_templates_path, 'w', encoding='utf-8') as configfile:
        templates_config.write(configfile)
    return JSONResponse(content={"message": "Templates saved successfully!"})

#############################python codegraph############################
@app.get('/codegraph')
async def codegraph_home(request: Request):
    return templates.TemplateResponse('index.html', {'request': request})


@app.get('/data')
async def data(filepath: str):
    code_data = parse_python_code(filepath)  # Ensure the path points to your Python code file
    return JSONResponse(content=code_data)

@app.get('/directory')
async def directory():
    current_repo_path = read_current_repo_path(current_session)
    if current_repo_path is None:
        raise HTTPException(status_code=404, detail="Repository path not set or not found")
    dir_tree = build_file_tree(current_repo_path)  # Ensure the path points to your code directory
    return JSONResponse(content=dir_tree)

@app.post('/analyze')
async def analyze(request: Request):
    data = await request.json()
    load_models_if_needed()
    chat_model = current_model_info["chat_model"]
    embedding_model = current_model_info["embedding_model"]
    code = data.get('code', '')
    # send the code to LLM
    data_handler = DataHandler(git_url='', chat_model=chat_model, embedding_model=embedding_model)
    code_analysis = data_handler.restrieval_qa_for_code(code)
    return JSONResponse(content={'analysis': code_analysis})

#####go codegraph#####
@app.get('/go_codegraph')
async def go_codegraph_home(request: Request):
    return templates.TemplateResponse('go_index.html', {'request': request})

@app.get('/go_data')
async def go_data(filepath: str):
    if os.path.isdir(filepath):
        raise HTTPException(status_code=400, detail="The specified path is a directory, not a file.")
    code_data = parse_go_code(filepath)
    return JSONResponse(content=code_data)

@app.get('/go_directory')
async def directory():
    current_repo_path = read_current_repo_path(current_session)
    if current_repo_path is None:
        raise HTTPException(status_code=404, detail="Repository path not set or not found")
    dir_tree = go_build_file_tree(current_repo_path)  # Ensure the path points to your code directory
    return JSONResponse(content=dir_tree)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=5003, log_level="debug")


================================================
FILE: check_postgresql_connection.py
================================================
import psycopg2
import configparser

config = configparser.ConfigParser()
config.read('config/config.ini')
db_config = config['database']

try:
    conn = psycopg2.connect(
        dbname=db_config['db_name'],
        user=db_config['db_user'],
        password=db_config['db_password'],
        host=db_config['db_host'],
        port=db_config['db_port']
    )
    print("Connection successful")
    cur = conn.cursor()
    cur.execute("SELECT version();")
    db_version = cur.fetchone()
    print(f"Database version: {db_version}")
    cur.close()
    conn.close()
except psycopg2.Error as e:
    print(f"Error: {e}")

================================================
FILE: config/config.ini
================================================
[app_setting]
version = v2.15.6

[model_providers]
provider_list = openai, ollama, mistralai, localai, zhipuai, anthropic, llamacpp, nvidia, tongyi, moonshot
selected_provider = ollama

[openai_llm_models]
model_list = gpt-3.5-turbo, gpt-4o
selected_model = gpt-4o

[mistralai_llm_models]
model_list = mistral-tiny, mistral-small
selected_model = mistral-small

[ollama_llm_models]
model_list = qwen2.5-coder:14b, qwen2.5:14b, deepseek-r1:14b
selected_model = qwen2.5:14b
base_url = http://localhost:11434

[localai_llm_models]
model_list = gpt-4
selected_model = gpt-4
base_url = http://localhost:8080/v1

[zhipuai_llm_models]
model_list = glm-4
selected_model = glm-4

[anthropic_llm_models]
model_list = claude-3-opus-20240229
selected_model = claude-3-opus-20240229

[llamacpp_llm_models]
model_list = 
selected_model = 

[nvidia_llm_models]
model_list = meta/llama3-70b-instruct
selected_model = meta/llama3-70b-instruct

[tongyi_llm_models]
model_list = qwen-turbo
selected_model = qwen-turbo

[moonshot_llm_models]
model_list = moonshot-v1-8k, moonshot-v1-32k, moonshot-v1-128k
selected_model = moonshot-v1-8k

[embedding_model_providers]
provider_list = huggingface, ollama
selected_provider = huggingface

[huggingface_embedding_models]
model_list = sentence-transformers/all-MiniLM-L6-v2
selected_model = sentence-transformers/all-MiniLM-L6-v2

[ollama_embedding_models]
model_list = nomic-embed-text, mxbai-embed-large
selected_model = nomic-embed-text

[prompt_templates]
qa_selected_prompt = qa_template
code_selected_prompt = code_template
localai_selected_prompt = code_template_localai

[the_project_dirs]
vectorstore_dir = VectorStore
sessions_dir = sessions
project_dir = projects

[for_loop_dirs_depth]
max_dir_depth = 5

[chunk_setting]
chunk_size = 3000
chunk_overlap = 200

[database]
db_name = qa_pilot_chatsession_db
db_user = qa_pilot_user
db_password = qa_pilot_p
db_host = localhost
db_port = 5432



================================================
FILE: config/prompt_templates.ini
================================================
[qa_prompt_templates]
qa_template = """I want you to act as a very senior code developer who is familar with github/gitlab community. I will provide you the code project, you need to provide answers which are based on the project. {context}"""
code_template = """I want you to act as a Senior Python developer. I will provide you the code project, you provide detailed exaplanation. Human: {input}History: {history} AI:"""
code_template_localai = """I want you to act as a Senior Python developer. I will provide you the code project, you provide detailed exaplanation. Human: {input} AI:"""



================================================
FILE: llamacpp_models/model_dir
================================================


================================================
FILE: parser.go
================================================
package main

import (
	"encoding/json"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"strings"
)

type Node struct {
	Name     string
	Type     string   // "func", "method", "type", "interface", "int", or "import"
	Calls    []string // list of called functions/methods
	Code     string   // source code of the node
	Position string   // file position of the node
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: ./parser <path_to_go_file>")
		os.Exit(1)
	}

	filePath := os.Args[1]
	fset := token.NewFileSet()
	node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	nodes := make(map[string]*Node)
	ast.Inspect(node, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.FuncDecl:
			funcName := x.Name.Name
			funcType := "func"
			if x.Recv != nil {
				recvType := fmt.Sprintf("%s", x.Recv.List[0].Type)
				if len(recvType) > 1 && recvType[0] == '*' {
					recvType = recvType[1:]
				}
				funcType = "method"
				funcName = fmt.Sprintf("%s.%s", cleanType(recvType), funcName)
			}
			pos := fset.Position(x.Pos())
			code := getNodeCode(fset, x.Pos(), x.End(), filePath)
			nodes[funcName] = &Node{
				Name:     funcName,
				Type:     funcType,
				Calls:    []string{},
				Code:     code,
				Position: pos.String(),
			}
		case *ast.GenDecl:
			if x.Tok == token.TYPE {
				for _, spec := range x.Specs {
					typeSpec := spec.(*ast.TypeSpec)
					typeName := typeSpec.Name.Name
					pos := fset.Position(typeSpec.Pos())
					code := getNodeCode(fset, typeSpec.Pos(), typeSpec.End(), filePath)
					nodes[typeName] = &Node{
						Name:     typeName,
						Type:     "type",
						Calls:    []string{},
						Code:     code,
						Position: pos.String(),
					}
				}
			} else if x.Tok == token.IMPORT {
				for _, spec := range x.Specs {
					importSpec := spec.(*ast.ImportSpec)
					importPath := importSpec.Path.Value
					pos := fset.Position(importSpec.Pos())
					code := importSpec.Path.Value
					nodes[importPath] = &Node{
						Name:     importPath,
						Type:     "import",
						Calls:    []string{},
						Code:     code,
						Position: pos.String(),
					}
				}
			}
		}
		return true
	})

	// Collect function and method calls
	ast.Inspect(node, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.CallExpr:
			caller := ""
			if sel, ok := x.Fun.(*ast.SelectorExpr); ok {
				if ident, ok := sel.X.(*ast.Ident); ok {
					caller = fmt.Sprintf("%s.%s", ident.Name, sel.Sel.Name)
				}
			} else if ident, ok := x.Fun.(*ast.Ident); ok {
				caller = ident.Name
			}
			if caller != "" {
				if parentFunc := getParentFunc(node, x.Pos(), fset); parentFunc != "" {
					if _, ok := nodes[parentFunc]; ok {
						nodes[parentFunc].Calls = append(nodes[parentFunc].Calls, caller)
					}
				}
			}
		}
		return true
	})

	jsonOutput, err := json.Marshal(nodes)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Println(string(jsonOutput))
}

func getNodeCode(fset *token.FileSet, start, end token.Pos, filePath string) string {
	startOffset := fset.Position(start).Offset
	endOffset := fset.Position(end).Offset
	code, _ := os.ReadFile(filePath)
	return string(code[startOffset:endOffset])
}

func getParentFunc(node *ast.File, pos token.Pos, fset *token.FileSet) string {
	var parentFunc string
	ast.Inspect(node, func(n ast.Node) bool {
		if fd, ok := n.(*ast.FuncDecl); ok {
			if fd.Pos() < pos && fd.End() > pos {
				if fd.Recv != nil {
					recvType := fmt.Sprintf("%s", fd.Recv.List[0].Type)
					if len(recvType) > 1 && recvType[0] == '*' {
						recvType = recvType[1:]
					}
					parentFunc = fmt.Sprintf("%s.%s", cleanType(recvType), fd.Name.Name)
				} else {
					parentFunc = fd.Name.Name
				}
				return false
			}
		}
		return true
	})
	return parentFunc
}

func cleanType(typeName string) string {
	// Remove generic type parameters if present
	return strings.TrimSpace(strings.Split(typeName, "[")[0])
}


================================================
FILE: qa_model_apis.py
================================================
from langchain_community.chat_models import ChatOllama
from langchain_mistralai.chat_models import ChatMistralAI
from langchain_openai import ChatOpenAI
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_community.embeddings import OllamaEmbeddings
import os
import configparser
from dotenv import load_dotenv
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from llama_index.llms.openai_like import OpenAILike
from langchain_community.chat_models import ChatZhipuAI
from langchain_anthropic import ChatAnthropic
import multiprocessing
from langchain_community.chat_models import ChatLlamaCpp
from langchain_nvidia_ai_endpoints import ChatNVIDIA
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_community.chat_models.moonshot import MoonshotChat

# read from the config.ini
config_path = os.path.join('config', 'config.ini')
config = configparser.ConfigParser()
config.read(config_path)
ollama_base_url = config.get('ollama_llm_models', 'base_url')
localai_base_url = config.get('localai_llm_models', 'base_url')


# get the chat model from config
def get_chat_model(provider, model_name=''):    
    if provider == 'ollama':
        return ChatOllama(
            base_url=ollama_base_url,
            model=model_name,
            streaming=True,
            callbacks=[StreamingStdOutCallbackHandler()]
        )
    elif provider == 'openai':
        load_dotenv()
        return ChatOpenAI(model_name=model_name)
    elif provider == 'mistralai':
        load_dotenv()
        return ChatMistralAI(model_name=model_name)
    elif provider == 'localai':
        return OpenAILike(  
            api_base=localai_base_url,  
            api_key="qa_pilot",  
            is_chat_model=True,  
            context_window=32768,  
            model=model_name
        )
    elif provider == 'zhipuai':
        load_dotenv()
        return ChatZhipuAI(
            model=model_name,
            temperature=0.5,
        )
    elif provider == 'anthropic':
        load_dotenv()
        return ChatAnthropic(
            model=model_name
        )
    elif provider == 'llamacpp':
        local_model = os.path.join("llamacpp_models", model_name)
        return ChatLlamaCpp(
            temperature=0.5,
            model_path=local_model,
            n_ctx=10000,
            n_gpu_layers=8,
            n_batch=300,  # Should be between 1 and n_ctx, consider the amount of VRAM in your GPU.
            max_tokens=512,
            n_threads=multiprocessing.cpu_count() - 1,
            repeat_penalty=1.5,
            top_p=0.5,
            verbose=True,
        )
    elif provider == 'nvidia':
        load_dotenv()
        return ChatNVIDIA(
            model=model_name
        )
    elif provider == 'tongyi':
        load_dotenv()
        return ChatTongyi(
            model=model_name
        ) 
    elif provider == 'moonshot':
        load_dotenv()
        return MoonshotChat(
            model=model_name
        ) 
    else:
        raise ValueError(f"Unsupported model provider: {provider}")
    

def get_embedding_model(eb_provider, model_name='', model_kwargs='', encode_kwargs=''):
     if eb_provider == 'huggingface': 
        return HuggingFaceEmbeddings(
                model_name=model_name,
                model_kwargs=model_kwargs,
                encode_kwargs=encode_kwargs,
                cache_folder="./cache_embeddings/"
            )
     elif eb_provider == 'ollama':
         return OllamaEmbeddings(
                model=model_name,
                model_kwargs=model_kwargs
         )
     else:
        raise ValueError(f"Unsupported embedding model provider: {eb_provider}")

================================================
FILE: qa_pilot_run.py
================================================
import uvicorn
from app import app

if __name__ == "__main__":
    print("Starting server on http://0.0.0.0:5000")
    uvicorn.run(app, host="0.0.0.0", port=5000)

================================================
FILE: requirements.txt
================================================
aiohttp==3.9.3
aiosignal==1.3.1
altair==5.2.0
annotated-types==0.6.0
anthropic==0.28.1
anyio==4.3.0
asgiref==3.8.1
async-timeout==4.0.3
attrs==23.2.0
backoff==2.2.1
bcrypt==4.1.2
blinker==1.7.0
boto3==1.34.71
botocore==1.34.71
build==1.1.1
cachetools==5.3.3
certifi==2024.2.2
charset-normalizer==3.3.2
chroma-hnswlib==0.7.3
chromadb==0.4.24
click==8.1.7
colorama==0.4.6
coloredlogs==15.0.1
dashscope==1.20.0
dataclasses-json==0.6.4
defusedxml==0.7.1
Deprecated==1.2.14
dill==0.3.8
dirtyjson==1.0.8
diskcache==5.6.3
distro==1.9.0
exceptiongroup==1.2.0
fastapi==0.110.0
filelock==3.13.3
FlashRank==0.2.5
Flask==3.0.3
Flask-Cors==4.0.1
flatbuffers==24.3.25
frozenlist==1.4.1
fsspec==2024.3.1
gitdb==4.0.11
GitPython==3.1.42
google-auth==2.29.0
googleapis-common-protos==1.63.0
greenlet==3.0.3
grpcio==1.62.1
h11==0.14.0
httpcore==1.0.4
httptools==0.6.1
httpx==0.27.0
httpx-sse==0.4.0
huggingface-hub==0.23.2
humanfriendly==10.0
humbug==0.3.2
idna==3.6
importlib-metadata==6.11.0
importlib_resources==6.4.0
intel-openmp==2021.4.0
itsdangerous==2.2.0
Jinja2==3.1.3
jiter==0.4.2
jmespath==1.0.1
joblib==1.3.2
jsonpatch==1.33
jsonpointer==2.4
jsonschema==4.21.1
jsonschema-specifications==2023.12.1
kubernetes==29.0.0
langchain==0.2.6
langchain-anthropic==0.1.15
langchain-chroma==0.1.1
langchain-community==0.2.6
langchain-core==0.2.11
langchain-huggingface==0.0.2
langchain-mistralai==0.1.8
langchain-nvidia-ai-endpoints==0.1.2
langchain-openai==0.1.8
langchain-text-splitters==0.2.1
langsmith==0.1.80
llama-index-core==0.10.43.post1
llama-index-llms-openai==0.1.22
llama-index-llms-openai-like==0.1.3
llama_cpp_python==0.2.67
llamaindex-py-client==0.1.19
lz4==4.3.3
markdown-it-py==3.0.0
MarkupSafe==2.1.5
marshmallow==3.21.1
mdurl==0.1.2
mkl==2021.4.0
mmh3==4.1.0
monotonic==1.6
mpmath==1.3.0
multidict==6.0.5
multiprocess==0.70.16
mypy-extensions==1.0.0
nest-asyncio==1.6.0
networkx==3.2.1
nltk==3.8.1
numpy==1.26.4
oauthlib==3.2.2
onnxruntime==1.17.1
openai==1.28.0
opentelemetry-api==1.23.0
opentelemetry-exporter-otlp-proto-common==1.23.0
opentelemetry-exporter-otlp-proto-grpc==1.23.0
opentelemetry-instrumentation==0.44b0
opentelemetry-instrumentation-asgi==0.44b0
opentelemetry-instrumentation-fastapi==0.44b0
opentelemetry-proto==1.23.0
opentelemetry-sdk==1.23.0
opentelemetry-semantic-conventions==0.44b0
opentelemetry-util-http==0.44b0
orjson==3.9.15
overrides==7.7.0
packaging==23.2
pandas==2.2.1
pathos==0.3.2
pillow==10.2.0
posthog==3.5.0
pox==0.3.4
ppft==1.7.6.8
protobuf==4.25.3
psycopg2-binary==2.9.9
pulsar-client==3.4.0
pyarrow==15.0.2
pyasn1==0.6.0
pyasn1_modules==0.4.0
pydantic==2.6.4
pydantic_core==2.16.3
pydeck==0.8.1b0
Pygments==2.17.2
PyJWT==2.8.0
PyPika==0.48.9
pyproject_hooks==1.0.0
pyreadline3==3.4.1
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-multipart==0.0.9
pytz==2024.1
PyYAML==6.0.1
referencing==0.34.0
regex==2023.12.25
requests==2.31.0
requests-oauthlib==2.0.0
rich==13.7.1
rpds-py==0.18.0
rsa==4.9
s3transfer==0.10.1
safetensors==0.4.2
scikit-learn==1.4.1.post1
scipy==1.12.0
sentence-transformers==2.6.1
six==1.16.0
smmap==5.0.1
sniffio==1.3.1
SQLAlchemy==2.0.29
starlette==0.36.3
streamlit==1.32.2
sympy==1.12
tbb==2021.12.0
tenacity==8.2.3
text-generation==0.7.0
threadpoolctl==3.4.0
tiktoken==0.7.0
tokenizers==0.19.1
toml==0.10.2
tomli==2.0.1
toolz==0.12.1
tornado==6.4
tqdm==4.66.2
transformers==4.41.2
typer==0.11.0
typing-inspect==0.9.0
typing_extensions==4.10.0
tzdata==2024.1
urllib3==2.2.1
uvicorn==0.29.0
waitress==3.0.0
watchdog==4.0.0
watchfiles==0.21.0
websocket-client==1.7.0
websockets==12.0
Werkzeug==3.0.3
wrapt==1.16.0
yarl==1.9.4
zipp==3.18.1


================================================
FILE: svelte-app/.gitignore
================================================
/node_modules/
/public/build/

.DS_Store


================================================
FILE: svelte-app/README.md
================================================
This repo is no longer maintained. Consider using `npm init vite` and selecting the `svelte` option or — if you want a full-fledged app framework — use [SvelteKit](https://kit.svelte.dev), the official application framework for Svelte.

---

# svelte app

This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.

To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):

```bash
npx degit sveltejs/template svelte-app
cd svelte-app
```

*Note that you will need to have [Node.js](https://nodejs.org) installed.*


## Get started

Install the dependencies...

```bash
cd svelte-app
npm install
```

...then start [Rollup](https://rollupjs.org):

```bash
npm run dev
```

Navigate to [localhost:8080](http://localhost:8080). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.

By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.

If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.

## Building and running in production mode

To create an optimised version of the app:

```bash
npm run build
```

You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).


## Single-page app mode

By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.

If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:

```js
"start": "sirv public --single"
```

## Using TypeScript

This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:

```bash
node scripts/setupTypeScript.js
```

Or remove the script via:

```bash
rm scripts/setupTypeScript.js
```

If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte).

## Deploying to the web

### With [Vercel](https://vercel.com)

Install `vercel` if you haven't already:

```bash
npm install -g vercel
```

Then, from within your project folder:

```bash
cd public
vercel deploy --name my-project
```

### With [surge](https://surge.sh/)

Install `surge` if you haven't already:

```bash
npm install -g surge
```

Then, from within your project folder:

```bash
npm run build
surge public my-project.surge.sh
```


================================================
FILE: svelte-app/package.json
================================================
{
  "name": "svelte-app",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "start": "sirv public --no-clear --port 5001 --host 0.0.0.0"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^24.0.0",
    "@rollup/plugin-node-resolve": "^15.0.0",
    "@rollup/plugin-terser": "^0.4.4",
    "rollup": "^3.15.0",
    "rollup-plugin-css-only": "^4.3.0",
    "rollup-plugin-livereload": "^2.0.0",
    "rollup-plugin-svelte": "^7.1.2",
    "svelte": "^3.55.0"
  },
  "dependencies": {
    "cytoscape": "^3.29.2",
    "cytoscape-dagre": "^2.5.0",
    "highlight.js": "^11.9.0",
    "marked": "^12.0.2",
    "sirv-cli": "^2.0.0",
    "svelte-spa-router": "^4.0.1"
  }
}


================================================
FILE: svelte-app/public/global.css
================================================
html, body {
	position: relative;
	width: 100%;
	height: 100%;
}

body {
	color: #333;
	margin: 0;
	padding: 8px;
	box-sizing: border-box;
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}

a {
	color: rgb(0,100,200);
	text-decoration: none;
}

a:hover {
	text-decoration: underline;
}

a:visited {
	color: rgb(0,80,160);
}

label {
	display: block;
}

input, button, select, textarea {
	font-family: inherit;
	font-size: inherit;
	-webkit-padding: 0.4em 0;
	padding: 0.4em;
	margin: 0 0 0.5em 0;
	box-sizing: border-box;
	border: 1px solid #ccc;
	border-radius: 2px;
}

input:disabled {
	color: #ccc;
}

button {
	color: #333;
	background-color: #f4f4f4;
	outline: none;
}

button:disabled {
	color: #999;
}

button:not(:disabled):active {
	background-color: #ddd;
}

button:focus {
	border-color: #666;
}


================================================
FILE: svelte-app/public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset='utf-8'>
	<meta name='viewport' content='width=device-width,initial-scale=1'>

	<title>Svelte app</title>

	<link rel='icon' type='image/png' href='/favicon.png'>
	<link rel='stylesheet' href='/global.css'>
	<link rel='stylesheet' href='/build/bundle.css'>

	<script defer src='/build/bundle.js'></script>
</head>

<body>
</body>
</html>


================================================
FILE: svelte-app/rollup.config.js
================================================
import { spawn } from 'child_process';
import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import css from 'rollup-plugin-css-only';

const production = !process.env.ROLLUP_WATCH;

function serve() {
	let server;

	function toExit() {
		if (server) server.kill(0);
	}

	return {
		writeBundle() {
			if (server) return;
			server = spawn('npm', ['run', 'start', '--', '--dev'], {
				stdio: ['ignore', 'inherit', 'inherit'],
				shell: true
			});

			process.on('SIGTERM', toExit);
			process.on('exit', toExit);
		}
	};
}

export default {
	input: 'src/main.js',
	output: {
		sourcemap: true,
		format: 'iife',
		name: 'app',
		file: 'public/build/bundle.js'
	},
	plugins: [
		svelte({
			compilerOptions: {
				// enable run-time checks when not in production
				dev: !production
			}
		}),
		// we'll extract any component CSS out into
		// a separate file - better for performance
		css({ output: 'bundle.css' }),

		// If you have external dependencies installed from
		// npm, you'll most likely need these plugins. In
		// some cases you'll need additional configuration -
		// consult the documentation for details:
		// https://github.com/rollup/plugins/tree/master/packages/commonjs
		resolve({
			browser: true,
			dedupe: ['svelte'],
			exportConditions: ['svelte']
		}),
		commonjs(),

		// In dev mode, call `npm run start` once
		// the bundle has been generated
		!production && serve(),

		// Watch the `public` directory and refresh the
		// browser on changes when not in production
		!production && livereload('public'),

		// If we're building for production (npm run build
		// instead of npm run dev), minify
		production && terser()
	],
	watch: {
		clearScreen: false
	}
};


================================================
FILE: svelte-app/scripts/setupTypeScript.js
================================================
// @ts-check

/** This script modifies the project to support TS code in .svelte files like:

  <script lang="ts">
  	export let name: string;
  </script>
 
  As well as validating the code for CI.
  */

/**  To work on this script:
  rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
*/

import fs from "fs"
import path from "path"
import { argv } from "process"
import url from 'url';

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const projectRoot = argv[2] || path.join(__dirname, "..")

// Add deps to pkg.json
const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
  "svelte-check": "^3.0.0",
  "svelte-preprocess": "^5.0.0",
  "@rollup/plugin-typescript": "^11.0.0",
  "typescript": "^4.9.0",
  "tslib": "^2.5.0",
  "@tsconfig/svelte": "^3.0.0"
})

// Add script for checking
packageJSON.scripts = Object.assign(packageJSON.scripts, {
  "check": "svelte-check"
})

// Write the package JSON
fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, "  "))

// mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
fs.renameSync(beforeMainJSPath, afterMainTSPath)

// Switch the app.svelte file to use TS
const appSveltePath = path.join(projectRoot, "src", "App.svelte")
let appFile = fs.readFileSync(appSveltePath, "utf8")
appFile = appFile.replace("<script>", '<script lang="ts">')
appFile = appFile.replace("export let name;", 'export let name: string;')
fs.writeFileSync(appSveltePath, appFile)

// Edit rollup config
const rollupConfigPath = path.join(projectRoot, "rollup.config.js")
let rollupConfig = fs.readFileSync(rollupConfigPath, "utf8")

// Edit imports
rollupConfig = rollupConfig.replace(`'rollup-plugin-css-only';`, `'rollup-plugin-css-only';
import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';`)

// Replace name of entry point
rollupConfig = rollupConfig.replace(`'src/main.js'`, `'src/main.ts'`)

// Add preprocessor
rollupConfig = rollupConfig.replace(
  'compilerOptions:',
  'preprocess: sveltePreprocess({ sourceMap: !production }),\n\t\t\tcompilerOptions:'
);

// Add TypeScript
rollupConfig = rollupConfig.replace(
  'commonjs(),',
  'commonjs(),\n\t\ttypescript({\n\t\t\tsourceMap: !production,\n\t\t\tinlineSources: !production\n\t\t}),'
);
fs.writeFileSync(rollupConfigPath, rollupConfig)

// Add svelte.config.js
const tsconfig = `{
  "extends": "@tsconfig/svelte/tsconfig.json",

  "include": ["src/**/*"],
  "exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}`
const tsconfigPath =  path.join(projectRoot, "tsconfig.json")
fs.writeFileSync(tsconfigPath, tsconfig)

// Add TSConfig
const svelteConfig = `import sveltePreprocess from 'svelte-preprocess';

export default {
  preprocess: sveltePreprocess()
};
`
const svelteConfigPath =  path.join(projectRoot, "svelte.config.js")
fs.writeFileSync(svelteConfigPath, svelteConfig)

// Add global.d.ts
const dtsPath =  path.join(projectRoot, "src", "global.d.ts")
fs.writeFileSync(dtsPath, `/// <reference types="svelte" />`)

// Delete this script, but not during testing
if (!argv[2]) {
  // Remove the script
  fs.unlinkSync(path.join(__filename))

  // Check for Mac's DS_store file, and if it's the only one left remove it
  const remainingFiles = fs.readdirSync(path.join(__dirname))
  if (remainingFiles.length === 1 && remainingFiles[0] === '.DS_store') {
    fs.unlinkSync(path.join(__dirname, '.DS_store'))
  }

  // Check if the scripts folder is empty
  if (fs.readdirSync(path.join(__dirname)).length === 0) {
    // Remove the scripts folder
    fs.rmdirSync(path.join(__dirname))
  }
}

// Adds the extension recommendation
fs.mkdirSync(path.join(projectRoot, ".vscode"), { recursive: true })
fs.writeFileSync(path.join(projectRoot, ".vscode", "extensions.json"), `{
  "recommendations": ["svelte.svelte-vscode"]
}
`)

console.log("Converted to TypeScript.")

if (fs.existsSync(path.join(projectRoot, "node_modules"))) {
  console.log("\nYou will need to re-run your dependency manager to get started.")
}


================================================
FILE: svelte-app/src/ApiKeyModal.svelte
================================================
<script>
    import { createEventDispatcher } from 'svelte';
    import { API_BASE_URL } from './config.js';

    export let isOpen = false;
    const dispatch = createEventDispatcher();
    let selectedProvider = 'openai';
    let apiKey = '';
    let successMessage = '';
    let errorMessage = '';
    let apiMessage = '';
    let apiMessageType = ''; // success or error

    async function checkApiKey() {
        const response = await fetch(`${API_BASE_URL}/check_api_key`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ provider: selectedProvider })
        });
        const data = await response.json();
        if (data.exists) {
            showApiMessage('API Key exists in .env file', 'success');
        } else {
            console.log('API Key does not exist in .env file');
        }
    }

    async function handleSave() {
        const response = await fetch(`${API_BASE_URL}/save_api_key`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ provider: selectedProvider, api_key: apiKey })
        });

        if (response.ok) {
            successMessage = 'API Key saved successfully!';
            errorMessage = '';
            // close it
            setTimeout(() => {
                dispatch('cancel');
                apiKey = '';
                successMessage = '';
            }, 1500);
        } else {
            errorMessage = 'Failed to save API Key';
            successMessage = '';
        }
    }

    
    function showApiMessage(message, type) {
        apiMessage = message;
        apiMessageType = type;
        setTimeout(() => {
            apiMessage = '';
            apiMessageType = '';
        }, 3000);
    }

    function handleCancel() {
        dispatch('cancel');
    }
</script>

<style>
    .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background-color: #2d2d2d;
        color: white;
        padding: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
        width: 60vw;
        max-width: 500px;
        z-index: 1000;
    }

    .modal h2 {
        margin-top: 0;
        margin-bottom: 10px;
    }

    .modal input, .modal select {
        width: 100%;
        margin-bottom: 10px;
        background-color: #3a3a3a;
        color: white;
        border: none;
        padding: 10px;
        border-radius: 5px;
    }

    .modal-buttons {
        display: flex;
        justify-content: space-between;
    }

    .modal-buttons button {
        padding: 10px;
        border: none;
        border-radius: 5px;
        background-color: #3a3a3a;
        color: white;
        cursor: pointer;
    }

    .modal-buttons button:hover {
        background-color: #555;
    }

    .overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        z-index: 999;
    }

    .api-message {
        position: fixed;
        top: 40px;
        left: 50%;
        transform: translateX(-50%);
        padding: 10px 20px;
        border-radius: 5px;
        z-index: 1001;
        font-size: 16px;
        font-weight: bold;
        text-align: center;
    }

    .api-message.success {
        background-color: green;
        color: white;
    }

    .api-message.error {
        background-color: yellow;
        color: rgb(218, 59, 11);
    }
</style>

{#if isOpen}
    <div class="overlay" on:click={handleCancel}></div>
    <div class="modal">
        <h2>API Key Settings</h2>
        <select bind:value={selectedProvider} on:change={checkApiKey}>
            <option value="openai">OpenAI</option>
            <option value="mistral">Mistral</option>
            <option value="zhipuai">ZhipuAI</option>
            <option value="anthropic">Anthropic</option>
            <option value="nvidia">Nvidia</option>
            <option value="dashscope">Tongyi</option>
            <option value="moonshot">Moonshot</option>
        </select>
        <input type="text" bind:value={apiKey} placeholder="Enter API Key" />
        {#if successMessage}
            <p style="color: green;">{successMessage}</p>
        {/if}
        {#if errorMessage}
            <p style="color: red;">{errorMessage}</p>
        {/if}
        <div class="modal-buttons">
            <button on:click={handleSave}>Submit</button>
            <button on:click={handleCancel}>Cancel</button>
        </div>
    </div>
{/if}


{#if apiMessage}
    <div class="api-message {apiMessageType}">
        {apiMessage}
    </div>
{/if}


================================================
FILE: svelte-app/src/App.svelte
================================================
<script>
    import { onMount } from 'svelte';
    import ConfigEditor from './ConfigEditor.svelte';
    import Chat from './Chat.svelte';
    import { marked } from 'marked';
    import NewSourceModal from './NewSourceModal.svelte';
    import ApiKeyModal from './ApiKeyModal.svelte';
    import DeleteConfirmationModal from './DeleteConfirmationModal.svelte';
    import LlamaCppModelsModal from './LlamaCppModelsModal.svelte';
    import PromptTemplatesModal from './PromptTemplatesModal.svelte';
    import { API_BASE_URL } from './config.js';

    let showConfigEditor = false;
    let showNewSourceModal = false;
    let showApiKeyModal = false;
    let showDeleteModal = false;
    let configData = {};
    let configOrder = [];
    let currentRepo = '';
    let sessions = [];
    let currentSessionIndex = -1;
    let messages = [];
    let providerList = [];
    let selectedProvider = '';
    let modelList = [];
    let selectedModel = '';
    let showDefaultMessage = true;
    let sessionToDelete = null;
    let sessionToDeleteName = '';
    let searchKeyword = '';
    let filteredSessions = [];
    let showSettings = false; // control the Settings hide or show
    let showLlamaCppModelsModal = false;
    let showPromptTemplatesModal = false;
    let showCodeGraphOptions = false;

    async function fetchConfig() {
        try {
            const response = await fetch(`${API_BASE_URL}/get_config`);
            if (response.ok) {
                const data = await response.json();
                configData = data;
                configOrder = Object.keys(data);
                providerList = data['model_providers']['provider_list'].split(', ');
                selectedProvider = data['model_providers']['selected_provider'];
                updateModelList();
            } else {
                console.error('Failed to fetch config');
            }
        } catch (error) {
            console.error('Error fetching config:', error);
        }
    }

    async function saveConfig() {
        const response = await fetch(`${API_BASE_URL}/save_config`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(configData)
        });

        if (!response.ok) {
            alert('Failed to save configuration');
        } else {
            console.log('Configuration saved successfully!');
        }
    }

    function updateModelList() {
        modelList = configData[`${selectedProvider}_llm_models`]['model_list'].split(', ');
        selectedModel = configData[`${selectedProvider}_llm_models`]['selected_model'];
    }

    function handleProviderChange(event) {
        selectedProvider = event.target.value;
        configData['model_providers']['selected_provider'] = selectedProvider;
        updateModelList();
        saveConfig();
    }

    function handleModelChange(event) {
        selectedModel = event.target.value;
        configData[`${selectedProvider}_llm_models`]['selected_model'] = selectedModel;
        saveConfig();
    }

    async function toggleConfigEditor() {
        showConfigEditor = !showConfigEditor;
        if (showConfigEditor) {
            await fetchConfig();
        }
    }

    async function createNewSession(gitUrl) {
        let repoName = gitUrl.split('/').pop().replace('.git', '');
        let newSession = { id: Date.now(), name: repoName, url: gitUrl, messages: [{ sender: 'QA-Pilot', text: `url:${gitUrl}` }, { sender: 'loader', text: 'Thinking...' }] };
        sessions.push(newSession);
        currentSessionIndex = sessions.length - 1;
        currentRepo = gitUrl;
        messages = newSession.messages;
        showDefaultMessage = false;
        await saveSessions();
        await loadRepo(gitUrl);
        await updateCurrentSession(newSession);
    }

    async function switchSession(sessionId) {
        const index = sessions.findIndex(session => session.id === sessionId);
        currentSessionIndex = index;
        currentRepo = sessions[index].url;
        await updateCurrentSession(sessions[index]);
        loadMessages(sessions[index].id);
    }

    async function updateCurrentSession(session) {
        try {
            const response = await fetch(`${API_BASE_URL}/update_current_session`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(session)
            });

            if (!response.ok) {
                console.error('Failed to update current session');
            }
        } catch (error) {
            console.error('Error updating current session:', error);
        }
    }

    function openNewSourceModal() {
        showNewSourceModal = true;
    }

    function closeNewSourceModal() {
        showNewSourceModal = false;
    }

    function openApiKeyModal() {
        showApiKeyModal = true;
    }

    function closeApiKeyModal() {
        showApiKeyModal = false;
    }

    async function handleNewSource(event) {
        const gitUrl = event.detail;
        closeNewSourceModal();
        if (gitUrl) {
            await createNewSession(gitUrl);
        }
    }

    async function loadRepo(gitUrl) {
        try {
            const response = await fetch(`${API_BASE_URL}/load_repo`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ git_url: gitUrl })
            });

            if (response.ok) {
                const data = await response.json();
                let currentMessages = sessions[currentSessionIndex].messages;
                currentMessages = currentMessages.filter(message => message.sender !== 'loader');
                currentMessages.push({ sender: 'QA-Pilot', text: `Repository ${gitUrl} loaded successfully!` });
                sessions[currentSessionIndex].messages = currentMessages;
                messages = currentMessages;
                await saveSessions();
            } else {
                throw new Error('Failed to load repository');
            }
        } catch (error) {
            console.error('Error loading repository:', error);
        }
    }

    async function loadSessions() {
        try {
            const response = await fetch(`${API_BASE_URL}/sessions`);
            if (response.ok) {
                const data = await response.json();
                sessions = data;
                if (sessions.length > 0) {
                    currentSessionIndex = 0;
                    currentRepo = sessions[0].url;
                    loadMessages(sessions[0].id);
                    showDefaultMessage = false;
                }

                // initial filter session
                filterSessions();
            } else {
                console.error('Failed to load sessions');
            }
        } catch (error) {
            console.error('Error loading sessions:', error);
        }
    }

    async function loadMessages(sessionId) {
        try {
            const response = await fetch(`${API_BASE_URL}/messages/${sessionId}`);
            if (response.ok) {
                const data = await response.json();
                messages = data;
            } else {
                console.error('Failed to load messages');
            }
        } catch (error) {
            console.error('Error loading messages:', error);
        }
    }

    async function saveSessions() {
        try {
            const response = await fetch(`${API_BASE_URL}/sessions`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(sessions)
            });
            if (!response.ok) {
                console.error('Failed to save sessions');
            }
        } catch (error) {
            console.error('Error saving sessions:', error);
        }
    }

    async function deleteSession(index) {
        const sessionId = sessions[index].id;
        try {
            const response = await fetch(`${API_BASE_URL}/sessions/${sessionId}`, {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json'
                }
            });

            if (response.ok) {
                sessions.splice(index, 1);
                currentSessionIndex = sessions.length > 0 ? 0 : -1;
                if (currentSessionIndex !== -1) {
                    currentRepo = sessions[0].url;
                    await updateCurrentSession(sessions[0]);
                    loadMessages(sessions[0].id);
                } else {
                    messages = [];
                    currentRepo = '';
                }
                await loadSessions();
            } else {
                console.error('Failed to delete session');
            }
        } catch (error) {
            console.error('Error deleting session:', error);
        }
    }

    function toggleCodeGraphOptions() {
        showCodeGraphOptions = !showCodeGraphOptions;
    }

    function openPythonCodeGraph() {
        window.open(`${API_BASE_URL}/codegraph`, '_blank');
    }

    function openGoCodeGraph() {
        window.open(`${API_BASE_URL}/go_codegraph`, '_blank');
    }

    function confirmDeleteSession(sessionId) {
        sessionToDelete = sessionId;
        const session = sessions.find(s => s.id === sessionId);
        sessionToDeleteName = session ? session.name : '';
        showDeleteModal = true;
    }

    async function handleConfirmDelete() {
        const index = sessions.findIndex(session => session.id === sessionToDelete);
        if (index !== -1) {
            deleteSession(index);
        }
        showDeleteModal = false;
    }

    function handleCancelDelete() {
        showDeleteModal = false;
    }

    function handleSearch(event) {
        searchKeyword = event.target.value.toLowerCase();
        filterSessions();
    }

    function filterSessions() {
        if (searchKeyword) {
            filteredSessions = sessions.filter(session => session.name.toLowerCase().includes(searchKeyword));
        } else {
            filteredSessions = sessions;
        }
    }

    function toggleSettings() {
        showSettings = !showSettings;
    }

    function openLlamaCppModelsModal() {
        showLlamaCppModelsModal = true;
    }

    function closeLlamaCppModelsModal() {
        showLlamaCppModelsModal = false;
    }

    function openPromptTemplatesModal() {
        showPromptTemplatesModal = true;
    }

    function closePromptTemplatesModal() {
        showPromptTemplatesModal = false;
    }

    onMount(async () => {
        await loadSessions();
        await fetchConfig();
        filterSessions(); //initial filter session
    });
</script>

<style>
    .container {
        display: flex;
        height: 100vh;
        background-color: #1e1e1e;
    }

    .sidebar {
        width: 200px;
        background-color: #2d2d2d;
        color: #fff;
        display: flex;
        flex-direction: column;
        padding: 10px;
    }

    .sidebar input {
        background-color: #3a3a3a;
        border: none;
        color: white;
        padding: 10px;
        font-size: 16px;
        margin: 5px 0;
    }

    .sidebar button {
        background-color: #3a3a3a;
        border: none;
        color: white;
        padding: 10px;
        text-align: left;
        text-decoration: none;
        display: block;
        font-size: 16px;
        margin: 5px 0;
        cursor: pointer;
        position: relative;
    }

    .sidebar button:hover {
        background-color: #555;
    }

    .content {
        flex: 1;
        background-color: #1e1e1e;
        color: #fff;
        display: flex;
        flex-direction: column;
        font-size: 18px;
        position: relative;
        padding: 10px;
    }

    .header {
        text-align: center;
        font-size: 24px;
        margin-bottom: 10px;
    }

    .active {
        background-color: #555;
    }

    .session-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding-right: 1px;
        margin-bottom: 5px;
        background-color: #3a3a3a;
    }

    .delete-button {
        background: none;
        border: none;
        color: white;
        cursor: pointer;
        font-size: 16px;
    }

    .delete-button:hover {
        color: red;
    }

    .submenu {
        display: none;
        flex-direction: column;
        margin-left: 10px;
    }

    .submenu.visible {
        display: flex;
    }

    .arrow {
        position: absolute;
        right: 10px;
        top: 50%;
        transform: translateY(-50%);
        font-size: 12px;
    }

    .rotate {
        transform: translateY(-50%) rotate(90deg);
    }

    .overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        z-index: 999;
    }

    .submenu-codegraph {
        display: none;
        flex-direction: column;
        margin-left: 10px;
    }

    .submenu-codegraph.visible {
        display: flex;
    }
</style>

<div class="container">
    <div class="sidebar">
        <label for="provider-select">Select Provider:</label>
        <select id="provider-select" on:change={handleProviderChange} bind:value={selectedProvider}>
            {#each providerList as provider}
                <option value={provider}>{provider}</option>
            {/each}
        </select>

        <label for="model-select">Select Model:</label>
        <select id="model-select" on:change={handleModelChange} bind:value={selectedModel}>
            {#each modelList as model}
                <option value={model}>{model}</option>
            {/each}
        </select>

        <button on:click={toggleCodeGraphOptions}>
            Open Code Graph
            <span class="arrow {showCodeGraphOptions ? 'rotate' : ''}">&#9654;</span>
        </button>
        <div class="submenu-codegraph {showCodeGraphOptions ? 'visible' : ''}">
            <button on:click={openPythonCodeGraph}>Python Codegraph</button>
            <button on:click={openGoCodeGraph}>Go Codegraph</button>
        </div>

        <button on:click={toggleSettings}>
            Settings
            <span class="arrow {showSettings ? 'rotate' : ''}">&#9654;</span>
        </button>
        <div class="submenu {showSettings ? 'visible' : ''}">
            <button on:click={toggleConfigEditor}>Edit QA-Pilot Settings</button>
            <button on:click={openApiKeyModal}>AI Vendor API Key</button>
            <button on:click={openLlamaCppModelsModal}>Llamacpp models</button>
            <button on:click={openPromptTemplatesModal}>Prompt Templates</button>
        </div>

        <button on:click={openNewSourceModal}>New Source Button</button>

        <input type="text" placeholder="Search sessions" on:input={handleSearch} bind:value={searchKeyword} />

        {#each filteredSessions as session}
        <div class="session-item">
            <button on:click={() => switchSession(session.id)} class:active={session.id === sessions[currentSessionIndex]?.id}>
                {session.name}
            </button>
            <button class="delete-button" on:click={() => confirmDeleteSession(session.id)}>🗑️</button>
        </div>
        {/each}
    </div>
    <div class="content">
        <div class="header">{currentSessionIndex !== -1 ? sessions[currentSessionIndex].name : 'QA-Pilot'}</div>
        {#if currentSessionIndex !== -1}
            <Chat {currentRepo} bind:messages={messages} bind:sessionId={sessions[currentSessionIndex].id} sessionName={sessions[currentSessionIndex].name} />
        {:else}
            <div style="color: white; font-size: 23px; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%;">
                <img src="/qa-pilot1.png" alt="Placeholder Image" style="width: 200px; height: auto; margin-bottom: 20px;">
                <p>1. Please check your config with "Edit QA-Pilot Settings" button.</p>
                <p>2. Click "New Source Button" to input the github URL.</p>
            </div>
        {/if}
    </div>
</div>

{#if showConfigEditor}
    <ConfigEditor
        {configData}
        {configOrder}
        {saveConfig}
        {toggleConfigEditor} />
{/if}

{#if showNewSourceModal}
    <NewSourceModal
        isOpen={showNewSourceModal}
        on:confirm={handleNewSource}
        on:cancel={closeNewSourceModal} />
{/if}

{#if showApiKeyModal}
    <ApiKeyModal
        isOpen={showApiKeyModal}
        on:cancel={closeApiKeyModal} />
{/if}

{#if showLlamaCppModelsModal}
    <LlamaCppModelsModal
        isOpen={showLlamaCppModelsModal}
        on:cancel={closeLlamaCppModelsModal} />
{/if}

{#if showPromptTemplatesModal}
    <PromptTemplatesModal
        isOpen={showPromptTemplatesModal}
        on:cancel={closePromptTemplatesModal} />
{/if}

{#if showDeleteModal}
    <DeleteConfirmationModal
        isOpen={showDeleteModal}
        sessionName={sessionToDeleteName}
        on:confirm={handleConfirmDelete}
        on:cancel={handleCancelDelete} />
{/if}


================================================
FILE: svelte-app/src/Chat.svelte
================================================
<script>
    import { onMount, afterUpdate } from 'svelte';
    import { marked } from 'marked';
    export let currentRepo;
    export let messages = [];
    export let sessionId;
    export let sessionName;
    import { API_BASE_URL } from './config.js';

    let chatInput = '';
    let isLoading = false;
    let messagesContainer;

    onMount(() => {
        scrollToBottom();
    });

    afterUpdate(() => {
        scrollToBottom();
    });

    function scrollToBottom() {
        if (messagesContainer) {
            messagesContainer.scrollTop = messagesContainer.scrollHeight;
        }
    }

    async function sendMessage() {
        if (chatInput.trim() === '') return;

        const userInput = chatInput;

        messages = [...messages, { sender: 'You', text: userInput }];
        chatInput = '';
        isLoading = true;
        messages = [...messages, { sender: 'loader', text: 'Thinking...' }];

        try {
            const response = await fetch(`${API_BASE_URL}/chat`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ message: userInput, current_repo: currentRepo, session_id: sessionId })
            });

            if (response.ok) {
                const data = await response.json();
                messages = messages.filter(message => message.sender !== 'loader');
                messages = [...messages, { sender: 'QA-Pilot', text: data.response }];
                await saveMessages();
            } else {
                throw new Error('Failed to send message');
            }
        } catch (error) {
            console.error('Error sending message:', error);
        } finally {
            isLoading = false;
        }
    }

    async function saveMessages() {
        console.log('Saving messages for current session:', messages);
        await fetch(`${API_BASE_URL}/sessions`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify([{ id: sessionId, name: sessionName, url: currentRepo, messages: JSON.stringify(messages) }])
        });
    }

    function handleKeyPress(event) {
        if (event.key === 'Enter') {
            sendMessage();
        }
    }

    async function copyToClipboard(text) {
        if (navigator.clipboard) {
            try {
                await navigator.clipboard.writeText(text);
                return true;
            } catch (error) {
                console.error('Failed to copy with clipboard API: ', error);
            }
        }
        // Fallback for older browsers
        const textArea = document.createElement('textarea');
        textArea.value = text;
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();
        try {
            const successful = document.execCommand('copy');
            return successful;
        } catch (error) {
            console.error('Failed to copy with execCommand: ', error);
            return false;
        } finally {
            document.body.removeChild(textArea);
        }
    }

    async function handleCopyClick(event, message) {
        const button = event.currentTarget;
        const textToCopy = message.text || "";
        const success = await copyToClipboard(textToCopy);

        if (success) {
            button.innerHTML = 'Copied';
            setTimeout(() => {
                button.innerHTML = 'Copy';
            }, 2000);
        }
    }

    function formatMessage(text) {
        if (!text) return { normal: "", think: "" };

        // Extract content inside <think> tags
        const thinkMatches = [...text.matchAll(/<think>(.*?)<\/think>/gs)];
        const thinkText = thinkMatches.map(match => match[1]).join(" ");

        // Remove <think> content to get the normal text
        const normalText = text.replace(/<think>.*?<\/think>/gs, "");

        return { normal: normalText, think: thinkText };
    }
</script>

<style>
    .chat-container {
        display: flex;
        flex-direction: column;
        flex: 1;
        overflow-y: auto;
    }

    .chat-messages {
        flex: 1;
        width: 100%;
        display: flex;
        flex-direction: column;
        margin-bottom: 20px;
        overflow-y: auto;
    }

    .chat-message {
        margin: 5px 0;
        padding: 10px;
        border-radius: 5px;
        font-size: 16px;
        position: relative;
    }

    .chat-message.user {
        align-self: flex-end;
        background-color: #3a3a3a;
    }

    .chat-message.bot {
        align-self: flex-start;
        background-color: #2d2d2d;
    }

    .chat-message.loader {
        align-self: flex-start;
        background-color: #2d2d2d;
        display: flex;
        align-items: center;
    }

    .chat-message .sender {
        font-weight: bold;
        margin-bottom: 5px;
    }

    .chat-message .loader-icon {
        border: 4px solid rgba(255, 255, 255, 0.3);
        border-radius: 50%;
        border-top: 4px solid #00ff00;
        width: 20px;
        height: 20px;
        animation: spin 1s linear infinite;
        margin-right: 10px;
    }

    @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }

    .copy-button-container {
        display: flex;
        justify-content: flex-start;
        margin-top: 5px;
    }

    .copy-button {
        background: none;
        border: none;
        color: #fff;
        cursor: pointer;
        font-size: 16px;
    }

    .chat-input-container {
        display: flex;
        width: 100%;
        position: sticky;
        bottom: 0;
        background-color: #1e1e1e;
        padding: 10px 0;
    }

    .chat-input {
        flex: 1;
        padding: 10px;
        border: none;
        border-radius: 5px;
        margin-right: 10px;
        background-color: #2d2d2d;
        color: #fff;
        font-size: 14px;
    }

    .send-button {
        padding: 10px;
        border: none;
        border-radius: 5px;
        background-color: #3a3a3a;
        color: #fff;
        cursor: pointer;
        font-size: 14px;
    }

    .send-button:hover {
        background-color: #555;
    }

    .upload-button {
        padding: 10px;
        border: none;
        background: none;
        cursor: pointer;
        color: #fff;
        margin-right: 10px;
    }

    .upload-button:hover {
        color: #ccc;
    }

    .think-text {
        font-size: 0.85em;
        font-style: italic;
        color: #ccc;
    }
</style>

<div class="chat-container">
    <div class="chat-messages" bind:this={messagesContainer}>
        {#each messages as message}
            <div class="chat-message {message.sender === 'You' ? 'user' : message.sender === 'loader' ? 'loader' : 'bot'}">
                {#if message.sender === 'loader'}
                    <div class="loader-icon"></div>
                    <div class="sender"></div>
                    <div>Thinking...</div>
                {:else}
                    <div class="sender">{message.sender}</div>
                    {#if formatMessage(message.text).think}
                        <div class="think-text">{@html marked(formatMessage(message.text).think)}</div>
                    {/if}

                    {#if formatMessage(message.text).normal}
                        <div>{@html marked(formatMessage(message.text).normal)}</div>
                    {/if}
                    {#if message.sender !== 'You'}
                        <div class="copy-button-container">
                            <button class="copy-button" on:click={(event) => handleCopyClick(event, message)}>Copy</button>
                        </div>
                    {/if}
                {/if}
            </div>
        {/each}
    </div>
    <div class="chat-input-container">
        <button class="upload-button">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="24" height="24">
                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4H1v4a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4v-4h-2zM18 9.83V13a1 1 0 0 1-2 0V9.83l-2.59 2.58a1 1 0 0 1-1.41-1.42l4.29-4.29a1 1 0 0 1 1.41 0l4.29 4.29a1 1 0 0 1-1.41 1.42L18 9.83z"/>
            </svg>
        </button>
        <input class="chat-input" type="text" placeholder="Type a message..." bind:value={chatInput} on:keypress={handleKeyPress}>
        <button class="send-button" on:click={sendMessage}>Send</button>
    </div>
</div>


================================================
FILE: svelte-app/src/ConfigEditor.svelte
================================================
<!-- src/ConfigEditor.svelte -->
<script>
    export let configData = {};
    export let configOrder = [];
    export let saveConfig;
    export let toggleConfigEditor;

    async function handleSaveConfig() {
        saveConfig();
        toggleConfigEditor(); // close the editor
    }
  </script>
  
  <style>
    .config-editor {
      position: absolute;
      top: 5%;
      left: 50%;
      transform: translate(-50%, 0);
      background-color: #2d2d2d;
      color: white;
      padding: 20px;
      border-radius: 10px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
      max-height: 90vh;
      overflow-y: auto;
      width: 60vw; /* 设置宽度 */
    }
  
    .config-section {
      margin-bottom: 20px;
    }
  
    .config-editor h3 {
      margin-top: 0;
      margin-bottom: 10px;
    }
  
    .config-editor label {
      display: block;
      margin-bottom: 5px;
      color: #ccc;
    }
  
    .config-editor input {
      width: 100%;
      margin-bottom: 10px;
      background-color: #3a3a3a; 
      color: #ccc; 
      border: 1px solid #777; 
      padding: 10px; 
      border-radius: 5px; 
    }

  .config-editor input:focus {
    outline: none;
    border-color: #bbb; 
  }
  
    .config-editor-buttons {
      display: flex;
      justify-content: space-between;
      margin-top: 20px;
    }
  </style>
  
  <div class="config-editor">
    <h2>Edit QA-Pilot Settings</h2>
    {#if Object.keys(configData).length === 0}
      <p>Loading configuration...</p>
    {:else}
      {#each configOrder as section}
        <div class="config-section">
          <h3>{section}</h3>
          {#each Object.keys(configData[section]) as key}
            <label>{key}</label>
            <input type="text" bind:value={configData[section][key]}>
          {/each}
        </div>
      {/each}
      <div class="config-editor-buttons">
        <button on:click={handleSaveConfig}>Save Changes</button>
        <button on:click={toggleConfigEditor}>Cancel</button>
      </div>
    {/if}
  </div>
  

================================================
FILE: svelte-app/src/DeleteConfirmationModal.svelte
================================================
<script>
    import { createEventDispatcher } from 'svelte';
    export let isOpen = false;
    export let sessionName = ''; // show the session name
    const dispatch = createEventDispatcher();

    function handleConfirm() {
        dispatch('confirm');
    }

    function handleCancel() {
        dispatch('cancel');
    }
</script>

<style>
    .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background-color: #2d2d2d;
        color: white;
        padding: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
        width: 60vw;
        max-width: 500px;
        z-index: 1000;
    }

    .modal h2 {
        margin-top: 0;
        margin-bottom: 10px;
        font-size: 18px; /* 调整字体大小 */
    }

    .modal .session-name {
        font-weight: bold; /* 加粗显示会话名称 */
        color: #ff4757; /* 高亮显示为红色 */
    }

    .modal-buttons {
        display: flex;
        justify-content: space-between;
    }

    .modal-buttons button {
        padding: 10px;
        border: none;
        border-radius: 5px;
        background-color: #3a3a3a;
        color: white;
        cursor: pointer;
    }

    .modal-buttons button:hover {
        background-color: #555;
    }

    .modal-buttons .delete-button {
        background-color: #ff4757; 
    }

    .modal-buttons .delete-button:hover {
        background-color: #ff6b81; 
    }

    .overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        z-index: 999;
    }
</style>

{#if isOpen}
    <div class="overlay" on:click={handleCancel}></div>
    <div class="modal">
        <h2>Are you sure you want to delete the session: <span class="session-name">{sessionName}</span>?</h2>
        <div class="modal-buttons">
            <button class="delete-button" on:click={handleConfirm}>Delete</button>
            <button on:click={handleCancel}>Cancel</button>
        </div>
    </div>
{/if}


================================================
FILE: svelte-app/src/DeleteTemplateModal.svelte
================================================
<script>
    import { createEventDispatcher } from 'svelte';
    export let isOpen = false;
    export let templateName = ''; 
    const dispatch = createEventDispatcher();

    function handleConfirm() {
        dispatch('confirm');
    }

    function handleCancel() {
        dispatch('cancel');
    }

    $: isDefaultTemplate = ['qa_template', 'code_template', 'code_template_localai'].includes(templateName);
</script>

<style>
    .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background-color: #2d2d2d;
        color: white;
        padding: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
        width: 60vw;
        max-width: 500px;
        z-index: 1000;
    }

    .modal h2 {
        margin-top: 0;
        margin-bottom: 10px;
        font-size: 18px; 
    }

    .modal .template-name {
        font-weight: bold;
        color: #ff4757;
    }

    .modal .warning {
        color: yellow; 
        margin-bottom: 10px;
    }

    .modal-buttons {
        display: flex;
        justify-content: space-between;
    }

    .modal-buttons button {
        padding: 10px;
        border: none;
        border-radius: 5px;
        background-color: #3a3a3a;
        color: white;
        cursor: pointer;
    }

    .modal-buttons button:hover {
        background-color: #555;
    }

    .modal-buttons .delete-button {
        background-color: #ff4757; 
    }

    .modal-buttons .delete-button:hover {
        background-color: #ff6b81; 
    }

    .overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        z-index: 999;
    }
</style>

{#if isOpen}
    <div class="overlay" on:click={handleCancel}></div>
    <div class="modal">
        <h2>Are you sure you want to delete the template: <span class="template-name">{templateName}</span>?</h2>
        {#if isDefaultTemplate}
            <p class="warning">Warning: You are about to delete a default template. Ensure there is a replacement before proceeding.</p>
        {/if}
        <div class="modal-buttons">
            <button class="delete-button" on:click={handleConfirm}>Delete</button>
            <button on:click={handleCancel}>Cancel</button>
        </div>
    </div>
{/if}


================================================
FILE: svelte-app/src/LlamaCppModelsModal.svelte
================================================
<script>
    import { createEventDispatcher, onMount } from 'svelte';
    import { API_BASE_URL } from './config.js';
    export let isOpen = false;
    const dispatch = createEventDispatcher();

    let models = [];
    let newModelFile = null;
    let uploadProgress = 0;
    let showProgressBar = false;
    let showMessage = false;
    let messageText = '';
    let messageType = '';

    async function fetchModels() {
        try {
            const response = await fetch(`${API_BASE_URL}/llamacpp_models`);
            if (!response.ok) {
                models = [];
            } else {
                models = await response.json();
            }
        } catch (error) {
            console.error('Failed to fetch models:', error);
            models = [];
        }
    }

    async function handleDelete(model) {
        await fetch(`${API_BASE_URL}/llamacpp_models/${model}`, { method: 'DELETE' });
        await fetchModels();
    }

    async function handleUpload() {
        if (!newModelFile) {
            showMessage = true;
            messageText = 'Please select a file to upload.';
            messageType = 'error';
            setTimeout(() => showMessage = false, 3000);
            return;
        }

        const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
        const totalChunks = Math.ceil(newModelFile.size / CHUNK_SIZE);

        showProgressBar = true;
        uploadProgress = 0;

        for (let start = 0; start < newModelFile.size; start += CHUNK_SIZE) {
            const chunk = newModelFile.slice(start, start + CHUNK_SIZE);
            const formData = new FormData();
            formData.append('file', chunk, newModelFile.name);
            formData.append('chunk', start / CHUNK_SIZE);
            formData.append('totalChunks', totalChunks);

            try {
                const response = await fetch(`${API_BASE_URL}/llamacpp_models`, {
                    method: 'POST',
                    body: formData,
                });

                if (!response.ok) {
                    throw new Error(`Failed to upload chunk ${start / CHUNK_SIZE}`);
                }

                // Update progress
                uploadProgress = ((start + CHUNK_SIZE) / newModelFile.size) * 100;
            } catch (error) {
                console.error('Error uploading chunk:', error);
                showMessage = true;
                messageText = `Error uploading chunk ${start / CHUNK_SIZE}`;
                messageType = 'error';
                setTimeout(() => showMessage = false, 3000);
                showProgressBar = false;
                uploadProgress = 0;
                return;
            }
        }

        showMessage = true;
        messageText = 'Upload successful';
        messageType = 'success';
        setTimeout(() => showMessage = false, 3000);

        showProgressBar = false;
        uploadProgress = 0;
        newModelFile = null; // Clear file input
        document.querySelector('input[type="file"]').value = ''; // Clear file input display
        await fetchModels();
    }

    function handleClose() {
        dispatch('cancel');
    }

    onMount(fetchModels);
</script>

<style>
    .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background-color: #2d2d2d;
        color: white;
        padding: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
        width: 60vw;
        max-width: 500px;
        z-index: 1000;
    }

    .modal h2 {
        margin-top: 0;
        margin-bottom: 10px;
    }

    .modal-buttons {
        display: flex;
        justify-content: space-between;
        margin-top: 20px;
    }

    .modal-buttons button {
        padding: 10px;
        border: none;
        border-radius: 5px;
        background-color: #3a3a3a;
        color: white;
        cursor: pointer;
    }

    .modal-buttons button:hover {
        background-color: #555;
    }

    .model-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 10px;
    }

    .model-item button {
        padding: 5px;
        border: none;
        border-radius: 3px;
        background-color: #555;
        color: white;
        cursor: pointer;
    }

    .model-item button:hover {
        background-color: red;
    }

    .progress-bar {
        width: 100%;
        background-color: #ddd;
        border-radius: 10px;
        overflow: hidden;
        margin-top: 20px; /* Adjusted margin */
    }

    .progress-bar-inner {
        height: 20px;
        background-color: #4caf50; /* Green color */
        width: 0;
        transition: width 0.2s;
    }

    .message {
        margin-top: 10px;
        padding: 10px;
        border-radius: 5px;
        text-align: center;
    }

    .message.success {
        background-color: #4caf50; /* Green */
        color: white;
    }

    .message.error {
        background-color: #f44336; /* Red */
        color: white;
    }
</style>

{#if isOpen}
    <div class="overlay" on:click={handleClose}></div>
    <div class="modal">
        <h2>LlamaCpp Models</h2>
        {#each models as model}
            <div class="model-item">
                <span>{model}</span>
                <button on:click={() => handleDelete(model)}>Delete</button>
            </div>
        {/each}
        <input type="file" on:change={e => newModelFile = e.target.files[0]} />
        {#if showProgressBar}
            <div class="progress-bar">
                <div class="progress-bar-inner" style="width: {uploadProgress}%"></div>
            </div>
        {/if}
        {#if showMessage}
            <div class="message {messageType}">{messageText}</div>
        {/if}
        <div class="modal-buttons">
            <button on:click={handleUpload}>Upload</button>
            <button on:click={handleClose}>Close</button>
        </div>
    </div>
{/if}


================================================
FILE: svelte-app/src/NewSourceModal.svelte
================================================
<script>
    import { createEventDispatcher } from 'svelte';
    export let isOpen = false;
    const dispatch = createEventDispatcher();

    let gitUrl = '';

    function handleConfirm() {
        if (gitUrl.trim()) {
            dispatch('confirm', gitUrl);
            gitUrl = '';
        }
    }

    function handleCancel() {
        dispatch('cancel');
    }
</script>

<style>
    .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background-color: #2d2d2d;
        color: white;
        padding: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
        width: 60vw;
        max-width: 500px;
        z-index: 1000;
    }

    .modal h2 {
        margin-top: 0;
        margin-bottom: 10px;
    }

    .modal input {
        width: 100%;
        margin-bottom: 10px;
        background-color: #3a3a3a;
        color: white;
        border: none;
        padding: 10px;
        border-radius: 5px;
    }

    .modal-buttons {
        display: flex;
        justify-content: space-between;
    }

    .modal-buttons button {
        padding: 10px;
        border: none;
        border-radius: 5px;
        background-color: #3a3a3a;
        color: white;
        cursor: pointer;
    }

    .modal-buttons button:hover {
        background-color: #555;
    }

    .overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        z-index: 999;
    }
</style>

{#if isOpen}
    <div class="overlay" on:click={handleCancel}></div>
    <div class="modal">
        <h2>Enter GitHub URL</h2>
        <input type="text" bind:value={gitUrl} placeholder="https://github.com/user/repo.git" />
        <div class="modal-buttons">
            <button on:click={handleConfirm}>Confirm</button>
            <button on:click={handleCancel}>Cancel</button>
        </div>
    </div>
{/if}


================================================
FILE: svelte-app/src/PromptTemplatesModal.svelte
================================================
<script>
    import { createEventDispatcher, onMount } from 'svelte';
    import { API_BASE_URL } from './config.js';
    import DeleteTemplateModal from './DeleteTemplateModal.svelte';

    export let isOpen = false;
    const dispatch = createEventDispatcher();

    let templates = {};
    let selectedTemplate = '';
    let selectedTemplateContent = '';
    let newTemplateName = '';
    let newTemplateContent = '';
    let showMessage = false;
    let messageText = '';
    let messageType = '';
    let showDeleteModal = false;

    async function fetchTemplates() {
        try {
            const response = await fetch(`${API_BASE_URL}/get_prompt_templates`);
            if (!response.ok) {
                throw new Error('Failed to fetch templates');
            }
            templates = await response.json();
            if (Object.keys(templates).length > 0) {
                selectedTemplate = Object.keys(templates)[0];
                selectedTemplateContent = templates[selectedTemplate];
            }
        } catch (error) {
            console.error('Error fetching templates:', error);
            showMessage = true;
            messageText = 'Error fetching templates';
            messageType = 'error';
            setTimeout(() => showMessage = false, 3000);
        }
    }

    function handleTemplateChange(event) {
        selectedTemplate = event.target.value;
        selectedTemplateContent = templates[selectedTemplate];
    }

    function handleContentChange(event) {
        selectedTemplateContent = event.target.value;
    }

    async function saveTemplates() {
        templates[selectedTemplate] = selectedTemplateContent;
        try {
            const response = await fetch(`${API_BASE_URL}/save_prompt_templates`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(templates)
            });
            if (!response.ok) {
                throw new Error('Failed to save templates');
            }
            showMessage = true;
            messageText = 'Templates saved successfully!';
            messageType = 'success';
        } catch (error) {
            console.error('Error saving templates:', error);
            showMessage = true;
            messageText = 'Failed to save templates';
            messageType = 'error';
        }
        setTimeout(() => showMessage = false, 3000);
    }

    function addTemplate() {
        if (newTemplateName && newTemplateContent) {
            templates[newTemplateName] = newTemplateContent;
            selectedTemplate = newTemplateName;
            selectedTemplateContent = newTemplateContent;
            newTemplateName = '';
            newTemplateContent = '';
            saveTemplates(); // Save new template to backend
        }
    }

    function openDeleteModal() {
        if (selectedTemplate) {
            showDeleteModal = true;
        }
    }

    async function deleteTemplate() {
        if (selectedTemplate) {
            try {
                const response = await fetch(`${API_BASE_URL}/delete_prompt_template`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ template_name: selectedTemplate })
                });

                if (!response.ok) {
                    throw new Error('Failed to delete template');
                }

                delete templates[selectedTemplate];
                if (Object.keys(templates).length > 0) {
                    selectedTemplate = Object.keys(templates)[0];
                    selectedTemplateContent = templates[selectedTemplate];
                } else {
                    selectedTemplate = '';
                    selectedTemplateContent = '';
                }
                showMessage = true;
                messageText = 'Template deleted successfully!';
                messageType = 'success';
                showDeleteModal = false;
                
                // Fetch templates again to update the dropdown
                await fetchTemplates();

            } catch (error) {
                console.error('Error deleting template:', error);
                showMessage = true;
                messageText = 'Failed to delete template';
                messageType = 'error';
                showDeleteModal = false;
            }
            setTimeout(() => showMessage = false, 3000);
        }
    }

    function handleClose() {
        dispatch('cancel');
    }

    onMount(fetchTemplates);
</script>

<style>
    .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background-color: #2d2d2d;
        color: white;
        padding: 20px;
        border-radius: 10px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
        width: 60vw;
        max-width: 500px;
        z-index: 1000;
    }

    .modal h2 {
        margin-top: 0;
        margin-bottom: 10px;
    }

    .modal-buttons {
        display: flex;
        justify-content: space-between;
        margin-top: 20px;
    }

    .modal-buttons button {
        padding: 10px;
        border: none;
        border-radius: 5px;
        background-color: #3a3a3a;
        color: white;
        cursor: pointer;
    }

    .modal-buttons button:hover {
        background-color: #555;
    }

    .message {
        margin-top: 10px;
        padding: 10px;
        border-radius: 5px;
        text-align: center;
    }

    .message.success {
        background-color: #4caf50; /* Green */
        color: white;
    }

    .message.error {
        background-color: #f44336; /* Red */
        color: white;
    }
</style>

{#if isOpen}
    <div class="overlay" on:click={handleClose}></div>
    <div class="modal">
        <h2>Prompt Templates</h2>
        <select on:change={handleTemplateChange} bind:value={selectedTemplate}>
            {#each Object.keys(templates) as key}
                <option value={key}>{key}</option>
            {/each}
        </select>
        <textarea rows="10" cols="50" bind:value={selectedTemplateContent} on:input={handleContentChange}></textarea>
        <div class="modal-buttons">
            <button on:click={saveTemplates}>Save Templates</button>
            <button on:click={openDeleteModal}>Delete Template</button>
            <button on:click={handleClose}>Close</button>
        </div>
        <input type="text" placeholder="New Template Name" bind:value={newTemplateName} />
        <textarea placeholder="New Template Content" rows="4" cols="50" bind:value={newTemplateContent}></textarea>
        <button on:click={addTemplate}>Add Template</button>
        {#if showMessage}
            <div class="message {messageType}">{messageText}</div>
        {/if}
    </div>
{/if}

<DeleteTemplateModal 
    isOpen={showDeleteModal} 
    templateName={selectedTemplate} 
    on:confirm={deleteTemplate} 
    on:cancel={() => showDeleteModal = false} 
/>


================================================
FILE: svelte-app/src/config.js
================================================
export const API_BASE_URL = 'http://localhost:5000';


================================================
FILE: svelte-app/src/global.css
================================================
a {
    color: #00ffff !important;
    text-decoration: none !important; 
}

a:hover {
    text-decoration: underline !important; 
}


================================================
FILE: svelte-app/src/main.js
================================================
import './global.css'
import App from './App.svelte';

const app = new App({
  target: document.body
});

export default app;


================================================
FILE: templates/go_index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Go CodeGPS</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.21.0/cytoscape.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/cytoscape-dagre@2.5.0/cytoscape-dagre.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
    <style>
        body {
            display: flex;
            margin: 0;
            height: 100vh;
            overflow: hidden;
        }

        #file-tree {
            width: 25%;
            border-right: 1px solid black;
            padding-left: 10px;
            overflow-y: auto;
            background-color: #333;
            color: white;
            font-size: 17px;
            font-family: Arial, sans-serif;
        }

        #cy-container {
            width: 75%;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
        }

        #cy {
            width: 100%;
            height: 90%;
            border-left: 1px solid black;
            overflow: auto;
            position: absolute;
            top: 5%;
        }

        #color-legend {
            position: absolute;
            top: 0;
            right: 0;
            background-color: #fff;
            border: 1px solid #ccc;
            padding: 5px;
            border-radius: 0 0 0 8px;
            display: flex;
            align-items: center;
            z-index: 1000;
            white-space: nowrap;
        }

        .legend-color-box {
            display: inline-block;
            width: 15px;
            height: 15px;
            margin: 0 10px;
        }

        #legend-list {
            font-family: Arial, sans-serif;
            display: flex;
            list-style: none;
            margin: 0;
            padding: 0;
        }

        .codeModal {
            position: absolute;
            width: 30%;
            background-color: #fdf6e3;
            border: 1px solid #d3d3d3;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
            padding: 10px;
            display: none;
            overflow: auto;
            z-index: 1000;
            resize: both;
            cursor: move;
            border-radius: 8px;
        }

        .close-button {
            position: absolute;
            top: 10px;
            right: 10px;
            cursor: pointer;
            font-size: 14px;
            padding: 5px 10px;
            background-color: #ff5c5c;
            color: white;
            border: none;
            border-radius: 50%;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        }

        .analyze-button {
            position: absolute;
            bottom: 10px;
            right: 10px;
            cursor: pointer;
            font-size: 14px;
            padding: 5px 10px;
            background-color: #4caf50;
            color: white;
            border: none;
            border-radius: 50%;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        }

        .modal-title {
            font-size: 18px;
            font-weight: bold;
            margin-bottom: 10px;
            background-color: #ffeb3b;
            padding: 5px 10px;
            border-radius: 5px;
            display: inline-block;
        }

        pre {
            margin: 0;
            padding: 10px;
            white-space: pre-wrap;
            word-wrap: break-word;
            background-color: white;
            border-radius: 8px;
            font-family: 'Courier New', Courier, monospace;
            color: #333;
            max-height: 50vh;
            overflow-y: auto;
        }

        code {
            background-color: white !important;
        }

        ul {
            list-style-type: none;
            padding-left: 20px;
        }

        .directory {
            font-weight: bold;
            cursor: pointer;
            color: white;
        }

        .file {
            cursor: pointer;
            color: white;
        }

        .directory .fa-folder {
            color: #f9a825;
        }

        .file .fa-file {
            color: #5E97F6;
        }

        .selected {
            background-color: #555;
        }

        #zoom-buttons {
            position: absolute;
            bottom: 20px;
            left: 20px;
            z-index: 1000;
        }

        .zoom-button {
            background-color: #333;
            color: white;
            border: none;
            border-radius: 50%;
            width: 30px;
            height: 30px;
            font-size: 18px;
            margin: 5px;
            cursor: pointer;
        }

        .zoom-button:hover {
            background-color: #555;
        }
    </style>
</head>
<body>
    <div id="file-tree"></div>
    <div id="cy-container">
        <div id="cy"></div>
        <div id="color-legend">
            <ul id="legend-list"></ul>
        </div>
        <div id="zoom-buttons">
            <button class="zoom-button" onclick="zoomIn()">+</button>
            <button class="zoom-button" onclick="zoomOut()">-</button>
        </div>
    </div>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            fetchDirectory();
            cytoscape.use(cytoscapeDagre);

            window.cy = cytoscape({
                container: document.getElementById('cy'),
                style: [
                    {
                        selector: 'node',
                        style: {
                            'label': 'data(name)',
                            'shape': 'roundrectangle',
                            'text-valign': 'center',
                            'text-halign': 'center',
                            'width': 'label',
                            'height': 'label',
                            'padding': '10px',
                            'color': 'white',
                            'font-size': '14px',
                            'font-family': 'Arial, sans-serif'
                        }
                    },
                    {
                        selector: 'node[class="struct"]',
                        style: {
                            'background-color': '#9370DB',
                        }
                    },
                    {
                        selector: 'node[class="function"]',
                        style: {
                            'background-color': '#34a853',
                        }
                    },
                    {
                        selector: 'node[class="method"]',
                        style: {
                            'background-color': '#FF69B4',
                        }
                    },
                    {
                        selector: 'node[class="import"]',
                        style: {
                            'background-color': '#ea4335',
                        }
                    },
                    {
                        selector: 'edge',
                        style: {
                            'width': 2,
                            'line-color': '#FFD700',
                            'target-arrow-color': '#FFD700',
                            'target-arrow-shape': 'triangle',
                            'curve-style': 'bezier'
                        }
                    },
                    {
                        selector: 'edge[category = "dashed"]',
                        style: {
                            'line-style': 'dashed',
                            'line-color': 'data(color)',
                            'target-arrow-color': 'data(color)',
                            'target-arrow-shape': 'triangle'
                        }
                    }
                ],
                layout: {
                    name: 'dagre',
                    nodeSep: 100,
                    rankSep: 150,
                    rankDir: 'TB',
                    animate: false
                }
            });

            addLegend();
        });

        function addLegend() {
            const legendItems = [
                { color: '#9370DB', label: 'Struct' },
                { color: '#34a853', label: 'Function' },
                { color: '#FF69B4', label: 'Method' },
                { color: '#ea4335', label: 'Import' },
                { color: '#FFD700', label: 'Solid-Direct call' },
                { color: 'gray', label: 'Dashed-Inherit' },
                { color: 'green', label: 'Dashed-Call' },
                { color: 'orange', label: 'Dashed-Import' }
            ];

            const legendList = document.getElementById('legend-list');
            legendItems.forEach(item => {
                const li = document.createElement('li');
                const colorBox = document.createElement('span');
                colorBox.style.backgroundColor = item.color;
                colorBox.className = 'legend-color-box';
                li.appendChild(colorBox);
                li.appendChild(document.createTextNode(item.label));
                legendList.appendChild(li);
            });
        }

        function makeDraggable(modal) {
            let isDragging = false;
            let startX, startY, initialX, initialY;

            modal.onmousedown = function(event) {
                isDragging = true;
                startX = event.clientX;
                startY = event.clientY;
                initialX = modal.offsetLeft;
                initialY = modal.offsetTop;
                document.addEventListener('mousemove', onMouseMove);
                document.addEventListener('mouseup', onMouseUp);
                modal.style.cursor = 'grabbing';
            };

            function onMouseMove(event) {
                if (!isDragging) return;
                let dx = event.clientX - startX;
                let dy = event.clientY - startY;
                modal.style.left = initialX + dx + 'px';
                modal.style.top = initialY + dy + 'px';
            }

            function onMouseUp() {
                isDragging = false;
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', onMouseUp);
                modal.style.cursor = 'move';
            }

            modal.ondragstart = function() {
                return false;
            };
        }

        function createCodeModal(title, showAnalyzeButton = true) {
            var modal = document.createElement('div');
            modal.className = 'codeModal';
            modal.innerHTML = '<span class="modal-title">' + title + '</span><button class="close-button" onclick="closeCodeModal(this)">✖</button>' + (showAnalyzeButton ? '<button class="analyze-button" onclick="analyzeCode(this)">🔍</button>' : '') + '<pre><code class="codeBlock"></code></pre>';
            document.body.appendChild(modal);
            makeDraggable(modal);
            return modal;
        }

        function closeCodeModal(button) {
            var modal = button.parentElement;
            modal.style.display = 'none';
        }

        function analyzeCode(button) {
            var modal = button.parentElement;
            var codeBlock = modal.querySelector('.codeBlock').textContent;
            fetch('/analyze', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ code: codeBlock })
            })
            .then(response => response.json())
            .then(data => {
                var analysisModal = createCodeModal("QA-Pilot", false);
                var codeBlock = analysisModal.querySelector('.codeBlock');
                codeBlock.textContent = data.analysis;
                hljs.highlightElement(codeBlock);
                document.body.appendChild(analysisModal);
                analysisModal.style.display = 'block';
                analysisModal.style.left = modal.style.left;
                analysisModal.style.top = modal.style.top;
            });
        }

        function showCodeModal(event) {
            var target = event.target;
            var targetData = target.data();

            if (!targetData || !targetData.source) return;

            var modal = target.codeModal || createCodeModal("Source Code");
            target.codeModal = modal;

            var codeBlock = modal.querySelector('.codeBlock');
            codeBlock.textContent = targetData.source;
            hljs.highlightElement(codeBlock);
            modal.style.display = 'block';

            var containerRect = document.getElementById('cy-container').getBoundingClientRect();
            var modalRect = modal.getBoundingClientRect();
            modal.style.left = (containerRect.width - modalRect.width) / 2 + 'px';
            modal.style.top = (containerRect.height - modalRect.height) / 2 + 'px';
            modal.style.maxHeight = '100vh';
        }

        function buildFileTree(fileTree, container) {
            var ul = document.createElement('ul');
            fileTree.forEach(function(item) {
                var li = document.createElement('li');
                if (item.type === 'directory') {
                    li.className = 'directory';
                    li.innerHTML = '<i class="fas fa-folder"></i> ' + item.name;
                    var childrenContainer = document.createElement('div');
                    childrenContainer.style.display = 'none';
                    li.appendChild(childrenContainer);
                    li.onclick = function(event) {
                        event.stopPropagation();
                        document.querySelectorAll('.selected').forEach(function(el) {
                            el.classList.remove('selected');
                        });
                        li.classList.add('selected');
                        childrenContainer.style.display = childrenContainer.style.display === 'none' ? 'block' : 'none';
                    };
                    buildFileTree(item.children, childrenContainer);
                } else if (item.type === 'file') {
                    li.className = 'file';
                    li.innerHTML = '<i class="fas fa-file"></i> ' + item.name;
                    li.dataset.path = item.path;
                    li.onclick = function(event) {
                        event.stopPropagation();
                        document.querySelectorAll('.selected').forEach(function(el) {
                            el.classList.remove('selected');
                        });
                        li.classList.add('selected');
                        fetch('/go_data?filepath=' + encodeURIComponent(item.path))
                            .then(response => response.json())
                            .then(data => {
                                const nodeIds = new Set();
                                window.cy.elements().remove();
                                data.nodeDataArray.forEach(node => {
                                    nodeIds.add(node.key);
                                    window.cy.add({
                                        group: 'nodes',
                                        data: {
                                            id: node.key,
                                            name: node.name,
                                            class: node.class,
                                            source: node.source
                                        }
                                    });
                                });

                                const addedEdges = new Set();
                                data.linkDataArray.forEach(link => {
                                    const edgeId = `${link.from}-${link.to}-${link.category}`;
                                    if (nodeIds.has(link.from) && nodeIds.has(link.to) && !addedEdges.has(edgeId)) {
                                        window.cy.add({
                                            group: 'edges',
                                            data: {
                                                source: link.from,
                                                target: link.to,
                                                color: link.color,
                                                category: link.category
                                            }
                                        });
                                        addedEdges.add(edgeId);
                                    } else if (!nodeIds.has(link.from) || !nodeIds.has(link.to)) {
                                        console.warn(`Edge from ${link.from} to ${link.to} ignored because one or both nodes do not exist`);
                                    } else {
                                        console.warn(`Duplicate edge from ${link.from} to ${link.to} ignored`);
                                    }
                                });

                                window.cy.nodes().on('click', showCodeModal);
                                window.cy.layout({ name: 'dagre' }).run();
                            });
                    };
                }
                ul.appendChild(li);
            });
            container.appendChild(ul);
        }

        function fetchDirectory() {
            fetch('/go_directory')
                .then(response => response.json())
                .then(data => {
                    var container = document.getElementById('file-tree');
                    buildFileTree(data, container);
                });
        }

        function zoomIn() {
            window.cy.zoom(window.cy.zoom() * 1.2);
            window.cy.center();
        }

        function zoomOut() {
            window.cy.zoom(window.cy.zoom() * 0.8);
            window.cy.center();
        }
    </script>
</body>
</html>


================================================
FILE: templates/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CodeGPS</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.21.0/cytoscape.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/cytoscape-dagre@2.5.0/cytoscape-dagre.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
    <style>
        body {
            display: flex;
            margin: 0;
            height: 100vh;
            overflow: hidden;
        }

        #file-tree {
            width: 25%;
            border-right: 1px solid black;
            padding-left: 10px;
            overflow-y: auto;
            background-color: #333;
            color: white;
            font-size: 17px;
            font-family: Arial, sans-serif;
        }

        #cy-container {
            width: 75%;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
        }

        #cy {
            width: 100%;
            height: 90%;
            border-left: 1px solid black;
            overflow: auto;
            position: absolute;
            top: 5%;
        }

        #color-legend {
            position: absolute;
            top: 0;
            right: 0;
            background-color: #fff;
            border: 1px solid #ccc;
            padding: 5px;
            border-radius: 0 0 0 8px;
            display: flex;
            align-items: center;
            z-index: 1000;
            white-space: nowrap;
        }

        .legend-color-box {
            display: inline-block;
            width: 15px;
            height: 15px;
            margin: 0 10px; /* Adjust spacing */
        }

        #legend-list {
            font-family: Arial, sans-serif;
            display: flex;
            list-style: none;
            margin: 0;
            padding: 0;
        }

        .codeModal {
            position: absolute;
            width: 30%;
            background-color: #fdf6e3;
            border: 1px solid #d3d3d3;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
            padding: 10px;
            display: none;
            overflow: auto;
            z-index: 1000;
            resize: both;
            cursor: move;
            border-radius: 8px;
        }

        .close-button {
            position: absolute;
            top: 10px;
            right: 10px;
            cursor: pointer;
            font-size: 14px;
            padding: 5px 10px;
            background-color: #ff5c5c;
            color: white;
            border: none;
            border-radius: 50%;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        }

        .analyze-button {
            position: absolute;
            bottom: 10px;
            right: 10px;
            cursor: pointer;
            font-size: 14px;
            padding: 5px 10px;
            background-color: #4caf50;
            color: white;
            border: none;
            border-radius: 50%;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        }

        .modal-title {
            font-size: 18px;
            font-weight: bold;
            margin-bottom: 10px;
            background-color: #ffeb3b;
            padding: 5px 10px;
            border-radius: 5px;
            display: inline-block;
        }

        pre {
            margin: 0;
            padding: 10px;
            white-space: pre-wrap;
            word-wrap: break-word;
            background-color: white;
            border-radius: 8px;
            font-family: 'Courier New', Courier, monospace;
            color: #333;
            max-height: 50vh;
            overflow-y: auto;
        }

        code {
            background-color: white !important;
        }

        ul {
            list-style-type: none;
            padding-left: 20px;
        }

        .directory {
            font-weight: bold;
            cursor: pointer;
            color: white;
        }

        .file {
            cursor: pointer;
            color: white;
        }

        .directory .fa-folder {
            color: #f9a825;
        }

        .file .fa-file {
            color: #5E97F6;
        }

        .selected {
            background-color: #555; /* Selected effect */
        }

        #zoom-buttons {
            position: absolute;
            bottom: 20px;
            left: 20px;
            z-index: 1000;
        }

        .zoom-button {
            background-color: #333;
            color: white;
            border: none;
            border-radius: 50%;
            width: 30px;
            height: 30px;
            font-size: 18px;
            margin: 5px;
            cursor: pointer;
        }

        .zoom-button:hover {
            background-color: #555;
        }
    </style>
</head>
<body>
    <div id="file-tree"></div>
    <div id="cy-container">
        <div id="cy"></div>
        <div id="color-legend">
            <ul id="legend-list"></ul>
        </div>
        <div id="zoom-buttons">
            <button class="zoom-button" onclick="zoomIn()">+</button>
            <button class="zoom-button" onclick="zoomOut()">-</button>
        </div>
    </div>
    <script>
        /**
         * Initialize Cytoscape and fetch directory structure on DOMContentLoaded
         */
        document.addEventListener('DOMContentLoaded', function() {
            fetchDirectory();
            cytoscape.use(cytoscapeDagre); // Use dagre plugin

            window.cy = cytoscape({
                container: document.getElementById('cy'),
                style: [
                    {
                        selector: 'node',
                        style: {
                            'label': 'data(name)',
                            'shape': 'roundrectangle',
                            'text-valign': 'center',
                            'text-halign': 'center',
                            'width': 'label',
                            'height': 'label',
                            'padding': '10px',
                            'color': 'white', // Text color
                            'font-size': '14px', // Font size
                            'font-family': 'Arial, sans-serif' // Font family
                        }
                    },
                    {
                        selector: 'node[class="class"]',
                        style: {
                            'background-color': '#9370DB', // Class node background color
                        }
                    },
                    {
                        selector: 'node[class="method"]',
                        style: {
                            'background-color': '#34a853', // Method node background color
                        }
                    },
                    {
                        selector: 'node[class="function"]',
                        style: {
                            'background-color': '#fbbc05', // Function node background color
                        }
                    },
                    {
                        selector: 'node[class="import"]',
                        style: {
                            'background-color': '#ea4335', // Import node background color
                        }
                    },
                    {
                        selector: 'edge',
                        style: {
                            'width': 2,
                            'line-color': '#FFD700', // Solid edge color
                            'target-arrow-color': '#FFD700', // Solid arrow color
                            'target-arrow-shape': 'triangle',
                            'curve-style': 'bezier'
                        }
                    },
                    {
                        selector: 'edge[category = "dashed"]',
                        style: {
                            'line-style': 'dashed',
                            'line-color': 'data(color)', // Dashed edge color from data
                            'target-arrow-color': 'data(color)', // Dashed arrow color from data
                            'target-arrow-shape': 'triangle'
                        }
                    }
                ],
                layout: {
                    name: 'dagre',
                    nodeSep: 100, // Increase node spacing
                    rankSep: 150, // Increase layer spacing
                    rankDir: 'TB',
                    animate: false
                }
            });

            addLegend();
        });

        /**
         * Add legend for node and edge colors
         */
        function addLegend() {
            const legendItems = [
                { color: '#9370DB', label: 'Class' },
                { color: '#34a853', label: 'Method' },
                { color: '#fbbc05', label: 'Function' },
                { color: '#ea4335', label: 'Import' },
                { color: '#FFD700', label: 'Solid-Direct call' },
                { color: 'gray', label: 'Dashed-Inherit' },
                { color: 'green', label: 'Dashed-Call' },
                { color: 'orange', label: 'Dashed-Import' }
            ];

            const legendList = document.getElementById('legend-list');
            legendItems.forEach(item => {
                const li = document.createElement('li');
                const colorBox = document.createElement('span');
                colorBox.style.backgroundColor = item.color;
                colorBox.className = 'legend-color-box';
                li.appendChild(colorBox);
                li.appendChild(document.createTextNode(item.label));
                legendList.appendChild(li);
            });
        }

        /**
         * Make the modal draggable
         */
        function makeDraggable(modal) {
            let isDragging = false;
            let startX, startY, initialX, initialY;

            modal.onmousedown = function(event) {
                isDragging = true;
                startX = event.clientX;
                startY = event.clientY;
                initialX = modal.offsetLeft;
                initialY = modal.offsetTop;
                document.addEventListener('mousemove', onMouseMove);
                document.addEventListener('mouseup', onMouseUp);
                modal.style.cursor = 'grabbing';
            };

            function onMouseMove(event) {
                if (!isDragging) return;
                let dx = event.clientX - startX;
                let dy = event.clientY - startY;
                modal.style.left = initialX + dx + 'px';
                modal.style.top = initialY + dy + 'px';
            }

            function onMouseUp() {
                isDragging = false;
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', onMouseUp);
                modal.style.cursor = 'move';
            }

            modal.ondragstart = function() {
                return false;
            };
        }

        /**
         * Create a modal to display code
         */
        function createCodeModal(title, showAnalyzeButton = true) {
            var modal = document.createElement('div');
            modal.className = 'codeModal';
            modal.innerHTML = '<span class="modal-title">' + title + '</span><button class="close-button" onclick="closeCodeModal(this)">✖</button>' + (showAnalyzeButton ? '<button class="analyze-button" onclick="analyzeCode(this)">🔍</button>' : '') + '<pre><code class="codeBlock"></code></pre>';
            document.body.appendChild(modal);
            makeDraggable(modal);
            return modal;
        }

        /**
         * Close the modal
         */
        function closeCodeModal(button) {
            var modal = button.parentElement;
            modal.style.display = 'none';
        }

        /**
         * Analyze the code in the modal
         */
        function analyzeCode(button) {
            var modal = button.parentElement;
            var codeBlock = modal.querySelector('.codeBlock').textContent;
            fetch('/analyze', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ code: codeBlock })
            })
            .then(response => response.json())
            .then(data => {
                var analysisModal = createCodeModal("QA-Pilot", false);
                var codeBlock = analysisModal.querySelector('.codeBlock');
                codeBlock.textContent = data.analysis;
                hljs.highlightElement(codeBlock);
                document.body.appendChild(analysisModal);
                analysisModal.style.display = 'block';
                analysisModal.style.left = modal.style.left;
                analysisModal.style.top = modal.style.top;
            });
        }

        /**
         * Show the code modal with source code
         */
        function showCodeModal(event) {
            var target = event.target;
            if (!target.data || !target.data().source) return;  // Ensure source code data is available

            var modal = target.codeModal || createCodeModal("Source Code");
            target.codeModal = modal;
            var codeBlock = modal.querySelector('.codeBlock');
            codeBlock.textContent = target.data().source;
            hljs.highlightElement(codeBlock);
            modal.style.display = 'block';

            // Center the modal
            var containerRect = document.getElementById('cy-container').getBoundingClientRect();
            var modalRect = modal.getBoundingClientRect();
            modal.style.left = (containerRect.width - modalRect.width) / 2 + 'px';
            modal.style.top = (containerRect.height - modalRect.height) / 2 + 'px';
            modal.style.maxHeight = '100vh';
        }

        /**
         * Build the file tree from the directory structure
         */
        function buildFileTree(fileTree, container) {
            var ul = document.createElement('ul');
            fileTree.forEach(function(item) {
                var li = document.createElement('li');
                if (item.type === 'directory') {
                    li.className = 'directory';
                    li.innerHTML = '<i class="fas fa-folder"></i> ' + item.name;
                    var childrenContainer = document.createElement('div');
                    childrenContainer.style.display = 'none'; // Default to collapsed
                    li.appendChild(childrenContainer);
                    li.onclick = function(event) {
                        event.stopPropagation(); // Prevent event bubbling
                        document.querySelectorAll('.selected').forEach(function(el) {
                            el.classList.remove('selected');
                        });
                        li.classList.add('selected');
                        childrenContainer.style.display = childrenContainer.style.display === 'none' ? 'block' : 'none';
                    };
                    buildFileTree(item.children, childrenContainer);
                } else if (item.type === 'file') {
                    li.className = 'file';
                    li.innerHTML = '<i class="fas fa-file"></i> ' + item.name;
                    li.dataset.path = item.path;
                    li.onclick = function(event) {
                        event.stopPropagation(); // Prevent event bubbling
                        document.querySelectorAll('.selected').forEach(function(el) {
                            el.classList.remove('selected');
                        });
                        li.classList.add('selected');
                        fetch('/data?filepath=' + encodeURIComponent(item.path))
                            .then(response => response.json())
                            .then(data => {
                                const nodeIds = new Set();
                                window.cy.elements().remove();
                                data.nodeDataArray.forEach(node => {
                                    nodeIds.add(node.key);
                                    window.cy.add({
                                        group: 'nodes',
                                        data: {
                                            id: node.key,
                                            name: node.name,
                                            class: node.class, // Differentiate node types
                                            source: node.source
                                        }
                                    });
                                });

                                const addedEdges = new Set();
                                data.linkDataArray.forEach(link => {
                                    const edgeId = `${link.from}-${link.to}-${link.category}`;
                                    if (nodeIds.has(link.from) && nodeIds.has(link.to) && !addedEdges.has(edgeId)) {
                                        window.cy.add({
                                            group: 'edges',
                                            data: {
                                                source: link.from,
                                                target: link.to,
                                                color: link.color,
                                                category: link.category
                                            }
                                        });
                                        addedEdges.add(edgeId);
                                    } else if (!nodeIds.has(link.from) || !nodeIds.has(link.to)) {
                                        console.warn(`Edge from ${link.from} to ${link.to} ignored because one or both nodes do not exist`);
                                    } else {
                                        console.warn(`Duplicate edge from ${link.from} to ${link.to} ignored`);
                                    }
                                });

                                window.cy.nodes().on('click', showCodeModal);
                                window.cy.layout({ name: 'dagre' }).run();
                            });
                    };
                }
                ul.appendChild(li);
            });
            container.appendChild(ul);
        }

        /**
         * Fetch the directory structure from the server
         */
        function fetchDirectory() {
            fetch('/directory')
                .then(response => response.json())
                .then(data => {
                    var container = document.getElementById('file-tree');
                    buildFileTree(data, container);
                });
        }

        /**
         * Zoom in the graph
         */
        function zoomIn() {
            window.cy.zoom(window.cy.zoom() * 1.2);
            window.cy.center();
        }

        /**
         * Zoom out the graph
         */
        function zoomOut() {
            window.cy.zoom(window.cy.zoom() * 0.8);
            window.cy.center();
        }
    </script>
</body>
</html>


================================================
FILE: utils/codegraph.py
================================================
import ast 
import os

def parse_python_code(filepath):
    """Parse Python code file, extract classes, methods, global functions, imported modules and their source code, and the call relationships."""
    with open(filepath, 'r', encoding='utf-8') as file:
        source = file.read()
    node = ast.parse(source)

    classes = []
    methods = []
    functions = []
    imports = []
    links = []
    method_calls = {}
    function_calls = {}
    import_calls = {}
    class_inheritance = {}

    # Extract classes and methods
    for item in ast.walk(node):
        if isinstance(item, ast.ClassDef):
            class_start_line = item.lineno - 1
            class_end_line = item.end_lineno
            class_source = "\n".join(source.splitlines()[class_start_line:class_end_line])
            classes.append({'key': item.name, 'name': item.name, 'class': 'class', 'color': 'lightblue', 'source': class_source})

            for base in item.bases:
                if isinstance(base, ast.Name):
                    class_inheritance[item.name] = base.id

            for method in [n for n in item.body if isinstance(n, ast.FunctionDef)]:
                method_start_line = method.lineno - 1
                method_end_line = method.end_lineno
                method_source = "\n".join(source.splitlines()[method_start_line:method_end_line])
                methods.append({'key': f"{item.name}.{method.name}", 'name': method.name, 'class': 'method', 'color': 'lightgreen', 'source': method_source})
                links.append({'from': item.name, 'to': f"{item.name}.{method.name}", 'color': 'blue'})

                method_calls[f"{item.name}.{method.name}"] = []
                for stmt in ast.walk(method):
                    if isinstance(stmt, ast.Call):
                        if isinstance(stmt.func, ast.Name):
                            method_calls[f"{item.name}.{method.name}"].append(stmt.func.id)
                        elif isinstance(stmt.func, ast.Attribute):
                            method_calls[f"{item.name}.{method.name}"].append(stmt.func.attr)

    # Handle inheritance relationships
    for child, parent in class_inheritance.items():
        links.append({'from': child, 'to': parent, 'category': 'dashed', 'color': 'gray'})

    # Extract global functions
    for item in node.body:
        if isinstance(item, ast.FunctionDef):
            function_start_line = item.lineno - 1
            function_end_line = item.end_lineno
            function_source = "\n".join(source.splitlines()[function_start_line:function_end_line])
            functions.append({'key': item.name, 'name': item.name, 'class': 'function', 'color': 'lightcoral', 'source': function_source})

            function_calls[item.name] = []
            for stmt in ast.walk(item):
                if isinstance(stmt, ast.Call):
                    if isinstance(stmt.func, ast.Name):
                        function_calls[item.name].append(stmt.func.id)
                    elif isinstance(stmt.func, ast.Attribute):
                        function_calls[item.name].append(stmt.func.attr)

    # Extract imported modules, classes, functions, or variables
    for item in node.body:
        if isinstance(item, ast.Import):
            for alias in item.names:
                imports.append({'key': alias.name, 'name': alias.name, 'class': 'import', 'color': 'lightyellow', 'source': f"import {alias.name}"})
        elif isinstance(item, ast.ImportFrom):
            module = item.module
            for alias in item.names:
                import_name = f"{module}.{alias.name}"
                imports.append({'key': alias.name, 'name': alias.name, 'class': 'import', 'color': 'lightyellow', 'source': f"from {module} import {alias.name}"})
                import_calls[alias.name] = import_name

    # Check which imports are actually called
    used_imports = set()
    for calls in method_calls.values():
        used_imports.update(calls)
    for calls in function_calls.values():
        used_imports.update(calls)

    # Filter out unused imports
    imports = [imp for imp in imports if imp['key'] in used_imports]

    # Add method and global function call links
    for caller, callees in method_calls.items():
        for callee in callees:
            if callee in method_calls or callee in function_calls or callee in import_calls:
                links.append({'from': caller, 'to': callee, 'category': 'dashed', 'color': 'green'})

    for caller, callees in function_calls.items():
        for callee in callees:
            if callee in method_calls or callee in function_calls or callee in import_calls:
                links.append({'from': caller, 'to': callee, 'category': 'dashed', 'color': 'green'})

    for import_name, import_module in import_calls.items():
        if import_name in used_imports:
            for caller, callees in method_calls.items():
                if import_name in callees:
                    links.append({'from': caller, 'to': import_name, 'category': 'dashed', 'color': 'orange'})
            for caller, callees in function_calls.items():
                if import_name in callees:
                    links.append({'from': caller, 'to': import_name, 'category': 'dashed', 'color': 'orange'})

    return {'nodeDataArray': classes + methods + functions + imports, 'linkDataArray': links}

def read_current_repo_path(current_session):
    if current_session:
        print("=============> path: ", os.path.join("projects", current_session['name']))
        return os.path.join("projects", current_session['name'])
    return None

def build_file_tree(directory):
    """Build the file tree for the given directory"""
    file_tree = []
    for root, dirs, files in os.walk(directory):
        for d in dirs:
            dir_path = os.path.join(root, d)
            file_tree.append({
                'name': d,
                'path': dir_path,
                'type': 'directory',
                'children': build_file_tree(dir_path)
            })
        for f in files:
            if f.endswith('.py'):
                file_path = os.path.join(root, f)
                file_tree.append({
                    'name': f,
                    'path': file_path,
                    'type': 'file'
                })
        break  # Only traverse current directory, not recursively
    return file_tree

================================================
FILE: utils/go_codegraph.py
================================================
import json
import os
import subprocess
from typing import Dict, List, Any
import re


def process_nodes(nodes: Dict[str, Any]) -> Dict[str, Any]:
    nodeDataArray = []
    linkDataArray = []
    methods = {}
    types = {}

    for key, node in nodes.items():
        print(f"Processing node: {key} -> {node}")

        if node["Type"] == "import":
            node_data = {
                "key": key,
                "name": node["Name"],
                "class": node["Type"],
                "source": "import " + node["Code"]
            }
            nodeDataArray.append(node_data)
        elif node["Type"] == "type":
            types[key] = node
            node_data = {
                "key": key,
                "name": node["Name"],
                "class": "struct",
                "source": "type " + node["Code"]
            }
            nodeDataArray.append(node_data)
        elif node["Type"] == "method":
            receiver_type = extract_receiver_type(key)
            method_name = extract_method_name(key)
            node_data = {
                "key": f"{receiver_type}.{method_name}",
                "name": method_name,
                "class": node["Type"],
                "source": node["Code"]
            }
            nodeDataArray.append(node_data)

            if receiver_type not in methods:
                methods[receiver_type] = []
            methods[receiver_type].append(node_data)
        elif node["Type"] == "func":
            node_data = {
                "key": key,
                "name": node["Name"],
                "class": "function",
                "source": node["Code"]
            }
            nodeDataArray.append(node_data)
        else:
            node_data = {
                "key": key,
                "name": node["Name"],
                "class": node["Type"],
                "source": node["Code"]
            }
            nodeDataArray.append(node_data)

    for receiver_type, method_list in methods.items():
        if receiver_type in types:
            for method in method_list:
                link = {
                    "from": receiver_type,
                    "to": method["key"],
                    "category": "dashed",
                    "color": "gray"
                }
                linkDataArray.append(link)

    for node_key, node in nodes.items():
        for call in node["Calls"]:
            print(f"Checking call from {node_key} to {call}")
            if call in nodes:
                link = {
                    "from": node_key,
                    "to": call,
                    "category": "dashed",
                    "color": "green"
                }
                linkDataArray.append(link)
                print(f"Link added: {link}")
            else:
                print(f"Call {call} not found in nodes")

    print(f"Final nodeDataArray: {nodeDataArray}")
    print(f"Final linkDataArray: {linkDataArray}")

    return {"nodeDataArray": nodeDataArray, "linkDataArray": linkDataArray}


def extract_receiver_type(key: str) -> str:
    if key.startswith("&"):
        parts = re.split(r'[{}]', key)
        receiver_type = parts[1].strip().split(' ')[-1]
    else:
        receiver_type = key.split(".")[0]

    # Remove generic type parameters if present
    receiver_type = re.sub(r'\[.*?\]', '', receiver_type).strip()
    return receiver_type


def extract_method_name(key: str) -> str:
    return key.split(".")[-1]


def parse_go_code(filepath: str) -> Dict[str, Any]:
    try:
        result = subprocess.run(['./parser', filepath], capture_output=True, text=True, check=True)
        output = result.stdout.strip()
        nodes = json.loads(output)
        return process_nodes(nodes)
    except subprocess.CalledProcessError as e:
        print(f"Error running parser: {e.stderr}")
        return {"nodeDataArray": [], "linkDataArray": []}
    except json.JSONDecodeError as e:
        print(f"Error decoding JSON: {e}")
        return {"nodeDataArray": [], "linkDataArray": []}


def go_build_file_tree(directory: str) -> List[Dict[str, Any]]:
    file_tree = []
    for root, dirs, files in os.walk(directory):
        for d in dirs:
            dir_path = os.path.join(root, d)
            file_tree.append({
                'name': d,
                'path': dir_path,
                'type': 'directory',
                'children': go_build_file_tree(dir_path)
            })
        for f in files:
            if f.endswith('.go'):
                file_path = os.path.join(root, f)
                file_tree.append({
                    'name': f,
                    'path': file_path,
                    'type': 'file'
                })
        break  # Only traverse current directory, not recursively
    return file_tree


================================================
FILE: utils/helper.py
================================================
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import Chroma
import git
import os
from queue import Queue
import shutil
from urllib.parse import urlparse
import configparser
from langchain.chains import ConversationalRetrievalChain
from langchain_core.prompts.prompt import PromptTemplate
from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
from langchain.chains import ConversationChain
from cachetools import cached, TTLCache
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import FlashrankRerank

# read from the config.ini
config_path = os.path.join('config', 'config.ini')
prompt_templates_path = 'config/prompt_templates.ini'
config = configparser.ConfigParser()
config.read(config_path)
vectorstore_dir = config.get('the_project_dirs', 'vectorstore_dir')
sessions_dir = config.get('the_project_dirs', 'sessions_dir')
project_dir = config.get('the_project_dirs', 'project_dir')
max_dir_depth = config.get('for_loop_dirs_depth', 'max_dir_depth')
chunk_size = config.get('chunk_setting', 'chunk_size')
chunk_overlap = config.get('chunk_setting', 'chunk_overlap')
base_url = config.get('ollama_llm_models', 'base_url')
encode_kwargs = {"normalize_embeddings": False}
model_kwargs = {"device": "cuda:0"}  
allowed_extensions = ['.py', '.md', '.js',
                    '.html', '.css', '.ts', '.sh',
                    '.go', '.java', '.svelte']

# remove the directories for the download/upload projects
def remove_directory(dir_path):
    if os.path.exists(dir_path):
        # check and update file permisiion first
        for root, dirs, files in os.walk(dir_path, topdown=False):
            for name in files:
                filepath = os.path.join(root, name)
                try:
                    os.chmod(filepath, 0o777)  # modify permission
                except PermissionError:
                    pass
            for name in dirs:
                dirpath = os.path.join(root, name)
                os.chmod(dirpath, 0o777)  
        
        # remove it
        shutil.rmtree(dir_path, ignore_errors=True)


def document_to_string(document):
    # Get the document content
    content = document.page_content
    # Get the metadata
    metadata = document.metadata
    # Convert the metadata dictionary to a string
    metadata_str = ', '.join(f'{key}: {value}' for key, value in metadata.items())
    # Combine the content and metadata into a single string
    result = f'Content: {content}\nMetadata: {metadata_str}'
    return result

def documents_to_string(documents):
    # Convert each Document object to a string and add to the list
    doc_strings = [document_to_string(doc) for doc in documents]
    # Join all strings into a single large string
    return '\n\n'.join(doc_strings)


def load_prompt_templates(path):
    config = configparser.ConfigParser()
    config.read(path)
    return {section: dict(config.items(section)) for section in config.sections()}

cache = TTLCache(maxsize=100, ttl=300)

class DataHandler:
    def __init__(self, git_url, chat_model, embedding_model) -> None:
        self.git_url = git_url
        last_part = git_url.split('/')[-1]
        self.repo_name = last_part.rsplit('.', 1)[0]
        # create the store db and project dir
        if not os.path.exists(vectorstore_dir):
            os.makedirs(vectorstore_dir)
        if not os.path.exists(project_dir):
            os.makedirs(project_dir)
        # config the path
        self.db_dir = os.path.join(vectorstore_dir, self.repo_name)
        self.download_path = os.path.join(project_dir, self.repo_name) 
        self.model = chat_model
        self.embedding_model = embedding_model     
        self.ChatQueue =  Queue(maxsize=2)

    # check the db dir exist or not
    def db_exists(self):
        return os.path.exists(self.db_dir)

    # update the chat message queue
    def update_chat_queue(self, value):
        if self.ChatQueue.full():
            self.ChatQueue.get()
        self.ChatQueue.put(value)

    # download or upload the project
    def git_clone_repo(self):
        url_parts = urlparse(self.git_url)

        # upload situation
        if not url_parts.scheme:
            print("Local repository detected, skipping cloning process.")
        else:
            # git clone
            if not os.path.exists(self.download_path):
                print(f"Cloning from Git URL: {self.git_url}")
                try:
                    git.Repo.clone_from(self.git_url, self.download_path)
                    print("Repository cloned successfully.")
                except Exception as e:
                    print(f"Failed to clone repository. Error: {e}")

    # load the projects
    def load_files(self, root_dir=None, current_depth=0, base_depth=0):
        if root_dir is None:
            root_dir = self.download_path
        self.docs = []
        
        print("Loading files from:", root_dir)
        
        # github projects
        if "UploadedRepo" not in self.git_url:
            for dirpath, _, filenames in os.walk(root_dir):
                for filename in filenames:
                    if any(filename.endswith(ext) for ext in allowed_extensions):
                        file_path = os.path.join(dirpath, filename)
                        try:
                            loader = TextLoader(file_path, encoding='utf-8')
                            self.docs.extend(loader.load_and_split())
                        except Exception as e:
                            print(f"Error loading file {file_path}: {e}")
        else:
            # sosreport project or directories upload
            if current_depth - base_depth > int(max_dir_depth):
                return  # over the dir depth, then stop
            
            for entry in os.scandir(root_dir):
                if entry.is_symlink():
                    continue  # skip the soft link(should be for windows)
                elif entry.is_dir():
                    if entry.name == 'boot':
                        base_depth = current_depth  # start from boot
                    self.load_files(entry.path, current_depth + 1, base_depth)
                elif entry.is_file():
                    # not limit for the file extension
                    try:
                        loader = TextLoader(entry.path, encoding='utf-8')
                        self.docs.extend(loader.load_and_split())
                    except Exception as e:
                        print(f"Error loading file {entry.path}: {e}")

    # split all the files
    def split_files(self):
        text_splitter = CharacterTextSplitter(chunk_size=int(chunk_size), chunk_overlap=int(chunk_overlap))
        self.texts = text_splitter.split_documents(self.docs)

    # store the all file chunk into chromadb
    def store_chroma(self):  
        if not os.path.exists(self.db_dir):
            os.makedirs(self.db_dir)
        print("eb:", self.embedding_model)
        db = Chroma.from_documents(self.texts, self.embedding_model, persist_directory=self.db_dir) 
        db.persist()  
        return db  
        
    # load 
    def load_into_db(self):
        if not os.path.exists(self.db_dir): 
            ## Create and load
            self.load_files()
            self.split_files()
            self.db = self.store_chroma()
        else:
            print("start-->chromadb")
            # Just load the DB
            self.db = Chroma(persist_directory=self.db_dir, embedding_function=self.embedding_model)
            print("end-->chromadb")
        
        self.retriever = self.db.as_retriever()
        self.retriever.search_kwargs['k'] = 3
        self.retriever.search_type = 'similarity'

    # create a chain, send the message into llm and ouput the answer
    @cached(cache)
    def retrieval_qa(self, query, rsd=False, rr=False):
        config = configparser.ConfigParser()
        config.read(config_path)
        the_selected_provider = config.get('model_providers', 'selected_provider')
        chat_history = list(self.ChatQueue.queue)

        try:
            templates = load_prompt_templates(prompt_templates_path)
            qa_template_name = config.get('prompt_templates', 'qa_selected_prompt')
            qa_template = templates['qa_prompt_templates'][qa_template_name]
        except Exception as e:
            print(f"Error reading config or templates: {e}")
            raise

        custom_prompt = ChatPromptTemplate.from_messages([
            SystemMessagePromptTemplate.from_template(qa_template), 
            HumanMessagePromptTemplate.from_template("{question}")])
        
        if the_selected_provider != 'localai':
            # add reranker
            if rr:
                compressor = FlashrankRerank()
                compression_retriever = ContextualCompressionRetriever(
                    base_compressor=compressor, base_retriever=self.retriever
                )
                the_retriever = compression_retriever
            else:
                the_retriever = self.retriever

                    
            qa = ConversationalRetrievalChain.from_llm(
                self.model, 
                chain_type="stuff", 
                retriever=the_retriever, 
                condense_question_llm = self.model,
                return_source_documents=True,
                combine_docs_chain_kwargs={"prompt": custom_prompt})
            
            result = qa({"question": query, "chat_history": chat_history})

            self.update_chat_queue((query, result["answer"]))

            # add the search source documents
            docs_strings = document_to_string(result['source_documents'][0])
            # docs_strings = documents_to_string(result['source_documents'])

            if rsd:
                return docs_strings
            else:
                return result['answer']
            
        elif the_selected_provider == 'localai':
            docs = self.retriever.get_relevant_documents(query)

            ds2s = documents_to_string(docs)

            the_question = """   
            the question: {question}"""  

            # build the prompt with string
            combine_strings = qa_template + the_question
            prompt = combine_strings.format(context=ds2s, question=query)
            result = self.model.complete(prompt)
            self.update_chat_queue((query, result.text))
            if rsd:
                return ds2s
            return result.text
        
        else:
            return "Wrong provider!!!"
        
    @cached(cache)
    def restrieval_qa_for_code(self, query):
        config = configparser.ConfigParser()
        config.read(config_path)
        the_selected_provider = config.get('model_providers', 'selected_provider')

        try:
            templates = load_prompt_templates(prompt_templates_path)
            code_template_name = config.get('prompt_templates', 'code_selected_prompt')
            localai_template_name = config.get('prompt_templates', 'localai_selected_prompt')
            
            code_template = templates['qa_prompt_templates'][code_template_name]
            code_template_localai = templates['qa_prompt_templates'][localai_template_name]
        except Exception as e:
            print(f"Error reading config or templates: {e}")
            raise

        if the_selected_provider != 'localai':
            PROMPT = PromptTemplate(input_variables=["input", "history"], template=code_template)
            # print("-->", datetime.now())
            conversation = ConversationChain(
                prompt=PROMPT,
                llm=self.model,
            )
            # print("-->", datetime.now())
            code_anaylsis = conversation.predict(input=query)
            return code_anaylsis
        elif the_selected_provider == 'localai':
            prompt = code_template_localai.format(input=query)
            result = self.model.complete(prompt)
            return result.text
        else:
            return "Wrong provider!!!"
Download .txt
gitextract_ot3ibsoy/

├── LICENSE
├── README.md
├── app.py
├── check_postgresql_connection.py
├── config/
│   ├── config.ini
│   └── prompt_templates.ini
├── llamacpp_models/
│   └── model_dir
├── parser.go
├── qa_model_apis.py
├── qa_pilot_run.py
├── requirements.txt
├── svelte-app/
│   ├── .gitignore
│   ├── README.md
│   ├── package.json
│   ├── public/
│   │   ├── global.css
│   │   └── index.html
│   ├── rollup.config.js
│   ├── scripts/
│   │   └── setupTypeScript.js
│   └── src/
│       ├── ApiKeyModal.svelte
│       ├── App.svelte
│       ├── Chat.svelte
│       ├── ConfigEditor.svelte
│       ├── DeleteConfirmationModal.svelte
│       ├── DeleteTemplateModal.svelte
│       ├── LlamaCppModelsModal.svelte
│       ├── NewSourceModal.svelte
│       ├── PromptTemplatesModal.svelte
│       ├── config.js
│       ├── global.css
│       └── main.js
├── templates/
│   ├── go_index.html
│   └── index.html
└── utils/
    ├── codegraph.py
    ├── go_codegraph.py
    └── helper.py
Download .txt
SYMBOL INDEX (62 symbols across 8 files)

FILE: app.py
  function init_db (line 67) | def init_db():
  function load_models_if_needed (line 93) | def load_models_if_needed():
  function create_message_table (line 114) | def create_message_table(session_id):
  function load_config (line 136) | def load_config():
  function get_config (line 141) | async def get_config():
  function save_config (line 147) | async def save_config(request: Request):
  function update_provider (line 160) | async def update_provider(request: Request):
  function update_model (line 169) | async def update_model(request: Request):
  function load_repo (line 179) | async def load_repo(request: Request):
  function chat (line 197) | async def chat(request: Request):
  function get_sessions (line 245) | async def get_sessions():
  function save_sessions (line 261) | async def save_sessions(request: Request):
  function get_messages (line 282) | async def get_messages(session_id: int):
  function update_current_session (line 299) | async def update_current_session(request: Request):
  function delete_session (line 305) | async def delete_session(session_id: int):
  function check_api_key (line 340) | async def check_api_key(request: Request):
  function save_api_key (line 351) | async def save_api_key(request: Request):
  function list_llamacpp_models (line 366) | async def list_llamacpp_models():
  function upload_llamacpp_model (line 375) | async def upload_llamacpp_model(file: UploadFile = File(...), chunk: int...
  function delete_llamacpp_model (line 404) | async def delete_llamacpp_model(model_name: str):
  function get_prompt_templates (line 414) | async def get_prompt_templates():
  function delete_prompt_template (line 438) | async def delete_prompt_template(request: Request):
  function save_prompt_templates (line 456) | async def save_prompt_templates(request: Request):
  function codegraph_home (line 468) | async def codegraph_home(request: Request):
  function data (line 473) | async def data(filepath: str):
  function directory (line 478) | async def directory():
  function analyze (line 486) | async def analyze(request: Request):
  function go_codegraph_home (line 499) | async def go_codegraph_home(request: Request):
  function go_data (line 503) | async def go_data(filepath: str):
  function directory (line 510) | async def directory():

FILE: parser.go
  type Node (line 13) | type Node struct
  function main (line 21) | func main() {
  function getNodeCode (line 123) | func getNodeCode(fset *token.FileSet, start, end token.Pos, filePath str...
  function getParentFunc (line 130) | func getParentFunc(node *ast.File, pos token.Pos, fset *token.FileSet) s...
  function cleanType (line 152) | func cleanType(typeName string) string {

FILE: qa_model_apis.py
  function get_chat_model (line 28) | def get_chat_model(provider, model_name=''):
  function get_embedding_model (line 94) | def get_embedding_model(eb_provider, model_name='', model_kwargs='', enc...

FILE: svelte-app/rollup.config.js
  function serve (line 11) | function serve() {

FILE: svelte-app/src/config.js
  constant API_BASE_URL (line 1) | const API_BASE_URL = 'http://localhost:5000';

FILE: utils/codegraph.py
  function parse_python_code (line 4) | def parse_python_code(filepath):
  function read_current_repo_path (line 111) | def read_current_repo_path(current_session):
  function build_file_tree (line 117) | def build_file_tree(directory):

FILE: utils/go_codegraph.py
  function process_nodes (line 8) | def process_nodes(nodes: Dict[str, Any]) -> Dict[str, Any]:
  function extract_receiver_type (line 97) | def extract_receiver_type(key: str) -> str:
  function extract_method_name (line 109) | def extract_method_name(key: str) -> str:
  function parse_go_code (line 113) | def parse_go_code(filepath: str) -> Dict[str, Any]:
  function go_build_file_tree (line 127) | def go_build_file_tree(directory: str) -> List[Dict[str, Any]]:

FILE: utils/helper.py
  function remove_directory (line 37) | def remove_directory(dir_path):
  function document_to_string (line 55) | def document_to_string(document):
  function documents_to_string (line 66) | def documents_to_string(documents):
  function load_prompt_templates (line 73) | def load_prompt_templates(path):
  class DataHandler (line 80) | class DataHandler:
    method __init__ (line 81) | def __init__(self, git_url, chat_model, embedding_model) -> None:
    method db_exists (line 98) | def db_exists(self):
    method update_chat_queue (line 102) | def update_chat_queue(self, value):
    method git_clone_repo (line 108) | def git_clone_repo(self):
    method load_files (line 125) | def load_files(self, root_dir=None, current_depth=0, base_depth=0):
    method split_files (line 164) | def split_files(self):
    method store_chroma (line 169) | def store_chroma(self):
    method load_into_db (line 178) | def load_into_db(self):
    method retrieval_qa (line 196) | def retrieval_qa(self, query, rsd=False, rr=False):
    method restrieval_qa_for_code (line 268) | def restrieval_qa_for_code(self, query):
Condensed preview — 35 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (186K chars).
[
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 6899,
    "preview": "<p align=\"center\">\n  <img src=\"https://github.com/reid41/QA-Pilot/assets/25558653/4b45b525-5fac-4a3c-94e9-46364bdb36c3\" "
  },
  {
    "path": "app.py",
    "chars": 18914,
    "preview": "from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form\nfrom fastapi.responses import JSONResponse\nf"
  },
  {
    "path": "check_postgresql_connection.py",
    "chars": 621,
    "preview": "import psycopg2\nimport configparser\n\nconfig = configparser.ConfigParser()\nconfig.read('config/config.ini')\ndb_config = c"
  },
  {
    "path": "config/config.ini",
    "chars": 1926,
    "preview": "[app_setting]\nversion = v2.15.6\n\n[model_providers]\nprovider_list = openai, ollama, mistralai, localai, zhipuai, anthropi"
  },
  {
    "path": "config/prompt_templates.ini",
    "chars": 593,
    "preview": "[qa_prompt_templates]\nqa_template = \"\"\"I want you to act as a very senior code developer who is familar with github/gitl"
  },
  {
    "path": "llamacpp_models/model_dir",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "parser.go",
    "chars": 3962,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"os\"\n\t\"strings\"\n)\n\ntype Node struct {"
  },
  {
    "path": "qa_model_apis.py",
    "chars": 3708,
    "preview": "from langchain_community.chat_models import ChatOllama\nfrom langchain_mistralai.chat_models import ChatMistralAI\nfrom la"
  },
  {
    "path": "qa_pilot_run.py",
    "chars": 162,
    "preview": "import uvicorn\nfrom app import app\n\nif __name__ == \"__main__\":\n    print(\"Starting server on http://0.0.0.0:5000\")\n    u"
  },
  {
    "path": "requirements.txt",
    "chars": 3613,
    "preview": "aiohttp==3.9.3\naiosignal==1.3.1\naltair==5.2.0\nannotated-types==0.6.0\nanthropic==0.28.1\nanyio==4.3.0\nasgiref==3.8.1\nasync"
  },
  {
    "path": "svelte-app/.gitignore",
    "chars": 41,
    "preview": "/node_modules/\n/public/build/\n\n.DS_Store\n"
  },
  {
    "path": "svelte-app/README.md",
    "chars": 3282,
    "preview": "This repo is no longer maintained. Consider using `npm init vite` and selecting the `svelte` option or — if you want a f"
  },
  {
    "path": "svelte-app/package.json",
    "chars": 752,
    "preview": "{\n  \"name\": \"svelte-app\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"roll"
  },
  {
    "path": "svelte-app/public/global.css",
    "chars": 890,
    "preview": "html, body {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n}\n\nbody {\n\tcolor: #333;\n\tmargin: 0;\n\tpadding: 8px;\n\tbox-s"
  },
  {
    "path": "svelte-app/public/index.html",
    "chars": 393,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset='utf-8'>\n\t<meta name='viewport' content='width=device-width,initi"
  },
  {
    "path": "svelte-app/rollup.config.js",
    "chars": 1885,
    "preview": "import { spawn } from 'child_process';\nimport svelte from 'rollup-plugin-svelte';\nimport commonjs from '@rollup/plugin-c"
  },
  {
    "path": "svelte-app/scripts/setupTypeScript.js",
    "chars": 4420,
    "preview": "// @ts-check\n\n/** This script modifies the project to support TS code in .svelte files like:\n\n  <script lang=\"ts\">\n  \tex"
  },
  {
    "path": "svelte-app/src/ApiKeyModal.svelte",
    "chars": 4787,
    "preview": "<script>\n    import { createEventDispatcher } from 'svelte';\n    import { API_BASE_URL } from './config.js';\n\n    export"
  },
  {
    "path": "svelte-app/src/App.svelte",
    "chars": 17331,
    "preview": "<script>\n    import { onMount } from 'svelte';\n    import ConfigEditor from './ConfigEditor.svelte';\n    import Chat fro"
  },
  {
    "path": "svelte-app/src/Chat.svelte",
    "chars": 8589,
    "preview": "<script>\n    import { onMount, afterUpdate } from 'svelte';\n    import { marked } from 'marked';\n    export let currentR"
  },
  {
    "path": "svelte-app/src/ConfigEditor.svelte",
    "chars": 2012,
    "preview": "<!-- src/ConfigEditor.svelte -->\n<script>\n    export let configData = {};\n    export let configOrder = [];\n    export le"
  },
  {
    "path": "svelte-app/src/DeleteConfirmationModal.svelte",
    "chars": 2059,
    "preview": "<script>\n    import { createEventDispatcher } from 'svelte';\n    export let isOpen = false;\n    export let sessionName ="
  },
  {
    "path": "svelte-app/src/DeleteTemplateModal.svelte",
    "chars": 2374,
    "preview": "<script>\n    import { createEventDispatcher } from 'svelte';\n    export let isOpen = false;\n    export let templateName "
  },
  {
    "path": "svelte-app/src/LlamaCppModelsModal.svelte",
    "chars": 5966,
    "preview": "<script>\n    import { createEventDispatcher, onMount } from 'svelte';\n    import { API_BASE_URL } from './config.js';\n  "
  },
  {
    "path": "svelte-app/src/NewSourceModal.svelte",
    "chars": 1988,
    "preview": "<script>\n    import { createEventDispatcher } from 'svelte';\n    export let isOpen = false;\n    const dispatch = createE"
  },
  {
    "path": "svelte-app/src/PromptTemplatesModal.svelte",
    "chars": 7118,
    "preview": "<script>\n    import { createEventDispatcher, onMount } from 'svelte';\n    import { API_BASE_URL } from './config.js';\n  "
  },
  {
    "path": "svelte-app/src/config.js",
    "chars": 53,
    "preview": "export const API_BASE_URL = 'http://localhost:5000';\n"
  },
  {
    "path": "svelte-app/src/global.css",
    "chars": 133,
    "preview": "a {\n    color: #00ffff !important;\n    text-decoration: none !important; \n}\n\na:hover {\n    text-decoration: underline !i"
  },
  {
    "path": "svelte-app/src/main.js",
    "chars": 126,
    "preview": "import './global.css'\nimport App from './App.svelte';\n\nconst app = new App({\n  target: document.body\n});\n\nexport default"
  },
  {
    "path": "templates/go_index.html",
    "chars": 18345,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Go CodeGPS</title>\n    <script src=\"https:"
  },
  {
    "path": "templates/index.html",
    "chars": 19624,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>CodeGPS</title>\n    <script src=\"https://c"
  },
  {
    "path": "utils/codegraph.py",
    "chars": 6378,
    "preview": "import ast \nimport os\n\ndef parse_python_code(filepath):\n    \"\"\"Parse Python code file, extract classes, methods, global "
  },
  {
    "path": "utils/go_codegraph.py",
    "chars": 4783,
    "preview": "import json\nimport os\nimport subprocess\nfrom typing import Dict, List, Any\nimport re\n\n\ndef process_nodes(nodes: Dict[str"
  },
  {
    "path": "utils/helper.py",
    "chars": 12179,
    "preview": "from langchain_community.document_loaders import TextLoader\nfrom langchain.text_splitter import CharacterTextSplitter\nfr"
  }
]

About this extraction

This page contains the full source code of the reid41/QA-Pilot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 35 files (173.1 KB), approximately 41.8k tokens, and a symbol index with 62 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!