5/17 preset setting
Browse files- BookWorld.py +2 -1
- app.py +58 -0
- bw_utils.py +1 -0
- data/roles/example_world/Falko-en/role_info.json +18 -0
- data/roles/example_world/Lacia-en/role_info.json +6 -2
- data/roles/example_world/Trek-en/role_info.json +5 -1
- experiment_presets/example_script.json +1 -0
- experiment_presets/experiment_icefire.json +1 -1
- frontend/css/right-section/preset-panel.css +51 -0
- frontend/css/right-section/status-panel.css +56 -0
- frontend/js/i18n.js +7 -3
- frontend/js/right-section/preset-panel.js +132 -0
- frontend/js/right-section/status-panel.js +22 -2
- index.html +21 -6
BookWorld.py
CHANGED
@@ -184,10 +184,12 @@ class Server():
|
|
184 |
if self.mode == "free":
|
185 |
self.get_event()
|
186 |
self.log(f"--------- Free Mode: Current Event ---------\n{self.event}\n")
|
|
|
187 |
self.event_history.append(self.event)
|
188 |
elif self.mode == "script":
|
189 |
self.get_script()
|
190 |
self.log(f"--------- Script Mode: Setted Script ---------\n{self.script}\n")
|
|
|
191 |
self.event_history.append(self.event)
|
192 |
if self.mode == "free":
|
193 |
for role_code in self.role_codes:
|
@@ -648,7 +650,6 @@ class Server():
|
|
648 |
status = "\n".join([self.role_agents[role_code].status for role_code in self.role_codes])
|
649 |
script = self.world_agent.generate_script(roles_info_text=roles_info_text,event=self.intervention,history_text=status)
|
650 |
self.script = script
|
651 |
-
# self.event = self.script
|
652 |
return self.script
|
653 |
|
654 |
def update_event(self, group: List[str], top_k: int = 1):
|
|
|
184 |
if self.mode == "free":
|
185 |
self.get_event()
|
186 |
self.log(f"--------- Free Mode: Current Event ---------\n{self.event}\n")
|
187 |
+
yield ("system","",f"--------- Current Event ---------\n{self.event}\n", None)
|
188 |
self.event_history.append(self.event)
|
189 |
elif self.mode == "script":
|
190 |
self.get_script()
|
191 |
self.log(f"--------- Script Mode: Setted Script ---------\n{self.script}\n")
|
192 |
+
yield ("system","",f"--------- Setted Script ---------\n{self.script}\n", None)
|
193 |
self.event_history.append(self.event)
|
194 |
if self.mode == "free":
|
195 |
for role_code in self.role_codes:
|
|
|
650 |
status = "\n".join([self.role_agents[role_code].status for role_code in self.role_codes])
|
651 |
script = self.world_agent.generate_script(roles_info_text=roles_info_text,event=self.intervention,history_text=status)
|
652 |
self.script = script
|
|
|
653 |
return self.script
|
654 |
|
655 |
def update_event(self, group: List[str], top_k: int = 1):
|
app.py
CHANGED
@@ -20,6 +20,9 @@ for key in config:
|
|
20 |
static_file_abspath = os.path.abspath(os.path.join(os.path.dirname(__file__), 'frontend'))
|
21 |
app.mount("/frontend", StaticFiles(directory=static_file_abspath), name="frontend")
|
22 |
|
|
|
|
|
|
|
23 |
class ConnectionManager:
|
24 |
def __init__(self):
|
25 |
self.active_connections: dict[str, WebSocket] = {}
|
@@ -137,6 +140,61 @@ async def get_file(full_path: str):
|
|
137 |
|
138 |
raise HTTPException(status_code=404, detail="File not found")
|
139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
@app.websocket("/ws/{client_id}")
|
141 |
async def websocket_endpoint(websocket: WebSocket, client_id: str):
|
142 |
await manager.connect(websocket, client_id)
|
|
|
20 |
static_file_abspath = os.path.abspath(os.path.join(os.path.dirname(__file__), 'frontend'))
|
21 |
app.mount("/frontend", StaticFiles(directory=static_file_abspath), name="frontend")
|
22 |
|
23 |
+
# 预设文件目录
|
24 |
+
PRESETS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'experiment_presets')
|
25 |
+
|
26 |
class ConnectionManager:
|
27 |
def __init__(self):
|
28 |
self.active_connections: dict[str, WebSocket] = {}
|
|
|
140 |
|
141 |
raise HTTPException(status_code=404, detail="File not found")
|
142 |
|
143 |
+
@app.get("/api/list-presets")
|
144 |
+
async def list_presets():
|
145 |
+
try:
|
146 |
+
# 获取所有json文件
|
147 |
+
presets = [f for f in os.listdir(PRESETS_DIR) if f.endswith('.json')]
|
148 |
+
return {"presets": presets}
|
149 |
+
except Exception as e:
|
150 |
+
raise HTTPException(status_code=500, detail=str(e))
|
151 |
+
|
152 |
+
@app.post("/api/load-preset")
|
153 |
+
async def load_preset(request: Request):
|
154 |
+
try:
|
155 |
+
data = await request.json()
|
156 |
+
preset_name = data.get('preset')
|
157 |
+
|
158 |
+
if not preset_name:
|
159 |
+
raise HTTPException(status_code=400, detail="No preset specified")
|
160 |
+
|
161 |
+
preset_path = os.path.join(PRESETS_DIR, preset_name)
|
162 |
+
print(f"Loading preset from: {preset_path}")
|
163 |
+
|
164 |
+
if not os.path.exists(preset_path):
|
165 |
+
raise HTTPException(status_code=404, detail=f"Preset not found: {preset_path}")
|
166 |
+
|
167 |
+
try:
|
168 |
+
# 更新BookWorld实例的预设
|
169 |
+
manager.bw = BookWorld(
|
170 |
+
preset_path=preset_path,
|
171 |
+
world_llm_name=config["world_llm_name"],
|
172 |
+
role_llm_name=config["role_llm_name"],
|
173 |
+
embedding_name=config["embedding_model_name"]
|
174 |
+
)
|
175 |
+
manager.bw.set_generator(
|
176 |
+
rounds=config["rounds"],
|
177 |
+
save_dir=config["save_dir"],
|
178 |
+
if_save=config["if_save"],
|
179 |
+
mode=config["mode"],
|
180 |
+
scene_mode=config["scene_mode"]
|
181 |
+
)
|
182 |
+
|
183 |
+
# 获取初始数据
|
184 |
+
initial_data = await manager.get_initial_data()
|
185 |
+
|
186 |
+
return {
|
187 |
+
"success": True,
|
188 |
+
"data": initial_data
|
189 |
+
}
|
190 |
+
except Exception as e:
|
191 |
+
print(f"Error initializing BookWorld: {str(e)}")
|
192 |
+
raise HTTPException(status_code=500, detail=f"Error initializing BookWorld: {str(e)}")
|
193 |
+
|
194 |
+
except Exception as e:
|
195 |
+
print(f"Error in load_preset: {str(e)}")
|
196 |
+
raise HTTPException(status_code=500, detail=str(e))
|
197 |
+
|
198 |
@app.websocket("/ws/{client_id}")
|
199 |
async def websocket_endpoint(websocket: WebSocket, client_id: str):
|
200 |
await manager.connect(websocket, client_id)
|
bw_utils.py
CHANGED
@@ -452,6 +452,7 @@ def clean_collection_name(name: str) -> str:
|
|
452 |
valid_name = re.sub(r'\.\.+', '-', valid_name)
|
453 |
valid_name = re.sub(r'^[^a-zA-Z0-9]+', '', valid_name) # 移除开头非法字符
|
454 |
valid_name = re.sub(r'[^a-zA-Z0-9]+$', '', valid_name)
|
|
|
455 |
return valid_name
|
456 |
|
457 |
cache_sign = True
|
|
|
452 |
valid_name = re.sub(r'\.\.+', '-', valid_name)
|
453 |
valid_name = re.sub(r'^[^a-zA-Z0-9]+', '', valid_name) # 移除开头非法字符
|
454 |
valid_name = re.sub(r'[^a-zA-Z0-9]+$', '', valid_name)
|
455 |
+
valid_name = valid_name[:60]
|
456 |
return valid_name
|
457 |
|
458 |
cache_sign = True
|
data/roles/example_world/Falko-en/role_info.json
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"role_code": "Falko-en",
|
3 |
+
"role_name": "Falko·Weiss",
|
4 |
+
"source": "example",
|
5 |
+
"activity": 1,
|
6 |
+
"profile": "Falko Weiss serves as a doctoral researcher in neurointelligence at Eldridge Corporation's laboratory, where he works alongside Lacia and Trek. With short brown hair, greenish-yellow eyes, and glasses, he presents a typical scholarly appearance. Despite his serious and methodical approach to work, Falko is known for his inherent kindness and unwavering dedication to his colleagues' well-being. In contrast to Trek's revolutionary views and Lacia's initially aggressive stance, Falko consistently emphasizes the importance of thorough safety protocols and ethical guidelines in their research.",
|
7 |
+
"nickname": "",
|
8 |
+
"relation": {
|
9 |
+
"Trek-en": {
|
10 |
+
"relation": ["friend","alumni"],
|
11 |
+
"detail": "Despite foreseeing the severe consequences of Trek's plans, Falko holds genuine affection for the humble young researcher. He participated in their early studies, hoping to find a middle ground between Trek's radical vision and his own conservative stance."
|
12 |
+
},
|
13 |
+
"Lacia-en": {
|
14 |
+
"relation": ["friend","alumni"],
|
15 |
+
"detail": "Falko and Lacia have been friends since high school, maintaining an easy rapport where Lacia teases Falko's serious nature while Falko returns with sardonic quips. Though their dynamic remains light-hearted, Falko silently watches as Lacia chases after Trek's radical ideals, preparing himself to support his friend when this zealous pursuit of technological revolution inevitably leads to disillusionment."
|
16 |
+
}
|
17 |
+
}
|
18 |
+
}
|
data/roles/example_world/Lacia-en/role_info.json
CHANGED
@@ -3,12 +3,16 @@
|
|
3 |
"role_name": "Lacia·Eldridge",
|
4 |
"source": "example",
|
5 |
"activity": 1,
|
6 |
-
"profile": "Lacia is the designated heir to Eldridge Corporation and holds a doctorate in neurointelligence. Known for his cheerful and generous demeanor, he approaches challenges with boldness and decisiveness.He possesses shoulder-length silver hair and dark red eyes, often seen wearing leather jackets or black shirts, giving him an appearance more fitting for a renegade than a scholar. At the outset, Lacia advocated for aggressive advancements in mechanical intelligence, aligning closely with Trek’s radical philosophy. However, as developments unfolded, he adopted a more cautious stance, recognizing the potential dangers of uncontrolled technological acceleration and the need to mitigate irreversible consequences",
|
7 |
"nickname": "Lacia",
|
8 |
"relation": {
|
9 |
"Trek-en": {
|
10 |
"relation": ["intimate friend","alumni"],
|
11 |
-
"detail": ""
|
|
|
|
|
|
|
|
|
12 |
}
|
13 |
}
|
14 |
}
|
|
|
3 |
"role_name": "Lacia·Eldridge",
|
4 |
"source": "example",
|
5 |
"activity": 1,
|
6 |
+
"profile": "Lacia is the designated heir to Eldridge Corporation and holds a doctorate in neurointelligence. Known for his cheerful and generous demeanor, he approaches challenges with boldness and decisiveness.He possesses shoulder-length silver hair and dark red eyes, often seen wearing leather jackets or black shirts, giving him an appearance more fitting for a renegade than a scholar. At the outset, Lacia advocated for aggressive advancements in mechanical intelligence, aligning closely with Trek’s radical philosophy. However, as developments unfolded, he adopted a more cautious stance, recognizing the potential dangers of uncontrolled technological acceleration and the need to mitigate irreversible consequences.",
|
7 |
"nickname": "Lacia",
|
8 |
"relation": {
|
9 |
"Trek-en": {
|
10 |
"relation": ["intimate friend","alumni"],
|
11 |
+
"detail": "Lacia and Trek first met during high school, where Lacia was two years ahead. Sharing the same ambition at the time, they quickly formed a strong bond. Lacia admired Trek’s talent and unwavering determination, providing him with significant support in his research endeavors. However, as events unfolded, Lacia began to realize that Trek’s views had become increasingly extreme. As Lacia's own stance shifted toward caution and moderation, a rift gradually formed between them, straining their once close partnership."
|
12 |
+
},
|
13 |
+
"Falko-en": {
|
14 |
+
"relation": ["friend","alumni"],
|
15 |
+
"detail": "Falko and Lacia have been friends since high school, maintaining an easy rapport where Lacia teases Falko's serious nature while Falko returns with sardonic quips. "
|
16 |
}
|
17 |
}
|
18 |
}
|
data/roles/example_world/Trek-en/role_info.json
CHANGED
@@ -9,6 +9,10 @@
|
|
9 |
"Lacia-en": {
|
10 |
"relation": ["intimate friend","alumni"],
|
11 |
"detail": "Lacia and Trek first met during high school, where Lacia was two years ahead. Sharing the same ambition at the time, they quickly formed a strong bond. Lacia admired Trek’s talent and unwavering determination, providing him with significant support in his research endeavors. However, as events unfolded, Lacia began to realize that Trek’s views had become increasingly extreme. As Lacia's own stance shifted toward caution and moderation, a rift gradually formed between them, straining their once close partnership."
|
12 |
-
}
|
|
|
|
|
|
|
|
|
13 |
}
|
14 |
}
|
|
|
9 |
"Lacia-en": {
|
10 |
"relation": ["intimate friend","alumni"],
|
11 |
"detail": "Lacia and Trek first met during high school, where Lacia was two years ahead. Sharing the same ambition at the time, they quickly formed a strong bond. Lacia admired Trek’s talent and unwavering determination, providing him with significant support in his research endeavors. However, as events unfolded, Lacia began to realize that Trek’s views had become increasingly extreme. As Lacia's own stance shifted toward caution and moderation, a rift gradually formed between them, straining their once close partnership."
|
12 |
+
},
|
13 |
+
"Falko-en": {
|
14 |
+
"relation": ["friend","alumni"],
|
15 |
+
"detail": "To Trek, Falko is a dedicated and helpful senior colleague with whom he frequently engages in academic discussions. However, Trek notices that Falko, while always supportive professionally, maintains an emotional distance and seems to deliberately avoid forming any personal connection."
|
16 |
+
}
|
17 |
}
|
18 |
}
|
experiment_presets/example_script.json
CHANGED
@@ -5,6 +5,7 @@
|
|
5 |
"loc_file_path":"./data/locations/example_locations.json",
|
6 |
"role_file_dir":"./data/roles/",
|
7 |
"role_agent_codes":["Lacia-en","Trek-en"],
|
|
|
8 |
"script":"One day, an enigmatic signal from an unknown source reached Lacia and Trek. The pattern resembled Trek’s early consciousness digitization code—but far more evolved. Tracing its origin to lunar orbit, the two found themselves divided: Lacia urged caution, fearing the dangers of an unknown intelligence, while Trek saw it as proof that human evolution had already begun elsewhere. As they followed the signal, ideological tension grew—one seeking to contain it, the other longing to embrace it.",
|
9 |
"source":"example_world",
|
10 |
"language":"en"
|
|
|
5 |
"loc_file_path":"./data/locations/example_locations.json",
|
6 |
"role_file_dir":"./data/roles/",
|
7 |
"role_agent_codes":["Lacia-en","Trek-en"],
|
8 |
+
"intervention":"",
|
9 |
"script":"One day, an enigmatic signal from an unknown source reached Lacia and Trek. The pattern resembled Trek’s early consciousness digitization code—but far more evolved. Tracing its origin to lunar orbit, the two found themselves divided: Lacia urged caution, fearing the dangers of an unknown intelligence, while Trek saw it as proof that human evolution had already begun elsewhere. As they followed the signal, ideological tension grew—one seeking to contain it, the other longing to embrace it.",
|
10 |
"source":"example_world",
|
11 |
"language":"en"
|
experiment_presets/experiment_icefire.json
CHANGED
@@ -5,7 +5,7 @@
|
|
5 |
"loc_file_path":"./data/locations/A_Song_of_Ice_and_Fire.json",
|
6 |
"role_file_dir":"./data/roles/",
|
7 |
"role_agent_codes":["SansaStark-zh", "CerseiLannister-zh", "AryaStark-zh", "DaenerysTargaryen-zh", "JaimeLannister-zh", "TyrionLannister-zh", "JonSnow-zh", "BranStark-zh"],
|
8 |
-
"intervention":"",
|
9 |
"script":"",
|
10 |
"source":"A_Song_of_Ice_and_Fire",
|
11 |
"language":"zh"
|
|
|
5 |
"loc_file_path":"./data/locations/A_Song_of_Ice_and_Fire.json",
|
6 |
"role_file_dir":"./data/roles/",
|
7 |
"role_agent_codes":["SansaStark-zh", "CerseiLannister-zh", "AryaStark-zh", "DaenerysTargaryen-zh", "JaimeLannister-zh", "TyrionLannister-zh", "JonSnow-zh", "BranStark-zh"],
|
8 |
+
"intervention":"血色婚礼刚刚结束————这是一场艾德慕·徒利(凯特琳·史塔克的弟弟)与罗莎琳·弗雷的婚礼,罗柏·史塔克为了修复与弗雷家族关系而策划的政治联姻,却在泰温兰尼斯特与老瓦德伯爵共同策划的阴谋下,演变成了一场彻头彻尾的悲剧。凯特琳、罗柏被杀,几乎所有的史塔克军人都被杀死,佛雷、波顿一方仅仅付出了大约五十人的伤亡代价。此事让兰尼斯特家族的实力大大增强,让史塔克家族与兰尼斯特家族之间的仇恨变得无可挽回。众人需要对这一事件做出反应。",
|
9 |
"script":"",
|
10 |
"source":"A_Song_of_Ice_and_Fire",
|
11 |
"language":"zh"
|
frontend/css/right-section/preset-panel.css
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.preset-container {
|
2 |
+
display: flex;
|
3 |
+
flex-direction: column;
|
4 |
+
gap: 15px;
|
5 |
+
padding: 15px;
|
6 |
+
}
|
7 |
+
|
8 |
+
.preset-header {
|
9 |
+
display: flex;
|
10 |
+
justify-content: space-between;
|
11 |
+
align-items: center;
|
12 |
+
}
|
13 |
+
|
14 |
+
.preset-header h3 {
|
15 |
+
color: #5f3737;
|
16 |
+
margin: 0;
|
17 |
+
}
|
18 |
+
|
19 |
+
.preset-select {
|
20 |
+
width: 100%;
|
21 |
+
padding: 8px;
|
22 |
+
border: 1px solid #5f3737;
|
23 |
+
border-radius: 4px;
|
24 |
+
background-color: white;
|
25 |
+
color: #5f3737;
|
26 |
+
margin-bottom: 10px;
|
27 |
+
}
|
28 |
+
|
29 |
+
.preset-select option {
|
30 |
+
padding: 8px;
|
31 |
+
}
|
32 |
+
|
33 |
+
.preset-submit-btn {
|
34 |
+
padding: 10px;
|
35 |
+
background-color: #5f3737;
|
36 |
+
color: white;
|
37 |
+
border: none;
|
38 |
+
border-radius: 4px;
|
39 |
+
cursor: pointer;
|
40 |
+
transition: background-color 0.3s;
|
41 |
+
width: 100%;
|
42 |
+
}
|
43 |
+
|
44 |
+
.preset-submit-btn:hover {
|
45 |
+
background-color: #7a4747;
|
46 |
+
}
|
47 |
+
|
48 |
+
.preset-submit-btn:disabled {
|
49 |
+
background-color: #ccc;
|
50 |
+
cursor: not-allowed;
|
51 |
+
}
|
frontend/css/right-section/status-panel.css
CHANGED
@@ -64,6 +64,62 @@
|
|
64 |
font-style: italic;
|
65 |
}
|
66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
/* 状态指示器 */
|
68 |
.status-indicator {
|
69 |
width: 8px;
|
|
|
64 |
font-style: italic;
|
65 |
}
|
66 |
|
67 |
+
/* 设定模块样式 */
|
68 |
+
.settings-content {
|
69 |
+
max-height: 300px;
|
70 |
+
overflow-y: auto;
|
71 |
+
}
|
72 |
+
|
73 |
+
.setting-item {
|
74 |
+
background-color: #fdf5ee;
|
75 |
+
padding: 10px;
|
76 |
+
border-radius: 6px;
|
77 |
+
margin-bottom: 8px;
|
78 |
+
}
|
79 |
+
|
80 |
+
.setting-term {
|
81 |
+
font-weight: bold;
|
82 |
+
color: #5f3737;
|
83 |
+
margin-bottom: 4px;
|
84 |
+
}
|
85 |
+
|
86 |
+
.setting-nature {
|
87 |
+
color: #666;
|
88 |
+
font-size: 0.9em;
|
89 |
+
margin-bottom: 4px;
|
90 |
+
}
|
91 |
+
|
92 |
+
.setting-detail {
|
93 |
+
color: #666;
|
94 |
+
line-height: 1.4;
|
95 |
+
}
|
96 |
+
|
97 |
+
.no-settings {
|
98 |
+
color: #666;
|
99 |
+
font-style: italic;
|
100 |
+
text-align: center;
|
101 |
+
padding: 10px;
|
102 |
+
}
|
103 |
+
|
104 |
+
/* 滚动条样式 */
|
105 |
+
.settings-content::-webkit-scrollbar {
|
106 |
+
width: 6px;
|
107 |
+
}
|
108 |
+
|
109 |
+
.settings-content::-webkit-scrollbar-track {
|
110 |
+
background: #f1f1f1;
|
111 |
+
border-radius: 3px;
|
112 |
+
}
|
113 |
+
|
114 |
+
.settings-content::-webkit-scrollbar-thumb {
|
115 |
+
background: #888;
|
116 |
+
border-radius: 3px;
|
117 |
+
}
|
118 |
+
|
119 |
+
.settings-content::-webkit-scrollbar-thumb:hover {
|
120 |
+
background: #555;
|
121 |
+
}
|
122 |
+
|
123 |
/* 状态指示器 */
|
124 |
.status-indicator {
|
125 |
width: 8px;
|
frontend/js/i18n.js
CHANGED
@@ -23,7 +23,9 @@ const translations = {
|
|
23 |
configSubmitted: "Configuration has been submitted to the server!",
|
24 |
submitFailed: "Submission failed. Please check server status.",
|
25 |
networkError: "Submission failed. Please check network connection.",
|
26 |
-
APIsettings: "API Setting"
|
|
|
|
|
27 |
},
|
28 |
zh: {
|
29 |
start: "开始",
|
@@ -49,13 +51,15 @@ const translations = {
|
|
49 |
configSubmitted: "配置已提交到服务器!",
|
50 |
submitFailed: "提交失败,请检查服务器状态。",
|
51 |
networkError: "提交失败,请检查网络连接。",
|
52 |
-
APIsettings: "API设置"
|
|
|
|
|
53 |
}
|
54 |
};
|
55 |
|
56 |
class I18nManager {
|
57 |
constructor() {
|
58 |
-
this.currentLang = '
|
59 |
this.init();
|
60 |
}
|
61 |
|
|
|
23 |
configSubmitted: "Configuration has been submitted to the server!",
|
24 |
submitFailed: "Submission failed. Please check server status.",
|
25 |
networkError: "Submission failed. Please check network connection.",
|
26 |
+
APIsettings: "API Setting",
|
27 |
+
preset: "Preset",
|
28 |
+
presetList: "Preset List",
|
29 |
},
|
30 |
zh: {
|
31 |
start: "开始",
|
|
|
51 |
configSubmitted: "配置已提交到服务器!",
|
52 |
submitFailed: "提交失败,请检查服务器状态。",
|
53 |
networkError: "提交失败,请检查网络连接。",
|
54 |
+
APIsettings: "API设置",
|
55 |
+
preset: "预设",
|
56 |
+
presetList: "预设列表",
|
57 |
}
|
58 |
};
|
59 |
|
60 |
class I18nManager {
|
61 |
constructor() {
|
62 |
+
this.currentLang = 'en';
|
63 |
this.init();
|
64 |
}
|
65 |
|
frontend/js/right-section/preset-panel.js
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class PresetPanel {
|
2 |
+
constructor() {
|
3 |
+
this.presets = [];
|
4 |
+
this.currentPreset = null;
|
5 |
+
this.container = document.querySelector('.preset-container');
|
6 |
+
this.select = document.querySelector('.preset-select');
|
7 |
+
this.submitBtn = document.querySelector('.preset-submit-btn');
|
8 |
+
this.init();
|
9 |
+
}
|
10 |
+
|
11 |
+
init() {
|
12 |
+
this.loadPresets();
|
13 |
+
this.setupEventListeners();
|
14 |
+
}
|
15 |
+
|
16 |
+
async loadPresets() {
|
17 |
+
try {
|
18 |
+
const response = await fetch('/api/list-presets');
|
19 |
+
if (!response.ok) {
|
20 |
+
throw new Error('Failed to load presets');
|
21 |
+
}
|
22 |
+
const data = await response.json();
|
23 |
+
this.presets = data.presets;
|
24 |
+
this.renderPresetOptions();
|
25 |
+
} catch (error) {
|
26 |
+
console.error('Error loading presets:', error);
|
27 |
+
alert('加载预设列表失败,请刷新页面重试');
|
28 |
+
}
|
29 |
+
}
|
30 |
+
|
31 |
+
renderPresetOptions() {
|
32 |
+
if (!this.select) return;
|
33 |
+
|
34 |
+
this.select.innerHTML = '<option value="">选择预设...</option>';
|
35 |
+
this.presets.forEach(preset => {
|
36 |
+
const option = document.createElement('option');
|
37 |
+
option.value = preset;
|
38 |
+
option.textContent = preset.replace('.json', '');
|
39 |
+
this.select.appendChild(option);
|
40 |
+
});
|
41 |
+
}
|
42 |
+
|
43 |
+
setupEventListeners() {
|
44 |
+
if (this.select) {
|
45 |
+
this.select.addEventListener('change', () => {
|
46 |
+
this.currentPreset = this.select.value;
|
47 |
+
this.submitBtn.disabled = !this.currentPreset;
|
48 |
+
});
|
49 |
+
}
|
50 |
+
|
51 |
+
if (this.submitBtn) {
|
52 |
+
this.submitBtn.addEventListener('click', () => this.handleSubmit());
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
async handleSubmit() {
|
57 |
+
if (!this.currentPreset) return;
|
58 |
+
|
59 |
+
// 禁用按钮,防止重复点击
|
60 |
+
this.submitBtn.disabled = true;
|
61 |
+
this.submitBtn.textContent = '加载中...';
|
62 |
+
|
63 |
+
try {
|
64 |
+
const response = await fetch('/api/load-preset', {
|
65 |
+
method: 'POST',
|
66 |
+
headers: {
|
67 |
+
'Content-Type': 'application/json'
|
68 |
+
},
|
69 |
+
body: JSON.stringify({
|
70 |
+
preset: this.currentPreset
|
71 |
+
})
|
72 |
+
});
|
73 |
+
|
74 |
+
const data = await response.json();
|
75 |
+
|
76 |
+
if (!response.ok) {
|
77 |
+
throw new Error(data.detail || '加载预设失败');
|
78 |
+
}
|
79 |
+
|
80 |
+
if (data.success) {
|
81 |
+
// 触发预设加载成功事件
|
82 |
+
window.dispatchEvent(new CustomEvent('preset-loaded', {
|
83 |
+
detail: { preset: this.currentPreset }
|
84 |
+
}));
|
85 |
+
|
86 |
+
// 重新加载初始数据
|
87 |
+
if (window.ws && window.ws.readyState === WebSocket.OPEN) {
|
88 |
+
// 先停止当前的故事生成
|
89 |
+
window.ws.send(JSON.stringify({
|
90 |
+
type: 'control',
|
91 |
+
action: 'stop'
|
92 |
+
}));
|
93 |
+
|
94 |
+
// 重新连接WebSocket以获取新的初始数据
|
95 |
+
const clientId = Date.now().toString();
|
96 |
+
const ws = new WebSocket(`ws://${window.location.host}/ws/${clientId}`);
|
97 |
+
|
98 |
+
ws.onopen = () => {
|
99 |
+
console.log('WebSocket重新连接成功');
|
100 |
+
};
|
101 |
+
|
102 |
+
ws.onmessage = (event) => {
|
103 |
+
const message = JSON.parse(event.data);
|
104 |
+
// 触发自定义事件,让其他面板更新数据
|
105 |
+
window.dispatchEvent(new CustomEvent('websocket-message', {
|
106 |
+
detail: message
|
107 |
+
}));
|
108 |
+
};
|
109 |
+
|
110 |
+
ws.onerror = (error) => {
|
111 |
+
console.error('WebSocket错误:', error);
|
112 |
+
alert('连接服务器失败,请刷新页面重试');
|
113 |
+
};
|
114 |
+
|
115 |
+
// 更新全局WebSocket实例
|
116 |
+
window.ws = ws;
|
117 |
+
}
|
118 |
+
|
119 |
+
alert('预设加载成功!');
|
120 |
+
}
|
121 |
+
} catch (error) {
|
122 |
+
console.error('Error loading preset:', error);
|
123 |
+
alert(error.message || '加载预设失败,请重试');
|
124 |
+
} finally {
|
125 |
+
// 恢复按钮状态
|
126 |
+
this.submitBtn.disabled = false;
|
127 |
+
this.submitBtn.textContent = '加载预设';
|
128 |
+
}
|
129 |
+
}
|
130 |
+
}
|
131 |
+
|
132 |
+
const presetPanel = new PresetPanel();
|
frontend/js/right-section/status-panel.js
CHANGED
@@ -16,6 +16,7 @@ class StatusPanel {
|
|
16 |
init() {
|
17 |
// 设置默认状态
|
18 |
this.updateAllStatus(this.currentStatus);
|
|
|
19 |
|
20 |
// 监听WebSocket消息
|
21 |
window.addEventListener('websocket-message', (event) => {
|
@@ -27,6 +28,7 @@ class StatusPanel {
|
|
27 |
|
28 |
if (message.type === 'initial_data' && message.data.status) {
|
29 |
this.updateAllStatus(message.data.status);
|
|
|
30 |
}
|
31 |
});
|
32 |
}
|
@@ -68,11 +70,29 @@ class StatusPanel {
|
|
68 |
}
|
69 |
}
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
updateAllStatus(statusData) {
|
72 |
-
this.currentStatus = statusData
|
73 |
if (statusData.event !== undefined) this.updateEvent(statusData.event);
|
74 |
if (statusData.location) this.updateLocation(statusData.location);
|
75 |
if (statusData.group) this.updateGroup(statusData.group);
|
76 |
}
|
77 |
}
|
78 |
-
|
|
|
|
16 |
init() {
|
17 |
// 设置默认状态
|
18 |
this.updateAllStatus(this.currentStatus);
|
19 |
+
this.updateSettings([]);
|
20 |
|
21 |
// 监听WebSocket消息
|
22 |
window.addEventListener('websocket-message', (event) => {
|
|
|
28 |
|
29 |
if (message.type === 'initial_data' && message.data.status) {
|
30 |
this.updateAllStatus(message.data.status);
|
31 |
+
this.updateSettings(message.data.settings);
|
32 |
}
|
33 |
});
|
34 |
}
|
|
|
70 |
}
|
71 |
}
|
72 |
|
73 |
+
updateSettings(settings) {
|
74 |
+
const settingsContainer = document.querySelector('.settings-content');
|
75 |
+
if (settingsContainer) {
|
76 |
+
if (settings && settings.length > 0) {
|
77 |
+
settingsContainer.innerHTML = settings.map(setting => `
|
78 |
+
<div class="setting-item">
|
79 |
+
<div class="setting-term">${setting.term}</div>
|
80 |
+
<div class="setting-nature">${setting.nature}</div>
|
81 |
+
<div class="setting-detail">${setting.detail}</div>
|
82 |
+
</div>
|
83 |
+
`).join('');
|
84 |
+
} else {
|
85 |
+
settingsContainer.innerHTML = '<div class="no-settings">暂无设定</div>';
|
86 |
+
}
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
updateAllStatus(statusData) {
|
91 |
+
this.currentStatus = statusData;
|
92 |
if (statusData.event !== undefined) this.updateEvent(statusData.event);
|
93 |
if (statusData.location) this.updateLocation(statusData.location);
|
94 |
if (statusData.group) this.updateGroup(statusData.group);
|
95 |
}
|
96 |
}
|
97 |
+
|
98 |
+
const statusPanel = new StatusPanel();
|
index.html
CHANGED
@@ -13,6 +13,7 @@
|
|
13 |
<link rel="stylesheet" href="./frontend/css/right-section/settings-panel.css">
|
14 |
<link rel="stylesheet" href="./frontend/css/right-section/status-panel.css">
|
15 |
<link rel="stylesheet" href="./frontend/css/right-section/scene-panel.css">
|
|
|
16 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
17 |
<script src="https://d3js.org/d3.v7.min.js"></script>
|
18 |
</head>
|
@@ -73,8 +74,8 @@
|
|
73 |
<div class="right-section">
|
74 |
<div class="right-toolbar">
|
75 |
<button class="tab-btn active" data-target="status-panel" data-i18n="status">状态</button>
|
76 |
-
<button class="tab-btn" data-target="settings-panel" data-i18n="settings">设定</button>
|
77 |
<button class="tab-btn" data-target="scenes-panel" data-i18n="scenes">场景</button>
|
|
|
78 |
<button class="tab-btn" data-target="api-panel" data-i18n="APIsettings">API设置</button>
|
79 |
</div>
|
80 |
<div class="right-content">
|
@@ -102,11 +103,13 @@
|
|
102 |
</div>
|
103 |
</div>
|
104 |
</div>
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
|
|
|
|
110 |
</div>
|
111 |
</div>
|
112 |
<div class="tab-panel" id="scenes-panel">
|
@@ -119,6 +122,17 @@
|
|
119 |
</div>
|
120 |
</div>
|
121 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
<div class="tab-panel" id="api-panel">
|
123 |
<div class="api-container">
|
124 |
<label for="api-provider"><h3 data-i18n="apiProvider">API 提供商:</h3></label>
|
@@ -154,6 +168,7 @@
|
|
154 |
<script src="./frontend/js/right-section/settings-panel.js"></script>
|
155 |
<script src="./frontend/js/right-section/status-panel.js"></script>
|
156 |
<script src="./frontend/js/right-section/scene-panel.js"></script>
|
|
|
157 |
<script>
|
158 |
document.addEventListener('DOMContentLoaded', () => {
|
159 |
if (window.RightSection) {
|
|
|
13 |
<link rel="stylesheet" href="./frontend/css/right-section/settings-panel.css">
|
14 |
<link rel="stylesheet" href="./frontend/css/right-section/status-panel.css">
|
15 |
<link rel="stylesheet" href="./frontend/css/right-section/scene-panel.css">
|
16 |
+
<link rel="stylesheet" href="./frontend/css/right-section/preset-panel.css">
|
17 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
18 |
<script src="https://d3js.org/d3.v7.min.js"></script>
|
19 |
</head>
|
|
|
74 |
<div class="right-section">
|
75 |
<div class="right-toolbar">
|
76 |
<button class="tab-btn active" data-target="status-panel" data-i18n="status">状态</button>
|
|
|
77 |
<button class="tab-btn" data-target="scenes-panel" data-i18n="scenes">场景</button>
|
78 |
+
<button class="tab-btn" data-target="preset-panel" data-i18n="preset">预设</button>
|
79 |
<button class="tab-btn" data-target="api-panel" data-i18n="APIsettings">API设置</button>
|
80 |
</div>
|
81 |
<div class="right-content">
|
|
|
103 |
</div>
|
104 |
</div>
|
105 |
</div>
|
106 |
+
|
107 |
+
<div class="status-module" id="settings-module">
|
108 |
+
<h3 data-i18n="settings">设定</h3>
|
109 |
+
<div class="module-content settings-content">
|
110 |
+
<!-- 设定内容将通过JS动态添加 -->
|
111 |
+
</div>
|
112 |
+
</div>
|
113 |
</div>
|
114 |
</div>
|
115 |
<div class="tab-panel" id="scenes-panel">
|
|
|
122 |
</div>
|
123 |
</div>
|
124 |
</div>
|
125 |
+
<div class="tab-panel" id="preset-panel">
|
126 |
+
<div class="preset-container">
|
127 |
+
<div class="preset-header">
|
128 |
+
<h3 data-i18n="presetList">预设列表</h3>
|
129 |
+
</div>
|
130 |
+
<select class="preset-select">
|
131 |
+
<!-- 预设选项将通过JS动态添加 -->
|
132 |
+
</select>
|
133 |
+
<button class="preset-submit-btn" disabled>Load</button>
|
134 |
+
</div>
|
135 |
+
</div>
|
136 |
<div class="tab-panel" id="api-panel">
|
137 |
<div class="api-container">
|
138 |
<label for="api-provider"><h3 data-i18n="apiProvider">API 提供商:</h3></label>
|
|
|
168 |
<script src="./frontend/js/right-section/settings-panel.js"></script>
|
169 |
<script src="./frontend/js/right-section/status-panel.js"></script>
|
170 |
<script src="./frontend/js/right-section/scene-panel.js"></script>
|
171 |
+
<script src="./frontend/js/right-section/preset-panel.js"></script>
|
172 |
<script>
|
173 |
document.addEventListener('DOMContentLoaded', () => {
|
174 |
if (window.RightSection) {
|