Compare commits
4 Commits
eb4d670ee0
...
6c905dc3ce
Author | SHA1 | Date | |
---|---|---|---|
6c905dc3ce | |||
e46d17eaf2 | |||
27794895c9 | |||
8f202262ee |
101
app.py
101
app.py
@ -15,10 +15,8 @@ from datetime import datetime, UTC
|
|||||||
import csv
|
import csv
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
app = FastAPI()
|
from starlette.types import ASGIApp
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
||||||
|
|
||||||
# Initialize GeoIP2 reader
|
# Initialize GeoIP2 reader
|
||||||
try:
|
try:
|
||||||
@ -42,8 +40,62 @@ def get_location_from_ip(ip):
|
|||||||
print(f"Error getting location for IP {ip}: {e}")
|
print(f"Error getting location for IP {ip}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
class LanguageMiddleware(BaseHTTPMiddleware):
|
||||||
|
def __init__(self, app: ASGIApp):
|
||||||
|
super().__init__(app)
|
||||||
|
self.default_language = "en" # Default language
|
||||||
|
self.supported_languages = ["zh", "en"] # Supported languages
|
||||||
|
|
||||||
|
async def dispatch(self, request: Request, call_next):
|
||||||
|
# Get language from query parameter
|
||||||
|
lang = request.query_params.get("lang")
|
||||||
|
|
||||||
|
# If no language in query params, try to get from Accept-Language header
|
||||||
|
if not lang:
|
||||||
|
accept_language = request.headers.get("accept-language", "")
|
||||||
|
if accept_language:
|
||||||
|
# Parse Accept-Language header and get the first language
|
||||||
|
lang = accept_language.split(",")[0].split(";")[0].strip()[:2]
|
||||||
|
|
||||||
|
# If still no language, try to detect from IP
|
||||||
|
if not lang and geoip_reader:
|
||||||
|
try:
|
||||||
|
ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() or \
|
||||||
|
request.headers.get("X-Real-IP", "") or \
|
||||||
|
request.client.host
|
||||||
|
response = geoip_reader.city(ip)
|
||||||
|
country_to_lang = { # Map country to language
|
||||||
|
"China": "zh",
|
||||||
|
"Hong Kong": "zh",
|
||||||
|
"Taiwan": "zh",
|
||||||
|
"Macau": "zh",
|
||||||
|
"United States": "en",
|
||||||
|
"United Kingdom": "en",
|
||||||
|
}
|
||||||
|
lang = country_to_lang.get(response.country.name, self.default_language)
|
||||||
|
except:
|
||||||
|
lang = self.default_language
|
||||||
|
|
||||||
|
# Ensure language is supported
|
||||||
|
if lang not in self.supported_languages:
|
||||||
|
lang = self.default_language
|
||||||
|
|
||||||
|
# Add language to request state
|
||||||
|
request.state.language = lang
|
||||||
|
|
||||||
|
# Continue processing the request
|
||||||
|
response = await call_next(request)
|
||||||
|
return response
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
app.add_middleware(LanguageMiddleware)
|
||||||
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
templates = {}
|
||||||
|
for lang in os.listdir("templates"):
|
||||||
|
templates[lang] = Jinja2Templates(directory="templates/"+lang)
|
||||||
|
|
||||||
# 加载所有问卷数据
|
# 加载所有问卷数据
|
||||||
def load_all_scales():
|
def load_all_scales(lang: str = None):
|
||||||
scales = {}
|
scales = {}
|
||||||
tags = []
|
tags = []
|
||||||
for root, dirs, files in os.walk(os.path.realpath('scales')):
|
for root, dirs, files in os.walk(os.path.realpath('scales')):
|
||||||
@ -52,39 +104,40 @@ def load_all_scales():
|
|||||||
try:
|
try:
|
||||||
with open(os.path.join(root, filename), 'r', encoding='utf-8') as f:
|
with open(os.path.join(root, filename), 'r', encoding='utf-8') as f:
|
||||||
scale = yaml.safe_load(f)
|
scale = yaml.safe_load(f)
|
||||||
scale['instructions']=markdown.markdown(scale['instructions'], extensions=['fenced_code','tables','mdx_math'])
|
if lang is None or (scale['lang'] and scale['lang'] == lang):
|
||||||
scale['descriptions']=markdown.markdown(scale['descriptions'], extensions=['fenced_code','tables','mdx_math'])
|
scale['instructions']=markdown.markdown(scale['instructions'], extensions=['fenced_code','tables','mdx_math'])
|
||||||
scale['abstract']=markdown.markdown(scale['abstract'], extensions=['fenced_code','tables','mdx_math'])
|
scale['descriptions']=markdown.markdown(scale['descriptions'], extensions=['fenced_code','tables','mdx_math'])
|
||||||
if 'tag' not in scale:
|
scale['abstract']=markdown.markdown(scale['abstract'], extensions=['fenced_code','tables','mdx_math'])
|
||||||
scale['tag']='其他'
|
if 'tag' not in scale:
|
||||||
if scale['tag'] not in tags:
|
scale['tag']='其他'
|
||||||
tags.append(scale['tag'])
|
if scale['tag'] not in tags:
|
||||||
scale_id = os.path.splitext(filename)[0] # 使用文件名作为标识
|
tags.append(scale['tag'])
|
||||||
scales[scale_id] = scale
|
scale_id = os.path.splitext(filename)[0] # 使用文件名作为标识
|
||||||
|
scales[scale_id] = scale
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading scale {filename}: {e}")
|
print(f"Error loading scale {filename}: {e}")
|
||||||
return tags, scales
|
return tags, scales
|
||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
async def index(request: Request):
|
async def index(request: Request):
|
||||||
tags, _ = load_all_scales()
|
tags, _ = load_all_scales(request.state.language)
|
||||||
# 新增读取README.md的逻辑
|
# 新增读取README.md的逻辑
|
||||||
readme_content = ""
|
readme_content = ""
|
||||||
try:
|
try:
|
||||||
with open("README.md", "r", encoding="utf-8") as f:
|
with open("templates/"+request.state.language+"/index.md", "r", encoding="utf-8") as f:
|
||||||
readme_content = markdown.markdown(f.read())
|
readme_content = markdown.markdown(f.read())
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass # 如果README不存在则静默失败
|
pass # 如果README不存在则静默失败
|
||||||
return templates.TemplateResponse("index.html", {
|
return templates[request.state.language].TemplateResponse("index.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"tags": tags,
|
"tags": tags,
|
||||||
"readme_content": readme_content # 新增模板变量
|
"readme_content": readme_content
|
||||||
})
|
})
|
||||||
|
|
||||||
@app.get("/tag/{tag}", response_class=HTMLResponse)
|
@app.get("/tag/{tag}", response_class=HTMLResponse)
|
||||||
async def list(request: Request, tag: str):
|
async def list(request: Request, tag: str):
|
||||||
tags, scales = load_all_scales()
|
tags, scales = load_all_scales(request.state.language)
|
||||||
return templates.TemplateResponse("list.html", {
|
return templates[request.state.language].TemplateResponse("list.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"tags": tags,
|
"tags": tags,
|
||||||
"scales": scales,
|
"scales": scales,
|
||||||
@ -93,10 +146,10 @@ async def list(request: Request, tag: str):
|
|||||||
|
|
||||||
@app.get("/scales/{scale_id}", response_class=HTMLResponse)
|
@app.get("/scales/{scale_id}", response_class=HTMLResponse)
|
||||||
async def scale(request: Request, scale_id: str):
|
async def scale(request: Request, scale_id: str):
|
||||||
tags, scales = load_all_scales()
|
tags, scales = load_all_scales(request.state.language)
|
||||||
scale = scales.get(scale_id)
|
scale = scales.get(scale_id)
|
||||||
if scale:
|
if scale:
|
||||||
return templates.TemplateResponse("scale.html", {
|
return templates[request.state.language].TemplateResponse("scale.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"scale_id": scale_id,
|
"scale_id": scale_id,
|
||||||
"scale": scale,
|
"scale": scale,
|
||||||
@ -107,7 +160,7 @@ async def scale(request: Request, scale_id: str):
|
|||||||
@app.post("/scales/{scale_id}", response_class=HTMLResponse)
|
@app.post("/scales/{scale_id}", response_class=HTMLResponse)
|
||||||
async def result(request: Request, scale_id: str, db: Session = Depends(get_db)):
|
async def result(request: Request, scale_id: str, db: Session = Depends(get_db)):
|
||||||
form_data = await request.form()
|
form_data = await request.form()
|
||||||
tags, scales = load_all_scales()
|
tags, scales = load_all_scales(request.state.language)
|
||||||
scale = scales.get(scale_id)
|
scale = scales.get(scale_id)
|
||||||
if scale:
|
if scale:
|
||||||
responses = {}
|
responses = {}
|
||||||
@ -144,7 +197,7 @@ async def result(request: Request, scale_id: str, db: Session = Depends(get_db))
|
|||||||
db.commit()
|
db.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return templates.TemplateResponse("result.html", {
|
return templates[request.state.language].TemplateResponse("result.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"responses": responses,
|
"responses": responses,
|
||||||
"average": average,
|
"average": average,
|
||||||
|
42
templates/en/base.html
Normal file
42
templates/en/base.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="PsychoScales心理学专业量表数据库,提供标准化心理测评工具,包含人格测试、症状评估等2000+临床心理学量表,支持在线测评及科研数据导出。">
|
||||||
|
<meta name="keywords" content="心尺, PsychoScales, 心理学量表,专业心理测评,抑郁症测试,焦虑症自评,在线心理测评系统,心理咨询师工具, 心理测验, 报告解读, 测试题">
|
||||||
|
<meta name="baidu-site-verification" content="codeva-mPOBUr0rLS" />
|
||||||
|
<link rel="stylesheet" href="/static/styles.css">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
|
{% block head_extra %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<div class="nav-left">
|
||||||
|
<a href="/" class="logo">PsychoScales.Org</a>
|
||||||
|
</div>
|
||||||
|
<div class="menu-toggle" onclick="document.querySelector('.nav-links').classList.toggle('active')">☰ </div>
|
||||||
|
<ul class="nav-links">
|
||||||
|
<li><a href="https://doc.psychoscales.com/"">Docs</a></li>
|
||||||
|
{% for tag in tags %}
|
||||||
|
<li><a href=" /tag/{{ tag }}"">{{ tag }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{% block main %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- 添加KaTeX支持 -->
|
||||||
|
<link rel=" stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body, {delimiters: [
|
||||||
|
{left: '$$', right: '$$', display: true},
|
||||||
|
{left: '$', right: '$', display: false}]});">
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
13
templates/en/index.html
Normal file
13
templates/en/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block head_extra %}
|
||||||
|
<title>PsychoScales.Org</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% if readme_content %}
|
||||||
|
<div class="readme-section">
|
||||||
|
{{ readme_content|safe }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
21
templates/en/index.md
Normal file
21
templates/en/index.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# PsychoScales.Org
|
||||||
|
|
||||||
|
## About Psychoscales.Org
|
||||||
|
|
||||||
|
This is an open source scale framework, built on Python.
|
||||||
|
The scale loading of this project is completely based on YAML, which strikes a balance between easy editing and easy loading.
|
||||||
|
|
||||||
|
[Open source address](https://git.mxr612.io/PsychoScales/PsychoScalesOrg)
|
||||||
|
|
||||||
|
Contact us: feedback@psychoscales.com
|
||||||
|
|
||||||
|
User community: QQ 513869177
|
||||||
|
|
||||||
|
## About PsychoScale
|
||||||
|
|
||||||
|
PsychoScale is a professional psychology resource platform that aims to provide users with comprehensive psychology measurement tools and assessment resources. We have brought together a variety of psychological measurement tools covering multiple fields such as emotions, personality, cognitive ability, and mental health to help users better understand their own psychological characteristics and conditions.
|
||||||
|
|
||||||
|
As a user of PsychoScale, you can quickly obtain detailed reports and analysis of your own psychological characteristics through simple online tests. These measurement tools are based on authoritative psychological theories and research, ensuring the accuracy and reliability of test results.
|
||||||
|
|
||||||
|
This website aims to provide free scale sharing and result calculation.
|
||||||
|
If you need analysis, you can get a comprehensive and in-depth analysis of AI results at [Psychoscales](https://www.psychoscales.com/).
|
16
templates/en/list.html
Normal file
16
templates/en/list.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block head_extra %}
|
||||||
|
<title>{{ tag }}</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<div class="scale-list">
|
||||||
|
{% for scale_id, scale in scales.items() %}
|
||||||
|
{% if scale.tag == tag %}
|
||||||
|
<a class="title" href="/scales/{{ scale_id }}">{{ scale.get('title','Untitled') }}</a>
|
||||||
|
<p>{{ scale.abstract|safe }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
17
templates/en/result.html
Normal file
17
templates/en/result.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block head_extra %}
|
||||||
|
<title>{{scale.title}}</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>{{ scale.title }} </h1>
|
||||||
|
<ul>
|
||||||
|
{% for key, value in responses.items() %}
|
||||||
|
<li>{{ key }}: Got {{ value }} in a {{options[key][0]}} to {{options[key][1]}} scale, with {{ average[key] }} in average</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<div>
|
||||||
|
{{scale.descriptions|safe}}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
24
templates/en/scale.html
Normal file
24
templates/en/scale.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block head_extra %}
|
||||||
|
<title>{{ scale.title }}</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>{{ scale.title }}</h1>
|
||||||
|
<div>
|
||||||
|
{{ scale.instructions|safe }}
|
||||||
|
</div>
|
||||||
|
<form class="scale" action="/scales/{{ scale_id }}" method="post">
|
||||||
|
{% for id, question in scale['items'].items() %}
|
||||||
|
<label for="{{ id }}">{{ id }}. {{ question }}</label>
|
||||||
|
<div class="scale-button">
|
||||||
|
{% for option, label in scale.options.items() %}
|
||||||
|
<input type="radio" id="{{ id }}_{{ option }}" name="{{ id }}" value="{{ option }}" required>
|
||||||
|
<label for="{{ id }}_{{ option }}" title="{{label}}">{{ option }}</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<!-- <html lang="zh"> -->
|
<html lang="zh">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
@ -1,13 +1,13 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<title>心尺.Org</title>
|
<title>心尺.Org</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% if readme_content %}
|
{% if readme_content %}
|
||||||
<div class="readme-section">
|
<div class="readme-section">
|
||||||
{{ readme_content|safe }}
|
{{ readme_content|safe }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
21
templates/zh/index.md
Normal file
21
templates/zh/index.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# 心尺.Org
|
||||||
|
|
||||||
|
## 关于心尺.Org
|
||||||
|
|
||||||
|
这个一个开源的量表框架,基于Python搭建。
|
||||||
|
本项目的量表加载完全基于YAML,在易于编辑和易于加载中间达到了平衡。
|
||||||
|
|
||||||
|
[开源地址](https://git.mxr612.io/PsychoScales/PsychoScalesOrg)
|
||||||
|
|
||||||
|
联系我们:feedback@psychoscales.com
|
||||||
|
|
||||||
|
用户社区:QQ 513869177
|
||||||
|
|
||||||
|
## 关于心尺
|
||||||
|
|
||||||
|
心尺是一个专业的心理学资源平台,旨在为用户提供全面的心理学测量工具和评估资源。我们汇集了各种心理测量工具,涵盖了情绪、人格、认知能力、心理健康等多个领域,帮助用户更好地了解自己的心理特点和状况。
|
||||||
|
|
||||||
|
作为心尺的用户,您可以通过简单的在线测试,快速获取关于自己心理特征的详细报告和分析。这些测量工具基于权威的心理学理论和研究,确保了测试结果的准确性和可靠性。
|
||||||
|
|
||||||
|
本网站旨在提供免费的量表分享和结果计算。
|
||||||
|
如果您需要解析,您可以在[心尺](https://www.psychoscales.com/)得到全面、深入的AI结果解析。
|
@ -1,17 +1,17 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<title>{{scale.title}}</title>
|
<title>{{scale.title}}</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h1>{{ scale.title }} </h1>
|
<h1>{{ scale.title }} </h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% for key, value in responses.items() %}
|
{% for key, value in responses.items() %}
|
||||||
<li>{{ key }}: 在 {{options[key][0]}} 到 {{options[key][1]}} 的量表中得分 {{ value }},均分 {{ average[key] }};</li>
|
<li>{{ key }}: 在 {{options[key][0]}} 到 {{options[key][1]}} 的量表中得分 {{ value }},均分 {{ average[key] }};</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
{{scale.descriptions|safe}}
|
{{scale.descriptions|safe}}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,24 +1,24 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<title>{{ scale.title }}</title>
|
<title>{{ scale.title }}</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h1>{{ scale.title }}</h1>
|
<h1>{{ scale.title }}</h1>
|
||||||
<div>
|
<div>
|
||||||
{{ scale.instructions|safe }}
|
{{ scale.instructions|safe }}
|
||||||
</div>
|
</div>
|
||||||
<form class="scale" action="/scales/{{ scale_id }}" method="post">
|
<form class="scale" action="/scales/{{ scale_id }}" method="post">
|
||||||
{% for id, question in scale['items'].items() %}
|
{% for id, question in scale['items'].items() %}
|
||||||
<label for="{{ id }}">{{ id }}. {{ question }}</label>
|
<label for="{{ id }}">{{ id }}. {{ question }}</label>
|
||||||
<div class="scale-button">
|
<div class="scale-button">
|
||||||
{% for option, label in scale.options.items() %}
|
{% for option, label in scale.options.items() %}
|
||||||
<input type="radio" id="{{ id }}_{{ option }}" name="{{ id }}" value="{{ option }}" required>
|
<input type="radio" id="{{ id }}_{{ option }}" name="{{ id }}" value="{{ option }}" required>
|
||||||
<label for="{{ id }}_{{ option }}" title="{{label}}">{{ option }}</label>
|
<label for="{{ id }}_{{ option }}" title="{{label}}">{{ option }}</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<input type="submit" value="提交">
|
<input type="submit" value="提交">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user