Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BitNet_Desktop_Start.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@echo off
setlocal
cd /d %~dp0

if not exist .venv (
py -m venv .venv
)

call .venv\Scripts\activate
python -m pip install --upgrade pip >nul
python -m pip install -e . >nul

start "" pythonw "%~dp0bitnet_desktop.pyw"
endlocal
84 changes: 64 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,52 @@

---

## 0) 이번 문서에서 바로 할 일
## 0) 현재 완성도 빠른 진단

현 시점 기준 기능 완성도(실사용 관점): **약 80%**

- 완료
- CSV 기초 요약(행/열/결측/숫자 통계)
- BitNet용 프롬프트 자동 생성
- CLI 실행 흐름
- 브라우저 UI(`bitnet-analyze ui`)
- **윈도우 데스크톱 UI(`bitnet-analyze desktop`, `BitNet_Desktop_Start.bat`)**
- 남은 과제
- 대용량 CSV(수십~수백 MB) 스트리밍 처리
- 시각화(차트) 및 리포트 내보내기
- 실행 전 환경진단(ollama 설치 여부 자동 점검)

### 파일 붙여넣기 분석 가능 범위

가능:
- Python 코드, 로그, 에러 메시지, 설정 파일(`.toml`, `.json`, `.yaml`), CSV 샘플
- 모듈 구조/의존성/리팩터링 포인트/버그 후보 분석
- 여러 파일을 순차로 붙여주면 아키텍처 단위 진단

제약:
- 실제 실행이 필요한 문제(환경/권한/OS 특이 이슈)는 붙여넣기만으로 100% 재현 불가
- 초대형 파일은 핵심 구간(에러 스택, 함수 단위) 분할 제공 권장

권장 붙여넣기 순서:
1. 에러 로그 전문
2. 관련 함수/클래스
3. 실행 명령어
4. `pyproject.toml` 또는 의존성 목록

---

## 1) 이번 문서에서 바로 할 일

1. Ollama 설치 및 실행
2. BitNet 모델 1개 Pull
3. CLI로 동작 확인
4. Open WebUI 연결
5. JupyterLab에서 CSV 분석 + BitNet 해석 워크플로우 구성
6. (Windows) 더블클릭으로 데스크톱 앱 실행

---

## 1) 사전 확인 (10~20분)
## 2) 사전 확인 (10~20분)

- OS 확인
- RAM/VRAM 확인
Expand All @@ -29,7 +64,7 @@

---

## 2) Step-by-step 시작 절차 (BitNet 우선)
## 3) Step-by-step 시작 절차 (BitNet 우선)

### Step 1. Ollama 설치
```bash
Expand Down Expand Up @@ -79,9 +114,22 @@ pip install jupyterlab pandas matplotlib
jupyter lab
```

### Step 6. Windows 원클릭 실행

터미널 없이 사용하려면 아래 중 하나를 사용하세요.

- 방법 A: 프로젝트 루트에서 `BitNet_Desktop_Start.bat` 더블클릭
- 방법 B: 설치 후 `bitnet-desktop` 실행
- 방법 C: `bitnet-analyze desktop` 실행

`BitNet_Desktop_Start.bat`는 다음을 자동 수행합니다.
- `.venv` 생성(없으면)
- 패키지 설치(`pip install -e .`)
- `pythonw`로 GUI 실행(콘솔창 없이)

---

## 3) BitNet 기본 설정값 (안정성 우선)
## 4) BitNet 기본 설정값 (안정성 우선)

- temperature: `0.2 ~ 0.5`
- top_p: `0.9`
Expand All @@ -95,12 +143,12 @@ jupyter lab

---

## 4) 데이터 분석 최소 워크플로우 (BitNet only)
## 5) 데이터 분석 최소 워크플로우 (BitNet only)

1. JupyterLab에서 CSV 로딩
2. pandas로 결측/타입/기초통계 계산
3. 계산 결과를 텍스트로 정리
4. 정리된 텍스트를 BitNet에 입력해 인사이트/한계/추가 데이터 제안 받기
1. CSV 로딩
2. 결측/타입/기초통계 계산
3. 계산 결과 기반 프롬프트 생성
4. BitNet 실행으로 인사이트/한계/추가 데이터 제안 받기

예시 프롬프트:

Expand All @@ -121,7 +169,7 @@ jupyter lab

---

## 5) 운영 안정화 체크리스트
## 6) 운영 안정화 체크리스트

- [ ] BitNet 모델 1~2개만 유지
- [ ] 프롬프트 템플릿은 검증된 것만 유지
Expand All @@ -135,7 +183,7 @@ jupyter lab

---

## 6) 지금 바로 실행할 최소 커맨드 모음
## 7) 지금 바로 실행할 최소 커맨드 모음

```bash
# 0) 프로젝트 설치
Expand All @@ -153,19 +201,16 @@ ollama pull <bitnet-model-tag>
# 3) CSV 분석 payload 생성
bitnet-analyze analyze sample.csv --question "샘플 매출 데이터를 요약해줘" --out payload.json

# 4) (선택) 웹 UI 실행
# 4) 웹 UI 실행
bitnet-analyze ui --host 127.0.0.1 --port 8765
```

필요하면 다음 단계에서 환경(OS/CPU/RAM/GPU)에 맞춰
- 정확한 BitNet 태그
- 권장 context/max_tokens
- Open WebUI 프리셋 프롬프트 3종
까지 바로 좁혀서 제안할 수 있습니다.
# 5) 데스크톱 UI 실행
bitnet-analyze desktop
```

---

## 7) GitHub 반영(적용) 절차
## 8) GitHub 반영(적용) 절차

로컬에서 문서/설정을 수정한 뒤 아래 순서로 GitHub에 반영합니다.

Expand All @@ -179,4 +224,3 @@ PR 생성 시 체크 포인트:
- 변경 목적(왜 바꿨는지) 1~2줄
- 실행/검증한 명령어
- 사용자 관점에서 달라진 점(BitNet 우선 흐름, 실행 순서 명확화 등)

5 changes: 5 additions & 0 deletions bitnet_desktop.pyw
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from bitnet_tools.desktop import launch_desktop


if __name__ == "__main__":
launch_desktop()
10 changes: 9 additions & 1 deletion bitnet_tools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ def _build_parser() -> argparse.ArgumentParser:
ui_parser.add_argument("--host", default="127.0.0.1", help="Bind host")
ui_parser.add_argument("--port", default=8765, type=int, help="Bind port")

subparsers.add_parser("desktop", help="Run Windows desktop UI")

return parser


def main(argv: list[str] | None = None) -> int:
raw_args = list(sys.argv[1:] if argv is None else argv)
if raw_args and raw_args[0] not in {"analyze", "ui", "-h", "--help"}:
if raw_args and raw_args[0] not in {"analyze", "ui", "desktop", "-h", "--help"}:
raw_args.insert(0, "analyze")

parser = _build_parser()
Expand All @@ -62,6 +64,12 @@ def main(argv: list[str] | None = None) -> int:
serve(host=args.host, port=args.port)
return 0

if args.command == "desktop":
from .desktop import launch_desktop

launch_desktop()
return 0

if args.command == "analyze":
payload = build_analysis_payload(args.csv, args.question)
args.out.write_text(
Expand Down
193 changes: 193 additions & 0 deletions bitnet_tools/desktop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
from __future__ import annotations

import json
import subprocess
import threading
import tkinter as tk
from pathlib import Path
from tkinter import filedialog, messagebox, ttk

from .analysis import build_analysis_payload


def run_ollama(model: str, prompt: str) -> str:
proc = subprocess.run(
["ollama", "run", model, prompt],
capture_output=True,
text=True,
check=False,
)
if proc.returncode != 0:
raise RuntimeError(proc.stderr.strip() or "ollama run failed")
return proc.stdout.strip()


class DesktopApp:
def __init__(self, root: tk.Tk) -> None:
self.root = root
self.root.title("BitNet CSV Analyzer (Windows)")
self.root.geometry("1100x760")

self.csv_path: Path | None = None
self.latest_prompt = ""

self._build_ui()

def _build_ui(self) -> None:
frame = ttk.Frame(self.root, padding=12)
frame.pack(fill="both", expand=True)

header = ttk.Label(
frame,
text="BitNet CSV Analyzer - 터미널 없이 바로 실행",
font=("Segoe UI", 14, "bold"),
)
header.pack(anchor="w")

sub = ttk.Label(
frame,
text="CSV 선택 → 분석 → BitNet 실행 순서로 사용하세요.",
)
sub.pack(anchor="w", pady=(0, 10))

top_row = ttk.Frame(frame)
top_row.pack(fill="x", pady=(0, 8))
ttk.Button(top_row, text="CSV 파일 열기", command=self._open_csv).pack(side="left")

self.csv_label = ttk.Label(top_row, text="선택된 파일 없음")
self.csv_label.pack(side="left", padx=12)

question_row = ttk.LabelFrame(frame, text="질문")
question_row.pack(fill="x", pady=(0, 8))

chip_row = ttk.Frame(question_row)
chip_row.pack(anchor="w", padx=8, pady=6)
presets = [
"핵심 인사이트 3개와 근거를 알려줘",
"이상치 의심 포인트와 추가 확인 항목을 알려줘",
"실행 가능한 다음 액션 5개를 우선순위로 제안해줘",
]
for txt in presets:
ttk.Button(chip_row, text=txt.split()[0], command=lambda t=txt: self._set_question(t)).pack(
side="left", padx=(0, 6)
)

self.question = tk.Text(question_row, height=3, wrap="word")
self.question.pack(fill="x", padx=8, pady=(0, 8))
self.question.insert("1.0", presets[0])

model_row = ttk.Frame(frame)
model_row.pack(fill="x", pady=(0, 8))

ttk.Label(model_row, text="BitNet 모델 태그").pack(side="left")
self.model = ttk.Entry(model_row)
self.model.insert(0, "bitnet:latest")
self.model.pack(side="left", fill="x", expand=True, padx=8)

ttk.Button(model_row, text="1) 분석", command=self._analyze_async).pack(side="left", padx=(8, 4))
ttk.Button(model_row, text="2) BitNet 실행", command=self._run_model_async).pack(side="left")

self.status = ttk.Label(frame, text="대기 중")
self.status.pack(anchor="w", pady=(0, 8))

output = ttk.Panedwindow(frame, orient="vertical")
output.pack(fill="both", expand=True)

self.summary = self._make_text_panel(output, "데이터 요약")
self.prompt = self._make_text_panel(output, "생성 프롬프트")
self.answer = self._make_text_panel(output, "BitNet 응답")

def _make_text_panel(self, parent: ttk.Panedwindow, title: str) -> tk.Text:
panel = ttk.LabelFrame(parent, text=title)
text = tk.Text(panel, wrap="word", height=10)
scrollbar = ttk.Scrollbar(panel, orient="vertical", command=text.yview)
text.configure(yscrollcommand=scrollbar.set)
text.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
parent.add(panel, weight=1)
return text

def _on_ui(self, func, *args) -> None:
self.root.after(0, lambda: func(*args))

def _set_question(self, text: str) -> None:
self.question.delete("1.0", "end")
self.question.insert("1.0", text)

def _open_csv(self) -> None:
path = filedialog.askopenfilename(
title="CSV 파일 선택",
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
)
if not path:
return
self.csv_path = Path(path)
self.csv_label.configure(text=str(self.csv_path))

def _get_question(self) -> str:
question = self.question.get("1.0", "end").strip()
Comment on lines +127 to +128
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Read Tk text widget on the UI thread

_analyze_async runs _analyze in a background threading.Thread, but _analyze immediately calls _get_question, which reads self.question.get(...) from that worker thread. Tkinter is not thread-safe, so this can intermittently raise TclError/hangs when users click "분석" (same pattern also appears in _run_model with self.model.get()). Capture widget values on the main thread (e.g., before starting the worker or via root.after/queue handoff) and only do non-UI work in the background thread.

Useful? React with 👍 / 👎.

return question or "이 데이터의 핵심 인사이트를 알려줘"

def _analyze_async(self) -> None:
threading.Thread(target=self._analyze, daemon=True).start()

def _analyze(self) -> None:
self._on_ui(self._set_status, "분석 중...")
try:
question = self._get_question()
if self.csv_path:
payload = build_analysis_payload(self.csv_path, question)
else:
self._on_ui(
messagebox.showinfo,
"파일 미선택",
"CSV를 선택하지 않아 본문 텍스트 입력을 안내합니다. 텍스트 박스에 CSV를 붙여넣으세요.",
)
return

self.latest_prompt = payload["prompt"]
self._on_ui(self._set_text, self.summary, json.dumps(payload["summary"], ensure_ascii=False, indent=2))
self._on_ui(self._set_text, self.prompt, self.latest_prompt)
self._on_ui(self._set_text, self.answer, "")
self._on_ui(self._set_status, "분석 완료")
except Exception as exc:
self._on_ui(self._set_status, f"오류: {exc}")

def _run_model_async(self) -> None:
threading.Thread(target=self._run_model, daemon=True).start()

def _run_model(self) -> None:
if not self.latest_prompt:
self._on_ui(self._set_text, self.answer, "먼저 분석을 실행해 프롬프트를 생성하세요.")
return

model = self.model.get().strip()
if not model:
self._on_ui(self._set_text, self.answer, "모델 태그를 입력하세요. 예: bitnet:latest")
return

self._on_ui(self._set_status, "BitNet 실행 중...")
try:
result = run_ollama(model, self.latest_prompt)
self._on_ui(self._set_text, self.answer, result)
self._on_ui(self._set_status, "BitNet 실행 완료")
except Exception as exc:
self._on_ui(self._set_text, self.answer, f"오류: {exc}")
self._on_ui(self._set_status, "BitNet 실행 실패")

def _set_text(self, widget: tk.Text, value: str) -> None:
widget.delete("1.0", "end")
widget.insert("1.0", value)

def _set_status(self, value: str) -> None:
self.status.configure(text=value)


def launch_desktop() -> None:
root = tk.Tk()
DesktopApp(root)
root.mainloop()


if __name__ == "__main__":
launch_desktop()
Loading