AngeloUNIMI's picture
Document Exam Trainer v5.0.3: written and oral answers
3035446 verified
Raw History Blame Contribute Delete
7.92 kB
"""Interactive installer runs INSIDE Docker; no host Python/venv required."""
from __future__ import annotations
import argparse
import getpass
from pathlib import Path
from .configuration import DATA, SECRETS, LocalConfig, DEFAULT_SPACE, load_config, save_config, repo_id
from .storage import LocalStore
def ask(text: str, default: str = '') -> str:
value=input(f'{text}' + (f' [{default}]' if default else '') + ': ').strip()
return value or default
def yes(text: str, default: bool = False) -> bool:
while True:
v=ask(text+' (y/n)','y' if default else 'n').lower()
if v in ('y','yes'): return True
if v in ('n','no'): return False
print('Enter y or n.')
def password() -> str:
while True:
p=getpass.getpass('Local password (12+ characters, hidden): ')
q=getpass.getpass('Repeat password: ')
if p==q and 12<=len(p)<=512:
return p
print('Passwords must match and contain 12-512 characters.')
def new_user(store, *, admin=False):
while True:
username=ask('Local username').lower()
try:
uid=store.create_user(username,password(),admin=admin)
print(f'Created local account: {username}')
return uid
except ValueError as exc:
print(exc)
def configure_token(store,uid):
u=store.by_id(uid)
print(f"HF settings for {u['username']} (not your local password).")
print('No token is needed for CPU mode. You may skip now and add your personal token in the app.')
token=getpass.getpass('Personal HF token (hidden; Enter to keep/skip): ').strip()
consent=yes('Allow sending source excerpts, question, rubric and transcript to the configured HF Space?',bool(u['remote_consent']))
store.preferences(uid,u['backend'],consent,token)
def manage(store,config):
# This CLI requires Docker/host access, which is already administrative.
# Still authenticate a local admin when account login is enabled.
if config.accounts:
name=ask('Local administrator username')
if not store.authenticate(name,getpass.getpass('Administrator password: ')):
raise SystemExit('Administrator sign-in failed.')
admin=store.user(name)
if not admin['admin']:
raise SystemExit('Administrator account required.')
else:
admin=store.user('local')
while True:
print('\nAccounts: '+', '.join(u['username']+(' (admin)' if u['admin'] else '') for u in store.list_users()))
op=ask('Action: add / password / token / delete / done','done')
if op=='done': return
if op=='add':
uid=new_user(store,admin=yes('Administrator account?'))
if yes('Configure this account HF token now?'): configure_token(store,uid)
continue
target=store.user(ask('Target username'))
if not target:
print('Account not found.');continue
try:
if op=='password':
store.reset_password(target['id'],password())
print('Password reset. Restart the app to invalidate existing sign-ins.')
elif op=='token': configure_token(store,target['id'])
elif op=='delete':
if yes('Permanently delete this account, saved courses and history?'):
store.delete_user(admin['id'],target['id'])
print('Account deleted. Restart the app to end its existing sessions.')
else: print('Unknown action.')
except ValueError as exc: print(exc)
def main():
parser=argparse.ArgumentParser()
parser.add_argument('--manage-users',action='store_true')
args=parser.parse_args()
store=LocalStore(DATA,SECRETS)
config=load_config() if (DATA/'settings.json').exists() else LocalConfig()
if args.manage_users:
return manage(store,config)
print('\nDOCUMENT EXAM TRAINER - LOCAL SETUP\n')
print('Data and encrypted tokens will be saved in separate named Docker volumes.')
print('The application port is restricted to localhost by default. No public access is configured.')
if (DATA/'settings.json').exists() and not yes('An installation exists. Change its settings?',False):
print('Keeping existing configuration and all saved data.');return
config.accounts=yes('Require local username/password login? (recommended for shared computers)',config.accounts)
if config.accounts:
admins=[u for u in store.list_users() if u['admin'] and u['enabled'] and u['username']!='local']
if not admins:
print('Create the first local administrator account.')
uid=new_user(store,admin=True)
else:
uid=admins[0]['id']
print('Keeping existing accounts and passwords.')
while yes('Add another local account?'):
new_user(store)
else:
print('Single-user mode has no login. Anyone who can use this computer/localhost port can access its local profile.')
u=store.user('local')
uid=u['id'] if u else store.create_user('local','',admin=True,single=True)
while True:
choice=ask('Default inference: 1 = remote HF Space, 2 = local CPU', '1' if config.default_backend=='remote_hf' else '2')
if choice in ('1','2'): break
config.default_backend='remote_hf' if choice=='1' else 'local_cpu'
while True:
try:
config.remote_space=repo_id(ask('Remote Space (used only in remote mode)',config.remote_space))
break
except ValueError as exc: print(exc)
config.allow_anonymous=yes('Allow remote requests without a personal HF token? Anonymous quotas are more restrictive.',config.allow_anonymous)
print('\nCPU models: 1 = compact Qwen3-1.7B (both tasks), 2 = Qwen3-4B (both tasks), 3 = custom public HF model IDs.')
print('CPU inference is slower and the compact model is less capable than the hosted 14B evaluator.')
print('Planning estimates: allow roughly 12-16 GB RAM for compact CPU mode, 24+ GB for 4B; actual usage varies. No quantization is enabled for the CPU LLM.')
choice=ask('CPU model preset','1')
if choice=='1': config.cpu_question_model=config.cpu_evaluation_model='Qwen/Qwen3-1.7B'
elif choice=='2': config.cpu_question_model=config.cpu_evaluation_model='Qwen/Qwen3-4B'
elif choice=='3':
config.cpu_question_model=repo_id(ask('CPU question model',config.cpu_question_model))
config.cpu_evaluation_model=repo_id(ask('CPU evaluation model',config.cpu_evaluation_model))
print('Custom checkpoints must work with Transformers/Qwen-style chat templates; not every model is compatible.')
else: raise ValueError('Unknown CPU model preset.')
config.cpu_threads=int(ask('CPU threads',str(config.cpu_threads)))
config.offline_models=yes('Use cached models only? Choose no for the first installation.',config.offline_models)
if yes('Configure the first account HF token now?',config.default_backend=='remote_hf'):
configure_token(store,uid)
if config.accounts:
others = [u for u in store.list_users() if u['id'] != uid and u['username'] != 'local' and u['enabled']]
for other in others:
if yes(f"Configure HF token/consent for {other['username']} now?"):
configure_token(store, other['id'])
save_config(config)
print('\nSetup saved. No passwords or HF tokens were written into the image or compose file.')
print('Open http://localhost:7860 after the container starts. Each user can change backend and HF token in Account & inference.')
print('Models download on first use. CPU-only mode sends no documents/answers to a remote inference service.')
if __name__=='__main__':
try: main()
except (EOFError,KeyboardInterrupt): raise SystemExit('\nSetup cancelled. Rerun the installer to finish.')
except ValueError as exc: raise SystemExit(str(exc))