Skip to content

Commit aaa1b57

Browse files
committed
first commit
0 parents  commit aaa1b57

19 files changed

Lines changed: 309 additions & 0 deletions

.github/dependabot.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: 2
2+
3+
updates:
4+
- package-ecosystem: pip
5+
directory: "/"
6+
schedule:
7+
interval: daily
8+
time: "15:00"
9+
10+
- package-ecosystem: github-actions
11+
directory: "/"
12+
schedule:
13+
interval: daily
14+
time: "15:00"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: Docker Image CI(GitHub)
3+
4+
on:
5+
push:
6+
7+
8+
jobs:
9+
build:
10+
name: Build & Push
11+
runs-on: ubuntu-latest
12+
env:
13+
REPOSITORY_URL: ghcr.io
14+
IMAGE_NAME: oldtyt/python-ai-console
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4.1.2
18+
- name: Variables
19+
id: vars
20+
run: echo "::set-output name=SHORT_COMMIT_ID::$(git rev-parse --short HEAD)"
21+
- name: Build image
22+
id: docker-build
23+
run: >-
24+
docker build
25+
-t $IMAGE_NAME
26+
-t $REPOSITORY_URL/$IMAGE_NAME:latest .
27+
- name: Docker Login
28+
uses: docker/login-action@v3
29+
with:
30+
registry: ${{ env.REPOSITORY_URL }}
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
- name: Publish Image
34+
id: docker-push
35+
run: docker push --all-tags $REPOSITORY_URL/$IMAGE_NAME

Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
ARG IMAGE=python:3.13-alpine
2+
3+
FROM $IMAGE AS builder
4+
5+
COPY requirements.txt .
6+
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt
7+
8+
FROM $IMAGE
9+
10+
WORKDIR /app
11+
COPY --from=builder /app/wheels /wheels
12+
13+
RUN apk add --no-cache musl-locales musl-locales-lang
14+
ENV LANG=C.UTF-8 \
15+
LC_ALL=C.UTF-8 \
16+
PYTHONIOENCODING=utf-8
17+
18+
RUN pip install --no-cache /wheels/*
19+
20+
COPY . .
21+
22+
ENTRYPOINT [ "python3" ]
23+
24+
CMD ["/app/main.py"]

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Python AI Console
2+
3+
Python AI Console is an interactive command-line tool that allows users to input prompts, generates Python code using an AI model (powered by OpenAI), executes the generated code, and maintains a history of interactions. It captures stdout/stderr from code execution and stores everything in a JSON-based history file for context in future prompts.
4+
5+
## Features
6+
- Interactive prompt loop for user inputs.
7+
- AI-generated Python code based on prompts and conversation history.
8+
- Safe execution of generated code with output capturing.
9+
- Persistent history management using Pydantic models.
10+
- Support for environment variables to customize behavior.
11+
- Docker support for easy deployment.
12+
13+
## Installation
14+
15+
### From Source
16+
1. Clone the repository:
17+
```
18+
git clone https://github.com/OldTyT/python-ai-console.git
19+
```
20+
2. Navigate to the project directory:
21+
```
22+
cd python-ai-console
23+
```
24+
3. Install dependencies:
25+
```
26+
pip install -r requirements.txt
27+
```
28+
4. Run the application:
29+
```
30+
python3 main.py
31+
```
32+
33+
### Using Docker
34+
You can run the project in a Docker container. Ensure you have Docker installed.
35+
36+
```
37+
docker run --rm -ti -e HISTORY_PATH=/history/history.json -e OPENAI_API_KEY=YOUR_KEY -v my_history:/history ghcr.io/oldtyt/autopost-python-ai-console
38+
```
39+
40+
- Replace `YOUR_KEY` with your actual OpenAI API key.
41+
42+
## Usage
43+
Once running, the console will prompt you for input:
44+
- Enter a prompt (e.g., "Write a function to calculate factorial").
45+
- The AI generates Python code based on the prompt and recent history.
46+
- You'll be asked if you want to execute the code (y/n).
47+
- If executed, outputs (stdout/stderr) are captured and displayed.
48+
- History is saved to the specified file for context in future interactions.
49+
50+
To exit, interrupt the process (e.g., Ctrl+C).
51+
52+
## Environment Variables
53+
Configure the application using these environment variables:
54+
55+
- **HISTORY_PATH**: Path to the JSON file for storing interaction history. Default: `history.json`.
56+
- **HISTORY_SIZE**: Number of recent history entries to include in AI prompts for context. Default: `20`.
57+
- **OPENAI_API_KEY**: Your OpenAI API key (required for AI code generation).
58+
59+
Example (on Unix-like systems):
60+
```
61+
export OPENAI_API_KEY=your-api-key
62+
export HISTORY_SIZE=10
63+
python3 main.py
64+
```
65+
66+
## Dependencies
67+
- Python 3.10+ (tested up to 3.13)
68+
- Libraries: openai, pydantic, loguru, etc. (see `requirements.txt`)
69+
70+
## Contributing
71+
Contributions are welcome! Please open an issue or submit a pull request.

history.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"history": [{"datetime": "2025-08-11T14:41:08.915812", "user_prompt": null, "ai_model_response": null, "stdout_result_code_execute": null, "stderr_result_code_execute": null}, {"datetime": "2025-08-11T14:41:08.915524", "user_prompt": "\u041f\u0440\u0438\u0432\u0435\u0442! \u041a\u0430\u043a \u0434\u0435\u043b\u0430? \u041a\u0430\u043a\u0430\u044f \u0441\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0433\u043e\u0434\u0430 \u0432 \u043c\u043e\u0441\u043a\u0432\u0435?", "ai_model_response": "import datetime, sys\ntry:\n import requests\n def fetch():\n r = requests.get('https://wttr.in/Moscow?format=%l:+%c+%t+%h+%w&lang=ru', timeout=10)\n r.encoding = 'utf-8'\n if r.status_code == 200 and r.text.strip():\n return r.text.strip()\n raise Exception(f'HTTP {r.status_code}')\nexcept Exception:\n from urllib import request, error\n def fetch():\n try:\n with request.urlopen('https://wttr.in/Moscow?format=%l:+%c+%t+%h+%w&lang=ru', timeout=10) as r:\n return r.read().decode('utf-8').strip()\n except error.HTTPError as e:\n raise Exception(f'HTTP {e.code}')\n except Exception as e:\n raise\n\nnow = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')\ntry:\n weather = fetch()\n print(\"\u041f\u0440\u0438\u0432\u0435\u0442! \u0423 \u043c\u0435\u043d\u044f \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e.\")\n print(f\"\u0412\u0440\u0435\u043c\u044f: {now}\")\n print(f\"\u0421\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0433\u043e\u0434\u0430 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435: {weather}\")\nexcept Exception as e:\n print(\"\u041f\u0440\u0438\u0432\u0435\u0442! \u0423 \u043c\u0435\u043d\u044f \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e.\")\n print(f\"\u0412\u0440\u0435\u043c\u044f: {now}\")\n print(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u043e\u0433\u043e\u0434\u0443 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u043e\u0437\u0436\u0435.\")", "stdout_result_code_execute": "\u041f\u0440\u0438\u0432\u0435\u0442! \u0423 \u043c\u0435\u043d\u044f \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e.\n\u0412\u0440\u0435\u043c\u044f: 2025-08-11 14:41:24\n\u0421\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0433\u043e\u0434\u0430 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435: Moscow: \u26c5\ufe0f +15\u00b0C 94% \u21977km/h\n", "stderr_result_code_execute": ""}, {"datetime": "2025-08-11T14:41:50.094569", "user_prompt": "\u041c\u043e\u0436\u0435\u0448\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c? \u0423 \u043c\u0435\u043d\u044f \u0432\u044b\u0432\u043e\u0434 \u043f\u0440\u043e\u043f\u0430\u043b", "ai_model_response": "import datetime, sys\ntry:\n import requests\n def fetch():\n try:\n r = requests.get('https://wttr.in/Moscow?format=%l:+%c+%t+%h+%w&lang=ru', timeout=10)\n r.encoding = 'utf-8'\n if r.status_code == 200 and r.text.strip():\n return r.text.strip()\n raise Exception(f'HTTP {r.status_code}')\n except Exception:\n raise\nexcept Exception:\n from urllib import request, error\n def fetch():\n try:\n with request.urlopen('https://wttr.in/Moscow?format=%l:+%c+%t+%h+%w&lang=ru', timeout=10) as r:\n return r.read().decode('utf-8').strip()\n except error.HTTPError as e:\n raise Exception(f'HTTP {e.code}')\n except Exception:\n raise\n\nnow = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')\ntry:\n weather = fetch()\n print(\"\u041f\u0440\u0438\u0432\u0435\u0442! \u0423 \u043c\u0435\u043d\u044f \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e.\")\n print(f\"\u0412\u0440\u0435\u043c\u044f: {now}\")\n print(f\"\u0421\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0433\u043e\u0434\u0430 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435: {weather}\")\nexcept Exception:\n print(\"\u041f\u0440\u0438\u0432\u0435\u0442! \u0423 \u043c\u0435\u043d\u044f \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e.\")\n print(f\"\u0412\u0440\u0435\u043c\u044f: {now}\")\n print(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u043e\u0433\u043e\u0434\u0443 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u043e\u0437\u0436\u0435.\")", "stdout_result_code_execute": "\u041f\u0440\u0438\u0432\u0435\u0442! \u0423 \u043c\u0435\u043d\u044f \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e, \u0441\u043f\u0430\u0441\u0438\u0431\u043e.\n\u0412\u0440\u0435\u043c\u044f: 2025-08-11 14:42:03\n\u0421\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0433\u043e\u0434\u0430 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435: Moscow: \u26c5\ufe0f +15\u00b0C 94% \u21977km/h\n", "stderr_result_code_execute": ""}, {"datetime": "2025-08-11T14:45:50.573751", "user_prompt": "\u041a\u0430\u043a\u0430\u044f \u043f\u043e\u0433\u043e\u0434\u0430 \u0432 \u043c\u0441\u043a?", "ai_model_response": "import datetime, sys\ntry:\n import requests\n def fetch():\n r = requests.get('https://wttr.in/Moscow?format=%l:+%c+%t+%h+%w&lang=ru', timeout=10)\n r.encoding = 'utf-8'\n if r.status_code == 200 and r.text.strip():\n return r.text.strip()\n raise Exception(f'HTTP {r.status_code}')\nexcept Exception:\n from urllib import request, error\n def fetch():\n try:\n with request.urlopen('https://wttr.in/Moscow?format=%l:+%c+%t+%h+%w&lang=ru', timeout=10) as r:\n return r.read().decode('utf-8').strip()\n except error.HTTPError as e:\n raise Exception(f'HTTP {e.code}')\n except Exception:\n raise\n\nnow = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')\ntry:\n weather = fetch()\n print(f\"\u0412\u0440\u0435\u043c\u044f: {now}\")\n print(f\"\u0421\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0433\u043e\u0434\u0430 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435: {weather}\")\nexcept Exception:\n print(f\"\u0412\u0440\u0435\u043c\u044f: {now}\")\n print(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u043e\u0433\u043e\u0434\u0443 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u043e\u0437\u0436\u0435.\")", "stdout_result_code_execute": "\u0412\u0440\u0435\u043c\u044f: 2025-08-11 14:46:00\n\u0421\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u0433\u043e\u0434\u0430 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435: Moscow: \u26c5\ufe0f +15\u00b0C 94% \u21977km/h\n", "stderr_result_code_execute": ""}]}

history/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# history/__init__.py
2+
from .models import HistoryModel, HistoryList
3+
from .state import History
250 Bytes
Binary file not shown.
878 Bytes
Binary file not shown.
1.53 KB
Binary file not shown.

history/models.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import annotations
2+
3+
from datetime import datetime
4+
from typing import List, Optional
5+
6+
from pydantic import BaseModel
7+
8+
9+
class HistoryModel(BaseModel):
10+
datetime: datetime
11+
user_prompt: Optional[str] = None
12+
ai_model_response: Optional[str] = None
13+
stdout_result_code_execute: Optional[str] = None
14+
stderr_result_code_execute: Optional[str] = None
15+
16+
17+
class HistoryList(BaseModel):
18+
history: List[HistoryModel]

0 commit comments

Comments
 (0)