Skip to content

Commit a4754a1

Browse files
authored
Merge pull request #1 from a3ro-dev/feature/ide-ai-enhancements-4819846811561708000
feat: transform aAlem into an AI IDE note writer
2 parents dce3ab6 + 97ddab9 commit a4754a1

10 files changed

Lines changed: 773 additions & 1 deletion
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: Build and Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
tags:
8+
- 'v*'
9+
10+
jobs:
11+
build:
12+
name: Build on ${{ matrix.os }}
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
matrix:
16+
os: [ubuntu-latest, windows-latest, macos-latest]
17+
python-version: ["3.12"]
18+
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v4
22+
23+
- name: Install System Dependencies (Linux)
24+
if: matrix.os == 'ubuntu-latest'
25+
run: |
26+
sudo apt-get update
27+
sudo apt-get install -y libxcb-cursor0
28+
29+
- name: Set up Python ${{ matrix.python-version }}
30+
uses: actions/setup-python@v5
31+
with:
32+
python-version: ${{ matrix.python-version }}
33+
cache: 'pip'
34+
35+
- name: Install dependencies
36+
run: |
37+
python -m pip install --upgrade pip
38+
pip install -r requirements.txt
39+
pip install pyinstaller
40+
41+
- name: Build executable (Windows)
42+
if: matrix.os == 'windows-latest'
43+
run: |
44+
pyinstaller --noconfirm --onedir --windowed --add-data "alem.png;." --name "Alem" launch_enhanced.py
45+
46+
- name: Build executable (macOS/Linux)
47+
if: matrix.os != 'windows-latest'
48+
run: |
49+
pyinstaller --noconfirm --onedir --windowed --add-data "alem.png:." --name "Alem" launch_enhanced.py
50+
51+
- name: Upload artifact (Windows)
52+
if: matrix.os == 'windows-latest'
53+
uses: actions/upload-artifact@v4
54+
with:
55+
name: Alem-Windows
56+
path: dist/Alem
57+
58+
- name: Upload artifact (Linux)
59+
if: matrix.os == 'ubuntu-latest'
60+
uses: actions/upload-artifact@v4
61+
with:
62+
name: Alem-Linux
63+
path: dist/Alem
64+
65+
- name: Upload artifact (macOS)
66+
if: matrix.os == 'macos-latest'
67+
uses: actions/upload-artifact@v4
68+
with:
69+
name: Alem-macOS
70+
path: dist/Alem
71+
72+
release:
73+
name: Create Release
74+
needs: build
75+
runs-on: ubuntu-latest
76+
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
77+
78+
steps:
79+
- name: Checkout code
80+
uses: actions/checkout@v4
81+
82+
- name: Download Windows artifact
83+
uses: actions/download-artifact@v4
84+
with:
85+
name: Alem-Windows
86+
path: Alem-Windows
87+
88+
- name: Download Linux artifact
89+
uses: actions/download-artifact@v4
90+
with:
91+
name: Alem-Linux
92+
path: Alem-Linux
93+
94+
- name: Download macOS artifact
95+
uses: actions/download-artifact@v4
96+
with:
97+
name: Alem-macOS
98+
path: Alem-macOS
99+
100+
- name: Zip artifacts
101+
run: |
102+
cd Alem-Windows && zip -r ../Alem-Windows.zip . && cd ..
103+
cd Alem-Linux && zip -r ../Alem-Linux.zip . && cd ..
104+
cd Alem-macOS && zip -r ../Alem-macOS.zip . && cd ..
105+
106+
- name: Generate timestamp tag
107+
if: github.ref == 'refs/heads/main'
108+
id: tagger
109+
run: echo "tag=v$(date +'%Y.%m.%d-%H%M%S')" >> $GITHUB_OUTPUT
110+
111+
- name: Release
112+
uses: softprops/action-gh-release@v1
113+
with:
114+
tag_name: ${{ github.ref == 'refs/heads/main' && steps.tagger.outputs.tag || github.ref_name }}
115+
files: |
116+
Alem-Windows.zip
117+
Alem-Linux.zip
118+
Alem-macOS.zip
119+
env:
120+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

alem_app/core/llm_router.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import config
2+
from openai import OpenAI
3+
4+
class LLMRouter:
5+
@staticmethod
6+
def get_client(provider="groq"):
7+
app_config = config.config
8+
9+
if provider == "groq":
10+
api_key = app_config.get("groq_api_key", "")
11+
base_url = "https://api.groq.com/openai/v1"
12+
elif provider == "nvidia":
13+
api_key = app_config.get("nvidia_api_key", "")
14+
base_url = "https://integrate.api.nvidia.com/v1"
15+
elif provider == "glm":
16+
api_key = app_config.get("glm_api_key", "")
17+
base_url = "https://open.bigmodel.cn/api/paas/v4/"
18+
else:
19+
raise ValueError(f"Unknown provider: {provider}")
20+
21+
return OpenAI(api_key=api_key, base_url=base_url)
22+
23+
@staticmethod
24+
def complete(prompt, system, provider="groq", model=None, stream=False):
25+
client = LLMRouter.get_client(provider)
26+
27+
if model is None:
28+
if provider == "groq":
29+
model = "llama-3.1-8b-instant"
30+
elif provider == "nvidia":
31+
model = "meta/llama-3.3-70b-instruct"
32+
elif provider == "glm":
33+
model = "glm-4-flash"
34+
35+
messages = []
36+
if system:
37+
messages.append({"role": "system", "content": system})
38+
if prompt:
39+
messages.append({"role": "user", "content": prompt})
40+
41+
response = client.chat.completions.create(
42+
model=model,
43+
messages=messages,
44+
stream=stream,
45+
temperature=0.7,
46+
max_tokens=1024
47+
)
48+
return response

alem_app/core/suggestion_engine.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from PyQt6.QtCore import QObject, pyqtSignal, QThread
2+
from alem_app.core.llm_router import LLMRouter
3+
import config
4+
5+
class SuggestionWorker(QThread):
6+
suggestion_ready = pyqtSignal(str)
7+
8+
def __init__(self, context: str, full_note: str):
9+
super().__init__()
10+
self.context = context
11+
self.full_note = full_note
12+
self.is_cancelled = False
13+
14+
def run(self):
15+
try:
16+
app_config = config.config
17+
if not app_config.get("ai_suggestions_enabled", True):
18+
return
19+
20+
provider = "groq"
21+
if app_config.get("groq_api_key", ""):
22+
provider = "groq"
23+
elif app_config.get("nvidia_api_key", ""):
24+
provider = "nvidia"
25+
elif app_config.get("glm_api_key", ""):
26+
provider = "glm"
27+
else:
28+
return
29+
30+
system_prompt = "You are a writing assistant. Complete the user's thought naturally. Output ONLY the completion text, no explanation, no quotes. Maximum 1-2 sentences."
31+
user_prompt = f"Note context:\n{self.full_note}\n\nComplete this: {self.context}"
32+
33+
response = LLMRouter.complete(
34+
prompt=user_prompt,
35+
system=system_prompt,
36+
provider=provider,
37+
model=None, # Fallback to LLMRouter's default per provider
38+
stream=True
39+
)
40+
41+
completion_text = ""
42+
for chunk in response:
43+
if self.is_cancelled:
44+
return
45+
if chunk.choices and chunk.choices[0].delta.content:
46+
completion_text += chunk.choices[0].delta.content
47+
48+
if not self.is_cancelled and completion_text:
49+
self.suggestion_ready.emit(completion_text)
50+
51+
except Exception as e:
52+
from alem_app.utils.logging import logger
53+
logger.error(f"Error in SuggestionWorker: {e}")
54+
55+
56+
class SuggestionEngine(QObject):
57+
suggestion_ready = pyqtSignal(str)
58+
59+
def __init__(self, parent=None):
60+
super().__init__(parent)
61+
self._worker = None
62+
63+
def request_suggestion(self, context: str, full_note: str):
64+
self.cancel()
65+
66+
self._worker = SuggestionWorker(context, full_note)
67+
self._worker.suggestion_ready.connect(self.suggestion_ready.emit)
68+
self._worker.start()
69+
70+
def cancel(self):
71+
if self._worker and self._worker.isRunning():
72+
self._worker.is_cancelled = True
73+
self._worker.quit()
74+
self._worker.wait()
75+
self._worker = None

0 commit comments

Comments
 (0)