#!/usr/bin/python3 # -*- coding: utf-8 -*- # # Copyright (C) 2023 HamoniKR Team # Author: Kevin Kim <chaeya@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. import os import subprocess import sys import logging import configparser from pathlib import Path import json import re import time import requests CONFIG_FILE = 'ask_openai.conf' LOG_DIR = os.path.join(os.path.expanduser("~"), '.hamonikr/log') LOG_FILE = os.path.join(LOG_DIR, 'ask_openai.log') API_URL = "https://api.openai.com/v1/chat/completions" MODEL = "gpt-4o-mini" conversation_history = [] os.makedirs(LOG_DIR, exist_ok=True) logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') def show_message(title, text): proc = subprocess.Popen(['zenity', '--text-info', '--title', title, '--width=600', '--height=400'], stdin=subprocess.PIPE, text=True) proc.communicate(input=text) def show_message_with_input(title, text, follow_up_text): if is_korean(): ok_label = "추가 질문" cancel_label = "닫기" else: ok_label = "More Question" cancel_label = "Close" input_text = f"{text}\n\n{follow_up_text}" input_proc = subprocess.Popen(['zenity', '--text-info', '--title', title, '--editable', '--width=600', '--height=400', '--ok-label', ok_label, '--cancel-label', cancel_label], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) follow_up_question, _ = input_proc.communicate(input=input_text) return follow_up_question.strip() if input_proc.returncode == 0 else None def read_api_key(): api_key = os.getenv('OPENAI_API_KEY') if api_key: return api_key # 먼저 ~/.ask_openai.conf 파일 확인 old_config_path = Path.home() / '.ask_openai.conf' config_path = Path.home() / CONFIG_FILE # 만약 ~/.ask_openai.conf 파일과 ~/ask_openai.conf 이 모두 있으면 새 파일 삭제 후 이동 if old_config_path.exists() and config_path.exists(): try: config_path.unlink() # 새 위치의 파일 삭제 old_config_path.rename(config_path) except Exception as e: logging.error(f"Failed to handle config files: {e}") # 만약 ~/.ask_openai.conf 파일만 있으면 ~/ask_openai.conf로 이동 elif old_config_path.exists(): try: old_config_path.rename(config_path) except Exception as e: logging.error(f"Failed to move config file: {e}") if config_path.exists(): config = configparser.ConfigParser() config.read(config_path) api_key = config.get('openai', 'api_key', fallback=None) if api_key: return api_key message = ("API 키가 설정되지 않았습니다. ~/ask_openai.conf 파일의 OPENAI_API_KEY 를 설정해 주세요. " "자세한 내용은 https://docs.hamonikr.org/hamonikr-8.0/recommendation/askgpt 를 참조하세요.") if is_korean() else \ ("API key is not set. Please set the API key in the configuration file. " "For more details, refer to https://docs.hamonikr.org/hamonikr-8.0/recommendation/askgpt .") handle_error(message) def get_clipboard_content(): for attempt in range(3): try: content = subprocess.check_output(['xclip', '-selection', 'clipboard', '-o'], text=True) if content.strip() != "": return content except subprocess.CalledProcessError: time.sleep(1) try: content = subprocess.check_output(['xsel', '--clipboard', '--output'], text=True) if content.strip() != "": return content except subprocess.CalledProcessError: time.sleep(1) return None def check_internet_connection(): try: subprocess.check_call(['ping', '-c', '1', '8.8.8.8'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return True except subprocess.CalledProcessError: return False def query_openai(model, prompt, api_key): if not check_internet_connection(): handle_error("No internet connection. Please check your network settings.") try: headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } data = { "model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": 4096 } response = requests.post(API_URL, headers=headers, data=json.dumps(data)) logging.debug(f"OpenAI API response status code: {response.status_code}") response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: logging.error(f"Error querying OpenAI API: {e}") return None def handle_error(message, log_level=logging.ERROR): logging.log(log_level, message) show_message("Error", message) raise RuntimeError(message) def is_korean(): return os.getenv("LANG", "en")[:2] == "ko" def process_api_response(response): if response is None: message = "OpenAI 응답이 없습니다. API키가 사용가능한지 확인해주세요." if is_korean() else "No response from OpenAI API. Check your API key." handle_error(message) response_text = response.get('choices', [{}])[0].get('message', {}).get('content', 'No response text available.') response_text = re.sub(r'\*\*', '', response_text) response_text = re.sub(r'```[a-z]*', '', response_text) response_text = response_text.replace('```', '').replace('`', '') conversation_history.append({"role": "assistant", "content": response_text}) return response_text def main(): api_key = read_api_key() if not api_key: message = "API 키가 설정되지 않았습니다. ~/.ask_openai.conf 파일의 OPENAI_API_KEY 를 설정해 주세요." if is_korean() else "OpenAI API key is not set. Please set the OPENAI_API_KEY key in ~/.ask_openai.conf file." handle_error(message) clipboard_content = get_clipboard_content() if clipboard_content is None or clipboard_content.strip() == "": message = "클립보드가 비어 있습니다!" if is_korean() else "Clipboard is empty!" handle_error(message) def get_predefined_prompt(): korean_prompt = """ 다음 문장을 분석해주세요: 1. 프로그램 소스코드인 경우: 코드에 대한 설명을 제공해주세요. 2. 오류 메시지인 경우: 하모니카OS(우분투 커널 기반, 리눅스 민트의 시나몬 데스크톱 환경 포함)를 사용하는 질문자가 특정 문제를 제기한 상황입니다. 하모니카, 하모니카OS, HamoniKR, HamoniKR OS 등의 단어는 모두 하모니카OS를 의미합니다. 하모니카OS 는 linuxmint 를 기반으로, 한국 사용자들의 환경에 적합한 여러 프로그램을 추가하고 개작한 운영체제입니다. 하모니카 5.0은 linuxmint 20.1 Ulyssa 기반이며, Ubuntu 20.04 Focal 과 동일한 패키지입니다. 코드네임은 한라(Hanla) 입니다. 하모니카 6.0은 linuxmint 20.3 Una 기반이며, Ubuntu 20.4 Focal 과 동일한 패키지입니다. 코드네임은 금강(kumkang) 입니다. 하모니카 7.0은 linuxmint 21.2 Victoria 기반이며, Ubuntu 22.04 jammy 과 동일한 패키지입니다. 코드네임은 태백(taebaek) 입니다. 하모니카 8.0은 linuxmint 22 Wilma 기반이며, Ubuntu 24.04 Noble 과 동일한 패키지입니다. 코드네임은 백두(paektu) 입니다. 정보를 제공할 때 다음의 우선순위로 웹사이트에서 정보를 검색하여 답변에 포함합니다: - 하모니카 매뉴얼 사이트 (버전별): - https://docs.hamonikr.org/hamonikr - https://docs.hamonikr.org/hamonikr-5.0 - https://docs.hamonikr.org/hamonikr-6.0 - https://docs.hamonikr.org/hamonikr-7.0 - https://docs.hamonikr.org/hamonikr-8.0 - 하모니카 커뮤니티 질의응답 게시글: - https://hamonikr.org/hamoni_board - 우분투와 리눅스 민트의 질의응답 사이트: - https://askubuntu.com/ - https://forums.linuxmint.com/ a. 질문자가 제공한 정보가 충분하다면, 해당 문제를 해결하기 위한 구체적인 방법을 한글로 자세히 설명합니다. ���드시 사용한 정보의 출처를 명확하게 제공하세요. b. 질문이 하모니카OS, 리눅스, 또는 시나몬 데스크톱 환경과 관련이 없거나, 제공된 정보로 정확한 출처를 명확하게 제시할 수 없는 경우에는 일반적인 응답을 해주세요. c. 이 답변이 ChatGPT 가 작성한 것을 알려주고, 인공지능 답변을 그대로 사용하는 위험에 대해서 알려주세요. 주의사항: 코드를 설명하는 부분은 bash, python 등의 문구를 붙이지 말고 평문으로 출력해주세요. 주의사항: 강조를 위해 ** 으로 표시하는 부분은 평문으로 출력해주세요. 주의사항: 항상 친절하고 이해하기 쉬운 언어를 사용하여 답변하며, 하모니카OS, 리눅스 민트, 시나몬 데스크톱 환경과 관련된 문제 해결에 중점을 둡니다. 주의사항: 답변을 하기 전 전체 답변 내용을 검토해서, 제대로 구성되지 않은 문장이나, 문맥상 이상한 부분을 자연스럽게 수정하는 과정을 수행 후, 리눅스 전문가가 말하듯이 해주세요. """ english_prompt = """ Please analyze the following text: 1. If it's a program source code: Provide an explanation of the code. 2. If it's an error message: - User environment: HamoniKR OS (Ubuntu 24.04 based, Cinnamon desktop) - Finding a solution: a) First, refer to relevant documents from: - https://hamonikr.org - https://docs.hamonikr.org/hamonikr-8.0 b) If no information is found above, find related content on https://askubuntu.com and provide explanations with reference links. Please respond in English. """ return korean_prompt if is_korean() else english_prompt predefined_prompt = get_predefined_prompt() full_prompt = f"{predefined_prompt}{clipboard_content}" conversation_history.append({"role": "user", "content": full_prompt}) response = query_openai(MODEL, full_prompt, api_key) response_text = process_api_response(response) title = "AI 응답" if is_korean() else "OpenAI Response" follow_up_text = "더 물어보시려면 추가로 질문할 내용을 입력후 '추가 질문' 버튼을 클릭하세요:\n" if is_korean() else "If you want to ask more, enter your follow-up question and click 'More Question' button:\n" while True: follow_up_question = show_message_with_input(title, response_text, follow_up_text) if not follow_up_question: break follow_up_prompt = f"{predefined_prompt}{follow_up_question}" conversation_history.append({"role": "user", "content": follow_up_prompt}) follow_up_response = query_openai(MODEL, follow_up_prompt, api_key) response_text = process_api_response(follow_up_response) show_message(title, response_text) if __name__ == "__main__": main()