SOY NV AI
commited on
Commit
·
208adf4
1
Parent(s):
e071024
Update: Hugging Face 배포 설정 및 EXAONE 통합 개선
Browse files- .github/workflows/README.md +1 -0
- .github/workflows/deploy-to-hf.yml +1 -0
- .github/workflows/test.yml +1 -0
- Dockerfile +1 -0
- EXAONE_설치_가이드.md +1 -0
- HF_UPLOAD_GUIDE.md +1 -0
- README_HF.md +1 -0
- add_exaone_model.py +1 -0
- add_exaone_with_token.py +33 -3
- app.py +1 -0
- app/huggingface_client.py +46 -0
- app/routes.py +65 -0
- download_exaone_model.py +32 -3
- install_exaone_direct.py +32 -3
- install_exaone_simple.py +33 -4
- migrate_add_is_public.py +1 -0
- remove_token_from_history.ps1 +33 -0
- templates/admin_settings.html +102 -0
- upload_to_hf.ps1 +1 -0
.github/workflows/README.md
CHANGED
|
@@ -81,3 +81,4 @@ GitHub 저장소의 Settings > Secrets and variables > Actions에서 다음 secr
|
|
| 81 |
- `.gitignore`에서 해당 파일이 제외되지 않았는지 확인
|
| 82 |
- 워크플로우의 `paths` 필터에 해당 파일이 포함되어 있는지 확인
|
| 83 |
|
|
|
|
|
|
| 81 |
- `.gitignore`에서 해당 파일이 제외되지 않았는지 확인
|
| 82 |
- 워크플로우의 `paths` 필터에 해당 파일이 포함되어 있는지 확인
|
| 83 |
|
| 84 |
+
|
.github/workflows/deploy-to-hf.yml
CHANGED
|
@@ -69,3 +69,4 @@ jobs:
|
|
| 69 |
echo "✅ Deployment completed successfully!"
|
| 70 |
echo "Space URL: https://huggingface.co/spaces/${{ secrets.HF_USERNAME }}/${{ secrets.HF_SPACE_NAME }}"
|
| 71 |
|
|
|
|
|
|
| 69 |
echo "✅ Deployment completed successfully!"
|
| 70 |
echo "Space URL: https://huggingface.co/spaces/${{ secrets.HF_USERNAME }}/${{ secrets.HF_SPACE_NAME }}"
|
| 71 |
|
| 72 |
+
|
.github/workflows/test.yml
CHANGED
|
@@ -57,3 +57,4 @@ jobs:
|
|
| 57 |
run: |
|
| 58 |
echo "✅ All tests passed!"
|
| 59 |
|
|
|
|
|
|
| 57 |
run: |
|
| 58 |
echo "✅ All tests passed!"
|
| 59 |
|
| 60 |
+
|
Dockerfile
CHANGED
|
@@ -35,3 +35,4 @@ ENV HOST=0.0.0.0
|
|
| 35 |
|
| 36 |
# 6. 애플리케이션 실행 (직접 실행 대신 start.sh를 통해 Ollama와 앱을 모두 실행)
|
| 37 |
CMD ["./start.sh"]
|
|
|
|
|
|
| 35 |
|
| 36 |
# 6. 애플리케이션 실행 (직접 실행 대신 start.sh를 통해 Ollama와 앱을 모두 실행)
|
| 37 |
CMD ["./start.sh"]
|
| 38 |
+
|
EXAONE_설치_가이드.md
CHANGED
|
@@ -168,5 +168,6 @@ tokenizer = AutoTokenizer.from_pretrained("LGAI-EXAONE/EXAONE-3.0-7.8B-Instruct"
|
|
| 168 |
|
| 169 |
|
| 170 |
|
|
|
|
| 171 |
|
| 172 |
|
|
|
|
| 168 |
|
| 169 |
|
| 170 |
|
| 171 |
+
|
| 172 |
|
| 173 |
|
HF_UPLOAD_GUIDE.md
CHANGED
|
@@ -142,3 +142,4 @@ Write-Host "파일 복사 완료!"
|
|
| 142 |
### 파일 누락
|
| 143 |
- 모든 필수 디렉토리(`app/`, `templates/`, `static/`)가 업로드되었는지 확인
|
| 144 |
|
|
|
|
|
|
| 142 |
### 파일 누락
|
| 143 |
- 모든 필수 디렉토리(`app/`, `templates/`, `static/`)가 업로드되었는지 확인
|
| 144 |
|
| 145 |
+
|
README_HF.md
CHANGED
|
@@ -54,3 +54,4 @@ Settings > Repository secrets에서 다음 환경 변수를 설정하세요:
|
|
| 54 |
|
| 55 |
MIT License
|
| 56 |
|
|
|
|
|
|
| 54 |
|
| 55 |
MIT License
|
| 56 |
|
| 57 |
+
|
add_exaone_model.py
CHANGED
|
@@ -163,5 +163,6 @@ if __name__ == "__main__":
|
|
| 163 |
|
| 164 |
|
| 165 |
|
|
|
|
| 166 |
|
| 167 |
|
|
|
|
| 163 |
|
| 164 |
|
| 165 |
|
| 166 |
+
|
| 167 |
|
| 168 |
|
add_exaone_with_token.py
CHANGED
|
@@ -5,11 +5,41 @@ EXAONE-3.0-7.8B-Instruct 모델을 Ollama에 추가하는 스크립트 (토큰
|
|
| 5 |
import os
|
| 6 |
import subprocess
|
| 7 |
from pathlib import Path
|
|
|
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
if not HF_TOKEN:
|
| 12 |
-
raise ValueError("HUGGINGFACE_HUB_TOKEN 또는 HF_TOKEN 환경 변수를 설정해주세요.")
|
| 13 |
|
| 14 |
def set_huggingface_token():
|
| 15 |
"""Hugging Face 토큰 설정"""
|
|
|
|
| 5 |
import os
|
| 6 |
import subprocess
|
| 7 |
from pathlib import Path
|
| 8 |
+
import sys
|
| 9 |
|
| 10 |
+
# 프로젝트 루트를 Python 경로에 추가
|
| 11 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 12 |
+
|
| 13 |
+
def get_huggingface_token():
|
| 14 |
+
"""
|
| 15 |
+
Hugging Face 토큰 가져오기 (환경 변수 우선, 없으면 DB에서)
|
| 16 |
+
Google API 키 관리 방식과 동일한 패턴
|
| 17 |
+
"""
|
| 18 |
+
# 환경 변수에서 먼저 확인
|
| 19 |
+
token = os.getenv('HUGGINGFACE_HUB_TOKEN', '').strip()
|
| 20 |
+
if not token:
|
| 21 |
+
token = os.getenv('HF_TOKEN', '').strip()
|
| 22 |
+
|
| 23 |
+
if token:
|
| 24 |
+
print(f"[Hugging Face] 환경 변수에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 25 |
+
return token
|
| 26 |
+
|
| 27 |
+
# DB에서 가져오기 (순환 참조 방지를 위해 여기서 임포트)
|
| 28 |
+
try:
|
| 29 |
+
from app.database import SystemConfig
|
| 30 |
+
# Flask 애플리케이션 컨텍스트 없이도 작동하도록 시도
|
| 31 |
+
token = SystemConfig.get_config('huggingface_token', '').strip()
|
| 32 |
+
if token:
|
| 33 |
+
print(f"[Hugging Face] DB에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 34 |
+
return token
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"[Hugging Face] DB에서 토큰 조회 실패 (환경 변수 사용): {e}")
|
| 37 |
+
return ''
|
| 38 |
+
|
| 39 |
+
# Hugging Face 토큰 가져오기
|
| 40 |
+
HF_TOKEN = get_huggingface_token()
|
| 41 |
if not HF_TOKEN:
|
| 42 |
+
raise ValueError("HUGGINGFACE_HUB_TOKEN 또는 HF_TOKEN 환경 변수를 설정하거나, 관리 페이지에서 토큰을 설정해주세요.")
|
| 43 |
|
| 44 |
def set_huggingface_token():
|
| 45 |
"""Hugging Face 토큰 설정"""
|
app.py
CHANGED
|
@@ -70,3 +70,4 @@ if __name__ == '__main__':
|
|
| 70 |
import traceback
|
| 71 |
traceback.print_exc()
|
| 72 |
|
|
|
|
|
|
| 70 |
import traceback
|
| 71 |
traceback.print_exc()
|
| 72 |
|
| 73 |
+
|
app/huggingface_client.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hugging Face 토큰 관리 모듈
|
| 3 |
+
Google Gemini API 키 관리 방식과 동일한 패턴으로 구현
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
def get_huggingface_token() -> str:
|
| 10 |
+
"""
|
| 11 |
+
Hugging Face 토큰 가져오기 (환경 변수 우선, 없으면 DB에서)
|
| 12 |
+
|
| 13 |
+
Returns:
|
| 14 |
+
Hugging Face 토큰 문자열 (없으면 빈 문자열)
|
| 15 |
+
"""
|
| 16 |
+
# 환경 변수에서 먼저 확인
|
| 17 |
+
token = os.getenv('HUGGINGFACE_HUB_TOKEN', '').strip()
|
| 18 |
+
if not token:
|
| 19 |
+
token = os.getenv('HF_TOKEN', '').strip()
|
| 20 |
+
|
| 21 |
+
if token:
|
| 22 |
+
print(f"[Hugging Face] 환경 변수에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 23 |
+
return token
|
| 24 |
+
|
| 25 |
+
# DB에서 가져오기 (순환 참조 방지를 위해 여기서 임포트)
|
| 26 |
+
try:
|
| 27 |
+
from app.database import SystemConfig
|
| 28 |
+
token = SystemConfig.get_config('huggingface_token', '').strip()
|
| 29 |
+
if token:
|
| 30 |
+
print(f"[Hugging Face] DB에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 31 |
+
else:
|
| 32 |
+
print(f"[Hugging Face] DB에 토큰이 없거나 비어있음")
|
| 33 |
+
return token
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"[Hugging Face] DB에서 토큰 조회 실패: {e}")
|
| 36 |
+
return ''
|
| 37 |
+
|
| 38 |
+
def reset_huggingface_token():
|
| 39 |
+
"""
|
| 40 |
+
Hugging Face 토큰 캐시 초기화 (토큰이 업데이트된 후 호출)
|
| 41 |
+
"""
|
| 42 |
+
# 현재는 단순히 함수를 다시 호출하면 되지만,
|
| 43 |
+
# 향후 캐싱이 추가될 경우를 대비한 함수
|
| 44 |
+
pass
|
| 45 |
+
|
| 46 |
+
|
app/routes.py
CHANGED
|
@@ -1983,6 +1983,26 @@ def get_gemini_api_key():
|
|
| 1983 |
traceback.print_exc()
|
| 1984 |
return jsonify({'error': f'API 키 조회 중 오류가 발생했습니다: {str(e)}'}), 500
|
| 1985 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1986 |
@main_bp.route('/api/admin/model-tokens', methods=['GET'])
|
| 1987 |
@admin_required
|
| 1988 |
def get_model_tokens():
|
|
@@ -2223,6 +2243,51 @@ def set_gemini_api_key():
|
|
| 2223 |
traceback.print_exc()
|
| 2224 |
return jsonify({'error': f'API 키 저장 중 오류가 발생했습니다: {str(e)}'}), 500
|
| 2225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2226 |
@main_bp.route('/api/ollama/models', methods=['GET'])
|
| 2227 |
@login_required
|
| 2228 |
def get_ollama_models():
|
|
|
|
| 1983 |
traceback.print_exc()
|
| 1984 |
return jsonify({'error': f'API 키 조회 중 오류가 발생했습니다: {str(e)}'}), 500
|
| 1985 |
|
| 1986 |
+
@main_bp.route('/api/admin/huggingface-token', methods=['GET'])
|
| 1987 |
+
@admin_required
|
| 1988 |
+
def get_huggingface_token():
|
| 1989 |
+
"""Hugging Face 토큰 조회"""
|
| 1990 |
+
try:
|
| 1991 |
+
from app.huggingface_client import get_huggingface_token
|
| 1992 |
+
token = get_huggingface_token()
|
| 1993 |
+
|
| 1994 |
+
# 보안을 위해 마스킹된 값 반환 (처음 8자만 표시)
|
| 1995 |
+
masked_token = token[:8] + '...' if token and len(token) > 8 else ''
|
| 1996 |
+
return jsonify({
|
| 1997 |
+
'has_token': bool(token),
|
| 1998 |
+
'masked_token': masked_token
|
| 1999 |
+
}), 200
|
| 2000 |
+
except Exception as e:
|
| 2001 |
+
print(f"[Hugging Face 토큰 조회] 오류: {e}")
|
| 2002 |
+
import traceback
|
| 2003 |
+
traceback.print_exc()
|
| 2004 |
+
return jsonify({'error': f'토큰 조회 중 오류가 발생했습니다: {str(e)}'}), 500
|
| 2005 |
+
|
| 2006 |
@main_bp.route('/api/admin/model-tokens', methods=['GET'])
|
| 2007 |
@admin_required
|
| 2008 |
def get_model_tokens():
|
|
|
|
| 2243 |
traceback.print_exc()
|
| 2244 |
return jsonify({'error': f'API 키 저장 중 오류가 발생했습니다: {str(e)}'}), 500
|
| 2245 |
|
| 2246 |
+
@main_bp.route('/api/admin/huggingface-token', methods=['POST'])
|
| 2247 |
+
@admin_required
|
| 2248 |
+
def set_huggingface_token():
|
| 2249 |
+
"""Hugging Face 토큰 저장/업데이트"""
|
| 2250 |
+
|
| 2251 |
+
try:
|
| 2252 |
+
if not request.is_json:
|
| 2253 |
+
return jsonify({'error': 'Content-Type이 application/json이 아닙니다.'}), 400
|
| 2254 |
+
|
| 2255 |
+
data = request.json
|
| 2256 |
+
if not data:
|
| 2257 |
+
return jsonify({'error': '요청 데이터가 없습니다.'}), 400
|
| 2258 |
+
|
| 2259 |
+
token = data.get('token', '').strip()
|
| 2260 |
+
|
| 2261 |
+
if not token:
|
| 2262 |
+
return jsonify({'error': '토큰을 입력해주세요.'}), 400
|
| 2263 |
+
|
| 2264 |
+
# 토큰 저장 (SystemConfig.set_config 내부에서 테이블 생성 처리)
|
| 2265 |
+
SystemConfig.set_config(
|
| 2266 |
+
key='huggingface_token',
|
| 2267 |
+
value=token,
|
| 2268 |
+
description='Hugging Face API 토큰'
|
| 2269 |
+
)
|
| 2270 |
+
|
| 2271 |
+
# Hugging Face 클라이언트에 토큰 재로드 알림
|
| 2272 |
+
try:
|
| 2273 |
+
from app.huggingface_client import reset_huggingface_token
|
| 2274 |
+
reset_huggingface_token()
|
| 2275 |
+
print(f"[Hugging Face] 토큰이 업데이트되어 클라이언트가 재로드되었습니다.")
|
| 2276 |
+
except Exception as e:
|
| 2277 |
+
print(f"[Hugging Face] 토큰 재로드 실패: {e}")
|
| 2278 |
+
|
| 2279 |
+
return jsonify({
|
| 2280 |
+
'message': 'Hugging Face 토큰이 성공적으로 저장되었습니다.',
|
| 2281 |
+
'has_token': True
|
| 2282 |
+
}), 200
|
| 2283 |
+
|
| 2284 |
+
except Exception as e:
|
| 2285 |
+
db.session.rollback()
|
| 2286 |
+
print(f"[Hugging Face 토큰 저장] 오류: {e}")
|
| 2287 |
+
import traceback
|
| 2288 |
+
traceback.print_exc()
|
| 2289 |
+
return jsonify({'error': f'토큰 저장 중 오류가 발생했습니다: {str(e)}'}), 500
|
| 2290 |
+
|
| 2291 |
@main_bp.route('/api/ollama/models', methods=['GET'])
|
| 2292 |
@login_required
|
| 2293 |
def get_ollama_models():
|
download_exaone_model.py
CHANGED
|
@@ -4,12 +4,41 @@ Ollama에서 사용할 수 있도록 준비하는 스크립트
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import os
|
|
|
|
| 7 |
from huggingface_hub import snapshot_download, login
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
if not HF_TOKEN:
|
| 12 |
-
raise ValueError("HUGGINGFACE_HUB_TOKEN 또는 HF_TOKEN 환경 변수를 설정해주세요.")
|
| 13 |
MODEL_NAME = "LGAI-EXAONE/EXAONE-3.0-7.8B-Instruct"
|
| 14 |
|
| 15 |
def download_model():
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import os
|
| 7 |
+
import sys
|
| 8 |
from huggingface_hub import snapshot_download, login
|
| 9 |
|
| 10 |
+
# 프로젝트 루트를 Python 경로에 추가
|
| 11 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 12 |
+
|
| 13 |
+
def get_huggingface_token():
|
| 14 |
+
"""
|
| 15 |
+
Hugging Face 토큰 가져오기 (환경 변수 우선, 없으면 DB에서)
|
| 16 |
+
Google API 키 관리 방식과 동일한 패턴
|
| 17 |
+
"""
|
| 18 |
+
# 환경 변수에서 먼저 확인
|
| 19 |
+
token = os.getenv('HUGGINGFACE_HUB_TOKEN', '').strip()
|
| 20 |
+
if not token:
|
| 21 |
+
token = os.getenv('HF_TOKEN', '').strip()
|
| 22 |
+
|
| 23 |
+
if token:
|
| 24 |
+
print(f"[Hugging Face] 환경 변수에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 25 |
+
return token
|
| 26 |
+
|
| 27 |
+
# DB에서 가져오기 (순환 참조 방지를 위해 여기서 임포트)
|
| 28 |
+
try:
|
| 29 |
+
from app.database import SystemConfig
|
| 30 |
+
token = SystemConfig.get_config('huggingface_token', '').strip()
|
| 31 |
+
if token:
|
| 32 |
+
print(f"[Hugging Face] DB에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 33 |
+
return token
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"[Hugging Face] DB에서 토큰 조회 실패 (환경 변수 사용): {e}")
|
| 36 |
+
return ''
|
| 37 |
+
|
| 38 |
+
# Hugging Face 토큰 가져오기
|
| 39 |
+
HF_TOKEN = get_huggingface_token()
|
| 40 |
if not HF_TOKEN:
|
| 41 |
+
raise ValueError("HUGGINGFACE_HUB_TOKEN 또는 HF_TOKEN 환경 변수를 설정하거나, 관리 페이지에서 토큰을 설정해주세요.")
|
| 42 |
MODEL_NAME = "LGAI-EXAONE/EXAONE-3.0-7.8B-Instruct"
|
| 43 |
|
| 44 |
def download_model():
|
install_exaone_direct.py
CHANGED
|
@@ -6,11 +6,40 @@ EXAONE-3.0-7.8B-Instruct를 Ollama에 직접 추가하는 스크립트
|
|
| 6 |
import os
|
| 7 |
import subprocess
|
| 8 |
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
-
# Hugging Face 토큰
|
| 11 |
-
HF_TOKEN =
|
| 12 |
if not HF_TOKEN:
|
| 13 |
-
raise ValueError("HUGGINGFACE_HUB_TOKEN 또는 HF_TOKEN 환경 변수를 설정해주세요.")
|
| 14 |
|
| 15 |
def create_simple_modelfile():
|
| 16 |
"""간단한 Modelfile 생성 (로컬 경로 대신 모델 이름만 사용)"""
|
|
|
|
| 6 |
import os
|
| 7 |
import subprocess
|
| 8 |
import json
|
| 9 |
+
import sys
|
| 10 |
+
|
| 11 |
+
# 프로젝트 루트를 Python 경로에 추가
|
| 12 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 13 |
+
|
| 14 |
+
def get_huggingface_token():
|
| 15 |
+
"""
|
| 16 |
+
Hugging Face 토큰 가져오기 (환경 변수 우선, 없으면 DB에서)
|
| 17 |
+
Google API 키 관리 방식과 동일한 패턴
|
| 18 |
+
"""
|
| 19 |
+
# 환경 변수에서 먼저 확인
|
| 20 |
+
token = os.getenv('HUGGINGFACE_HUB_TOKEN', '').strip()
|
| 21 |
+
if not token:
|
| 22 |
+
token = os.getenv('HF_TOKEN', '').strip()
|
| 23 |
+
|
| 24 |
+
if token:
|
| 25 |
+
print(f"[Hugging Face] 환경 변수에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 26 |
+
return token
|
| 27 |
+
|
| 28 |
+
# DB에서 가져오기 (순환 참조 방지를 위해 여기서 임포트)
|
| 29 |
+
try:
|
| 30 |
+
from app.database import SystemConfig
|
| 31 |
+
token = SystemConfig.get_config('huggingface_token', '').strip()
|
| 32 |
+
if token:
|
| 33 |
+
print(f"[Hugging Face] DB에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 34 |
+
return token
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"[Hugging Face] DB에서 토큰 조회 실패 (환경 변수 사용): {e}")
|
| 37 |
+
return ''
|
| 38 |
|
| 39 |
+
# Hugging Face 토큰 가져오기
|
| 40 |
+
HF_TOKEN = get_huggingface_token()
|
| 41 |
if not HF_TOKEN:
|
| 42 |
+
raise ValueError("HUGGINGFACE_HUB_TOKEN 또는 HF_TOKEN 환경 변수를 설정하거나, 관리 페이지에서 토큰을 설정해주세요.")
|
| 43 |
|
| 44 |
def create_simple_modelfile():
|
| 45 |
"""간단한 Modelfile 생성 (로컬 경로 대신 모델 이름만 사용)"""
|
install_exaone_simple.py
CHANGED
|
@@ -7,11 +7,40 @@ Ollama 0.13.0에서는 직접 Hugging Face 모델을 지원하지 않으므로
|
|
| 7 |
import os
|
| 8 |
import subprocess
|
| 9 |
from pathlib import Path
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
if not HF_TOKEN:
|
| 14 |
-
raise ValueError("HUGGINGFACE_HUB_TOKEN 또는 HF_TOKEN 환경 변수를 설정해주세요.")
|
| 15 |
|
| 16 |
def main():
|
| 17 |
print("\n" + "=" * 60)
|
|
|
|
| 7 |
import os
|
| 8 |
import subprocess
|
| 9 |
from pathlib import Path
|
| 10 |
+
import sys
|
| 11 |
+
|
| 12 |
+
# 프로젝트 루트를 Python 경로에 추가
|
| 13 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 14 |
+
|
| 15 |
+
def get_huggingface_token():
|
| 16 |
+
"""
|
| 17 |
+
Hugging Face 토큰 가져오기 (환경 변수 우선, 없으면 DB에서)
|
| 18 |
+
Google API 키 관리 방식과 동일한 패턴
|
| 19 |
+
"""
|
| 20 |
+
# 환경 변수에서 먼저 확인
|
| 21 |
+
token = os.getenv('HUGGINGFACE_HUB_TOKEN', '').strip()
|
| 22 |
+
if not token:
|
| 23 |
+
token = os.getenv('HF_TOKEN', '').strip()
|
| 24 |
+
|
| 25 |
+
if token:
|
| 26 |
+
print(f"[Hugging Face] 환경 변수에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 27 |
+
return token
|
| 28 |
+
|
| 29 |
+
# DB에서 가져오기 (순환 참조 방지를 위해 여기서 임포트)
|
| 30 |
+
try:
|
| 31 |
+
from app.database import SystemConfig
|
| 32 |
+
token = SystemConfig.get_config('huggingface_token', '').strip()
|
| 33 |
+
if token:
|
| 34 |
+
print(f"[Hugging Face] DB에서 토큰 가져옴 (길이: {len(token)}자)")
|
| 35 |
+
return token
|
| 36 |
+
except Exception as e:
|
| 37 |
+
print(f"[Hugging Face] DB에서 토큰 조회 실패 (환경 변수 사용): {e}")
|
| 38 |
+
return ''
|
| 39 |
+
|
| 40 |
+
# Hugging Face 토큰 가져오기
|
| 41 |
+
HF_TOKEN = get_huggingface_token()
|
| 42 |
if not HF_TOKEN:
|
| 43 |
+
raise ValueError("HUGGINGFACE_HUB_TOKEN 또는 HF_TOKEN 환경 변수를 설정하거나, 관리 페이지에서 토큰을 설정해주세요.")
|
| 44 |
|
| 45 |
def main():
|
| 46 |
print("\n" + "=" * 60)
|
migrate_add_is_public.py
CHANGED
|
@@ -59,3 +59,4 @@ if __name__ == '__main__':
|
|
| 59 |
print("=" * 60)
|
| 60 |
migrate_database()
|
| 61 |
|
|
|
|
|
|
| 59 |
print("=" * 60)
|
| 60 |
migrate_database()
|
| 61 |
|
| 62 |
+
|
remove_token_from_history.ps1
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Git 히스토리에서 Hugging Face 토큰 제거 스크립트
|
| 2 |
+
|
| 3 |
+
$token = "YOUR_HUGGINGFACE_TOKEN_HERE"
|
| 4 |
+
$files = @(
|
| 5 |
+
"EXAONE_추가_안내.md",
|
| 6 |
+
"add_exaone_with_token.py",
|
| 7 |
+
"download_exaone_model.py",
|
| 8 |
+
"install_exaone_direct.py",
|
| 9 |
+
"install_exaone_simple.py"
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
Write-Host "Git 히스토리에서 토큰 제거 중..."
|
| 13 |
+
|
| 14 |
+
# 각 파일에 대해 filter-branch 실행
|
| 15 |
+
foreach ($file in $files) {
|
| 16 |
+
Write-Host "처리 중: $file"
|
| 17 |
+
|
| 18 |
+
# 파일이 존재하는 커밋만 처리
|
| 19 |
+
$commits = git log --all --pretty=format:"%H" -- "$file" 2>$null
|
| 20 |
+
if ($commits) {
|
| 21 |
+
# PowerShell을 사용하여 파일 내용 수정
|
| 22 |
+
git filter-branch --force --tree-filter @"
|
| 23 |
+
if [ -f "$file" ]; then
|
| 24 |
+
sed -i 's/$token/YOUR_HUGGINGFACE_TOKEN_HERE/g' "$file" 2>/dev/null || \
|
| 25 |
+
(powershell -Command "(Get-Content '$file') -replace '$token', 'YOUR_HUGGINGFACE_TOKEN_HERE' | Set-Content '$file'")
|
| 26 |
+
fi
|
| 27 |
+
"@ --prune-empty --tag-name-filter cat -- --all 2>&1 | Out-Null
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
Write-Host "완료!"
|
| 32 |
+
|
| 33 |
+
|
templates/admin_settings.html
CHANGED
|
@@ -345,6 +345,27 @@
|
|
| 345 |
</div>
|
| 346 |
</div>
|
| 347 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
<!-- AI 모델별 토큰 수 관리 섹션 -->
|
| 349 |
<div class="card">
|
| 350 |
<div class="card-header">
|
|
@@ -467,6 +488,86 @@
|
|
| 467 |
}
|
| 468 |
}
|
| 469 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
// AI 모델별 토큰 수 관리 (입력/출력 분리)
|
| 471 |
async function loadModelTokens() {
|
| 472 |
const statusDiv = document.getElementById('modelTokensStatus');
|
|
@@ -747,6 +848,7 @@
|
|
| 747 |
// 페이지 로드 시 API 키 상태 확인 및 토큰 수 설정 로드
|
| 748 |
window.addEventListener('load', () => {
|
| 749 |
loadGeminiApiKey();
|
|
|
|
| 750 |
loadModelTokens();
|
| 751 |
});
|
| 752 |
</script>
|
|
|
|
| 345 |
</div>
|
| 346 |
</div>
|
| 347 |
|
| 348 |
+
<!-- Hugging Face 토큰 설정 섹션 -->
|
| 349 |
+
<div class="card">
|
| 350 |
+
<div class="card-header">
|
| 351 |
+
<div class="card-title">Hugging Face 토큰 설정</div>
|
| 352 |
+
</div>
|
| 353 |
+
<div style="padding: 16px 0;">
|
| 354 |
+
<div class="form-group">
|
| 355 |
+
<label for="huggingfaceToken">Hugging Face 토큰</label>
|
| 356 |
+
<div style="display: flex; gap: 8px;">
|
| 357 |
+
<input type="password" id="huggingfaceToken" placeholder="Hugging Face 토큰을 입력하세요" style="flex: 1; padding: 8px 12px; border: 1px solid #dadce0; border-radius: 4px; font-size: 14px;">
|
| 358 |
+
<button class="btn btn-primary" onclick="saveHuggingFaceToken()">저장</button>
|
| 359 |
+
<button class="btn btn-secondary" onclick="loadHuggingFaceToken()">현재 상태 확인</button>
|
| 360 |
+
</div>
|
| 361 |
+
<small style="color: #5f6368; font-size: 12px; display: block; margin-top: 4px;">
|
| 362 |
+
Hugging Face(<a href="https://huggingface.co/settings/tokens" target="_blank">https://huggingface.co/settings/tokens</a>)에서 토큰을 발급받을 수 있습니다.
|
| 363 |
+
</small>
|
| 364 |
+
<div id="huggingfaceTokenStatus" style="margin-top: 8px; font-size: 13px;"></div>
|
| 365 |
+
</div>
|
| 366 |
+
</div>
|
| 367 |
+
</div>
|
| 368 |
+
|
| 369 |
<!-- AI 모델별 토큰 수 관리 섹션 -->
|
| 370 |
<div class="card">
|
| 371 |
<div class="card-header">
|
|
|
|
| 488 |
}
|
| 489 |
}
|
| 490 |
|
| 491 |
+
// Hugging Face 토큰 관련 함수
|
| 492 |
+
async function loadHuggingFaceToken() {
|
| 493 |
+
try {
|
| 494 |
+
const response = await fetch('/api/admin/huggingface-token', {
|
| 495 |
+
credentials: 'include'
|
| 496 |
+
});
|
| 497 |
+
|
| 498 |
+
// 응답이 JSON인지 확인
|
| 499 |
+
const contentType = response.headers.get('content-type');
|
| 500 |
+
if (!contentType || !contentType.includes('application/json')) {
|
| 501 |
+
const text = await response.text();
|
| 502 |
+
console.error('Non-JSON response:', text.substring(0, 200));
|
| 503 |
+
const statusDiv = document.getElementById('huggingfaceTokenStatus');
|
| 504 |
+
statusDiv.innerHTML = `<span style="color: #ea4335;">서버 오류: 응답 형식이 올바르지 않습니다.</span>`;
|
| 505 |
+
return;
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
const data = await response.json();
|
| 509 |
+
|
| 510 |
+
const statusDiv = document.getElementById('huggingfaceTokenStatus');
|
| 511 |
+
if (!response.ok) {
|
| 512 |
+
statusDiv.innerHTML = `<span style="color: #ea4335;">오류: ${data.error || '알 수 없는 오류'}</span>`;
|
| 513 |
+
return;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
if (data.has_token) {
|
| 517 |
+
statusDiv.innerHTML = `<span style="color: #137333;">✓ 토큰이 설정되어 있습니다 (${data.masked_token})</span>`;
|
| 518 |
+
} else {
|
| 519 |
+
statusDiv.innerHTML = `<span style="color: #ea4335;">⚠ 토큰이 설정되지 않았습니다</span>`;
|
| 520 |
+
}
|
| 521 |
+
} catch (error) {
|
| 522 |
+
console.error('Hugging Face 토큰 로드 오류:', error);
|
| 523 |
+
const statusDiv = document.getElementById('huggingfaceTokenStatus');
|
| 524 |
+
statusDiv.innerHTML = `<span style="color: #ea4335;">오류: ${error.message}</span>`;
|
| 525 |
+
}
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
async function saveHuggingFaceToken() {
|
| 529 |
+
const tokenInput = document.getElementById('huggingfaceToken');
|
| 530 |
+
const token = tokenInput.value.trim();
|
| 531 |
+
|
| 532 |
+
if (!token) {
|
| 533 |
+
showAlert('토큰을 입력해주세요.', 'error');
|
| 534 |
+
return;
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
try {
|
| 538 |
+
const response = await fetch('/api/admin/huggingface-token', {
|
| 539 |
+
method: 'POST',
|
| 540 |
+
headers: {
|
| 541 |
+
'Content-Type': 'application/json',
|
| 542 |
+
},
|
| 543 |
+
credentials: 'include',
|
| 544 |
+
body: JSON.stringify({ token: token })
|
| 545 |
+
});
|
| 546 |
+
|
| 547 |
+
// 응답이 JSON인지 확인
|
| 548 |
+
const contentType = response.headers.get('content-type');
|
| 549 |
+
if (!contentType || !contentType.includes('application/json')) {
|
| 550 |
+
const text = await response.text();
|
| 551 |
+
console.error('Non-JSON response:', text.substring(0, 200));
|
| 552 |
+
showAlert('서버 오류: 응답 형식이 올바르지 않습니다.', 'error');
|
| 553 |
+
return;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
const data = await response.json();
|
| 557 |
+
|
| 558 |
+
if (response.ok) {
|
| 559 |
+
showAlert(data.message, 'success');
|
| 560 |
+
tokenInput.value = ''; // 보안을 위해 입력 필드 초기화
|
| 561 |
+
loadHuggingFaceToken(); // 상태 업데이트
|
| 562 |
+
} else {
|
| 563 |
+
showAlert(data.error || '토큰 저장 중 오류가 발생했습니다.', 'error');
|
| 564 |
+
}
|
| 565 |
+
} catch (error) {
|
| 566 |
+
console.error('Hugging Face 토큰 저장 오류:', error);
|
| 567 |
+
showAlert(`오류: ${error.message}`, 'error');
|
| 568 |
+
}
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
// AI 모델별 토큰 수 관리 (입력/출력 분리)
|
| 572 |
async function loadModelTokens() {
|
| 573 |
const statusDiv = document.getElementById('modelTokensStatus');
|
|
|
|
| 848 |
// 페이지 로드 시 API 키 상태 확인 및 토큰 수 설정 로드
|
| 849 |
window.addEventListener('load', () => {
|
| 850 |
loadGeminiApiKey();
|
| 851 |
+
loadHuggingFaceToken();
|
| 852 |
loadModelTokens();
|
| 853 |
});
|
| 854 |
</script>
|
upload_to_hf.ps1
CHANGED
|
@@ -112,3 +112,4 @@ try {
|
|
| 112 |
exit 1
|
| 113 |
}
|
| 114 |
|
|
|
|
|
|
| 112 |
exit 1
|
| 113 |
}
|
| 114 |
|
| 115 |
+
|