본문 바로가기
PYTHON(파이썬)/TKINTER & KIVY

파이썬으로 라마에 질문하기

by eplus 2025. 5. 3.

# 기본 Kivy 및 Ollama 관련 모듈 import
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.clock import Clock
from kivy.core.clipboard import Clipboard
from kivy.config import Config
from kivy.core.window import Window
import threading
import ollama
import os

# ✅ 초기 윈도우 크기 설정 (시작 전 설정 필요)
Config.set('graphics', 'width', '800')
Config.set('graphics', 'height', '1024')

# ✅ 폰트 경로 설정
FONT_PATH = os.path.join(os.path.dirname(__file__), 'fonts', 'NotoSansKR-Regular.ttf')

# ✅ 출력 텍스트 정리 함수 (줄바꿈 허용, 이모지/비표준 제거)
def clean_text(text):
    return ''.join(c for c in text if c.isprintable() or c == '\n')

# ✅ 메인 레이아웃 클래스 정의
class ChatLayout(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(orientation='vertical', **kwargs)

        # ✅ 실행 중 윈도우 사이즈 설정 (보완용)
        Window.size = (800, 1024)

        # ✅ 질문 입력 필드
        self.input = TextInput(
            hint_text='질문을 입력하세요',
            size_hint=(1, 0.11),
            multiline=False,
            font_name=FONT_PATH
        )

        # ✅ 질문하기 버튼
        self.button = Button(
            text='질문하기',
            size_hint=(1, 0.1),
            font_name=FONT_PATH
        )
        self.button.bind(on_press=self.ask_model)

        # ✅ 복사 버튼
        self.copy_button = Button(
            text='답변 복사하기',
            size_hint=(1, 0.08),
            font_name=FONT_PATH
        )
        self.copy_button.bind(on_press=self.copy_answer)

        # ✅ 스크롤 가능한 답변 영역
        self.scroll = ScrollView(size_hint=(1, 0.68))
        self.output = Label(
            text='답변이 여기에 표시됩니다',
            font_name=FONT_PATH,
            size_hint_y=None,        # height를 직접 제어 가능하게 설정
            halign='left',
            valign='top',
            markup=True              # 줄바꿈 등 마크업 반영
        )
        self.output.bind(texture_size=self.update_height)
        self.scroll.add_widget(self.output)

        # ✅ 전체 UI 구성
        self.add_widget(self.input)
        self.add_widget(self.button)
        self.add_widget(self.copy_button)
        self.add_widget(self.scroll)

        # ✅ 텍스트 영역 크기 자동 조정 설정
        Clock.schedule_once(self.set_text_width, 0)
        self.scroll.bind(width=self.set_text_width)

    # ✅ 텍스트 너비 설정
    def set_text_width(self, *args):
        self.output.text_size = (self.scroll.width - 20, None)
        self.output.texture_update()

    # ✅ 텍스트 높이 자동 반영 (스크롤뷰 내에서 줄 수에 따라 높이 조절)
    def update_height(self, *args):
        self.output.height = self.output.texture_size[1]
        self.output.canvas.ask_update()

    # ✅ 질문 버튼 클릭 시 호출
    def ask_model(self, instance):
        question = self.input.text.strip()
        if question:
            self.output.text = '답변을 생성 중입니다...\n\n'
            App.get_running_app().root_window.set_title("질문 처리 중...")
            # ✅ 백그라운드 스레드로 처리 (UI 멈춤 방지)
            threading.Thread(target=self.query_ollama, args=(question,)).start()

    # ✅ Ollama API를 통한 응답 처리
    def query_ollama(self, question):
        try:
            response = ollama.generate(
                model='rabbit-ko',
                prompt=question + "\n\n자세히 설명해줘. 줄바꿈 포함해서 길게 써줘.",
                stream=False,
                options={'num_predict': 12000}  # ✅ 최대 토큰 수 설정 (응답 길이 제한 완화)
            )
            # 응답 텍스트 정리
            answer = clean_text(response['response'])

            # ✅ 메인 UI 스레드에서 Label 업데이트
            def update_ui(dt):
                self.output.text = answer
                App.get_running_app().root_window.set_title("Llama")

            Clock.schedule_once(update_ui, 0)

        except Exception as e:
            # ✅ 오류 발생 시 UI에 표시
            def show_error(dt):
                self.output.text = f"[오류 발생]\n{str(e)}"
                App.get_running_app().root_window.set_title("Llama")
            Clock.schedule_once(show_error, 0)

    # ✅ 복사 버튼 클릭 시 클립보드에 저장
    def copy_answer(self, instance):
        Clipboard.copy(self.output.text)
        self.copy_button.text = '복사 완료!'

# ✅ 앱 실행 클래스
class LlamaApp(App):
    def build(self):
        return ChatLayout()

# ✅ 앱 실행 시작
if __name__ == '__main__':
    LlamaApp().run()

728x90
반응형