Skip to content

Commit 1fd379d

Browse files
committed
first commit
0 parents  commit 1fd379d

14 files changed

Lines changed: 512 additions & 0 deletions

File tree

.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

.gitignore

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[codz]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
# Usually these files are written by a python script from a template
31+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+
*.manifest
33+
*.spec
34+
35+
# Installer logs
36+
pip-log.txt
37+
pip-delete-this-directory.txt
38+
39+
# Unit test / coverage reports
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
.coverage
44+
.coverage.*
45+
.cache
46+
nosetests.xml
47+
coverage.xml
48+
*.cover
49+
*.py.cover
50+
.hypothesis/
51+
.pytest_cache/
52+
cover/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
.pybuilder/
76+
target/
77+
78+
# Jupyter Notebook
79+
.ipynb_checkpoints
80+
81+
# IPython
82+
profile_default/
83+
ipython_config.py
84+
85+
# pyenv
86+
# For a library or package, you might want to ignore these files since the code is
87+
# intended to run in multiple environments; otherwise, check them in:
88+
# .python-version
89+
90+
# pipenv
91+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
93+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
94+
# install all needed dependencies.
95+
#Pipfile.lock
96+
97+
# UV
98+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99+
# This is especially recommended for binary packages to ensure reproducibility, and is more
100+
# commonly ignored for libraries.
101+
#uv.lock
102+
103+
# poetry
104+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105+
# This is especially recommended for binary packages to ensure reproducibility, and is more
106+
# commonly ignored for libraries.
107+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108+
#poetry.lock
109+
#poetry.toml
110+
111+
# pdm
112+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115+
#pdm.lock
116+
#pdm.toml
117+
.pdm-python
118+
.pdm-build/
119+
120+
# pixi
121+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122+
#pixi.lock
123+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124+
# in the .venv directory. It is recommended not to include this directory in version control.
125+
.pixi
126+
127+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128+
__pypackages__/
129+
130+
# Celery stuff
131+
celerybeat-schedule
132+
celerybeat.pid
133+
134+
# SageMath parsed files
135+
*.sage.py
136+
137+
# Environments
138+
.env
139+
.envrc
140+
.venv
141+
env/
142+
venv/
143+
ENV/
144+
env.bak/
145+
venv.bak/
146+
147+
# Spyder project settings
148+
.spyderproject
149+
.spyproject
150+
151+
# Rope project settings
152+
.ropeproject
153+
154+
# mkdocs documentation
155+
/site
156+
157+
# mypy
158+
.mypy_cache/
159+
.dmypy.json
160+
dmypy.json
161+
162+
# Pyre type checker
163+
.pyre/
164+
165+
# pytype static type analyzer
166+
.pytype/
167+
168+
# Cython debug symbols
169+
cython_debug/
170+
171+
# PyCharm
172+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174+
# and can be added to the global gitignore or merged into this file. For a more nuclear
175+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
176+
#.idea/
177+
178+
# Abstra
179+
# Abstra is an AI-powered process automation framework.
180+
# Ignore directories containing user credentials, local state, and settings.
181+
# Learn more at https://abstra.io/docs
182+
.abstra/
183+
184+
# Visual Studio Code
185+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
188+
# you could uncomment the following to ignore the entire vscode folder
189+
# .vscode/
190+
191+
# Ruff stuff:
192+
.ruff_cache/
193+
194+
# PyPI configuration file
195+
.pypirc
196+
197+
# Marimo
198+
marimo/_static/
199+
marimo/_lsp/
200+
__marimo__/
201+
202+
# Streamlit
203+
.streamlit/secrets.toml

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

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)