SOY NV AI commited on
Commit
208adf4
·
1 Parent(s): e071024

Update: Hugging Face 배포 설정 및 EXAONE 통합 개선

Browse files
.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
- # Hugging Face 토큰 (환경 변수에서 읽기)
10
- HF_TOKEN = os.environ.get("HUGGINGFACE_HUB_TOKEN") or os.environ.get("HF_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Hugging Face 토큰 (환경 변수에서 읽기)
10
- HF_TOKEN = os.environ.get("HUGGINGFACE_HUB_TOKEN") or os.environ.get("HF_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = os.environ.get("HUGGINGFACE_HUB_TOKEN") or os.environ.get("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
- # Hugging Face 토큰 (환경 변수에서 읽기)
12
- HF_TOKEN = os.environ.get("HUGGINGFACE_HUB_TOKEN") or os.environ.get("HF_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+