m97j commited on
Commit
8cebfe3
ยท
verified ยท
1 Parent(s): a4594bb

style(symbolic): update UI text

Browse files
Files changed (3) hide show
  1. README.md +8 -7
  2. app.py +7 -160
  3. templates/index.html +121 -102
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
- title: CWIE Symbolic Processing Server
3
  emoji: ๐Ÿ‘€
4
  colorFrom: green
5
- colorTo: green
6
  sdk: docker
7
  pinned: false
8
  license: mit
@@ -116,15 +116,16 @@ symbolic/
116
  ---
117
 
118
  <!-- app-tab:start -->
119
- # ๐Ÿ‘€ CWIE Symbolic Processing Server
120
 
121
- ๊ฒŒ์ž„ ๋‚ด ํ™˜๊ฒฝ๊ณผ CWIE Core Inference Server์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” API ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค.
122
- ์ตœ์†Œ ์ž…๋ ฅ๋งŒ์œผ๋กœ๋„ ๋™์ž‘ํ•˜๋ฉฐ, RAG ๊ธฐ๋ฐ˜ ๋ฌธ์„œ์™€ NPC ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•ด ๋Œ€ํ™”๋ฅผ ๋ณด๊ฐ•ํ•ฉ๋‹ˆ๋‹ค.
123
 
124
  ### โš™๏ธ ์ฃผ์š” ๊ธฐ๋Šฅ
125
  - ๊ฒŒ์ž„ ์„œ๋ฒ„ ์š”์ฒญ ์ˆ˜์‹  ๋ฐ ์ „์ฒ˜๋ฆฌ
126
  - ์กฐ๊ฑด ํŒ์ • ํ›„ ๋ชจ๋ธ ์ถ”๋ก  ๋ฐฉ์‹ ๊ฒฐ์ • [delta, flag head ์‚ฌ์šฉ ์—ฌ๋ถ€ ๊ฒฐ์ •]
127
- - RAG ๊ธฐ๋ฐ˜ ์„ธ๊ณ„๊ด€ยท์ƒํ™ฉยทNPC ์„ฑ๊ฒฉ ๋ฐ˜์˜
128
- - ์‘๋‹ต ํ›„์ฒ˜๋ฆฌ ๋ฐ JSON ํ‘œ์ค€ ์‘๋‹ต ๋ฐ˜ํ™˜
 
129
  <!-- app-tab:end -->
130
  ---
 
1
  ---
2
+ title: CWIE Symbolic Processor
3
  emoji: ๐Ÿ‘€
4
  colorFrom: green
5
+ colorTo: blue
6
  sdk: docker
7
  pinned: false
8
  license: mit
 
116
  ---
117
 
118
  <!-- app-tab:start -->
119
+ # ๐Ÿ‘€ CWIE Symbolic Processor
120
 
121
+ ๊ฒŒ์ž„ ๋‚ด ํ™˜๊ฒฝ๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” API ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค.
122
+ Neuro-Symbolic System์—์„œ Symbolic Processing(Pre/Post Processing)์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
123
 
124
  ### โš™๏ธ ์ฃผ์š” ๊ธฐ๋Šฅ
125
  - ๊ฒŒ์ž„ ์„œ๋ฒ„ ์š”์ฒญ ์ˆ˜์‹  ๋ฐ ์ „์ฒ˜๋ฆฌ
126
  - ์กฐ๊ฑด ํŒ์ • ํ›„ ๋ชจ๋ธ ์ถ”๋ก  ๋ฐฉ์‹ ๊ฒฐ์ • [delta, flag head ์‚ฌ์šฉ ์—ฌ๋ถ€ ๊ฒฐ์ •]
127
+ - Neuro Engine๊ณผ API ํ†ต์‹ ์„ ํ†ตํ•ด Core ๋ชจ๋ธ ์ถ”๋ก  ์ง„ํ–‰
128
+ - ๋ชจ๋ธ ์‘๋‹ต ํ›„์ฒ˜๋ฆฌ ๋ฐ JSON ํ‘œ์ค€ ์‘๋‹ต ๋ฐ˜ํ™˜
129
+ - ์ „, ํ›„์ฒ˜๋ฆฌ, ์กฐ๊ฑด ๊ฒ€์ฆ์‹œ RAG ๊ธฐ๋ฐ˜ ์„ธ๊ณ„๊ด€ยท์ƒํ™ฉ๋ณ„ ๊ทœ์น™ยทNPC ์„ฑ๊ฒฉ ๋ฐ˜์˜
130
  <!-- app-tab:end -->
131
  ---
app.py CHANGED
@@ -19,7 +19,7 @@ model_ready = False
19
 
20
  async def load_models(app: FastAPI):
21
  global model_ready
22
- print("๐Ÿš€ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹œ์ž‘...")
23
  fb_tokenizer, fb_model = load_fallback_model(FALLBACK_MODEL_NAME, FALLBACK_MODEL_DIR, token=HF_TOKEN)
24
  app.state.fallback_tokenizer = fb_tokenizer
25
  app.state.fallback_model = fb_model
@@ -32,20 +32,20 @@ async def load_models(app: FastAPI):
32
  if not chroma_initialized():
33
  docs = load_game_docs_from_disk(str(docs_path))
34
  add_docs(docs)
35
- print(f"โœ… RAG ๋ฌธ์„œ {len(docs)}๊ฐœ ์‚ฝ์ž… ์™„๋ฃŒ")
36
  else:
37
- print("๐Ÿ”„ RAG DB ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋จ")
38
 
39
  model_ready = True
40
- print("โœ… ๋ชจ๋“  ๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ")
41
 
42
  @asynccontextmanager
43
  async def lifespan(app: FastAPI):
44
  asyncio.create_task(load_models(app))
45
  yield
46
- print("๐Ÿ›‘ ์„œ๋ฒ„ ์ข…๋ฃŒ ์ค‘...")
47
 
48
- app = FastAPI(title="ai-server", lifespan=lifespan)
49
 
50
  app.add_middleware(
51
  CORSMiddleware,
@@ -65,7 +65,7 @@ async def root(request: Request):
65
  if start_tag in md_content and end_tag in md_content:
66
  short_md = md_content.split(start_tag)[1].split(end_tag)[0].strip()
67
  else:
68
- short_md = md_content # fallback: ์ „์ฒด ๋‚ด์šฉ
69
 
70
  html_from_md = markdown.markdown(short_md)
71
  return templates.TemplateResponse("index.html", {"request": request, "readme_content": html_from_md})
@@ -103,156 +103,3 @@ async def ask(request: Request, req: AskReq):
103
  npc_config=npc_config_dict
104
  )
105
 
106
-
107
-
108
- '''
109
- ์ตœ์ข… gameโ€‘server โ†’ aiโ€‘server ์š”์ฒญ ์˜ˆ์‹œ
110
- {
111
- "session_id": "abc123",
112
- "npc_id": "mother_abandoned_factory",
113
- "user_input": "์•„! ๋จธ๋ฆฌ๊ฐ€โ€ฆ ๊ธฐ์–ต์ด ๋– ์˜ฌ๋ž์–ด์š”.",
114
-
115
- /* game-server์—์„œ ํ•„ํ„ฐ๋งํ•œ ํ•„์ˆ˜/์„ ํƒ require ์š”์†Œ๋งŒ ํฌํ•จ */
116
- "context": {
117
- "require": {
118
- "items": ["photo_forgotten_party"], // ํ•„์ˆ˜/์„ ํƒ ๊ตฌ๋ถ„์€ npc_config.json์—์„œ
119
- "actions": ["visited_factory"],
120
- "game_state": ["box_opened"], // ํ•„์š” ์‹œ
121
- "delta": { "trust": 0.35, "relationship": 0.1 }
122
- },
123
-
124
- "player_state": {
125
- "level": 7,
126
- "reputation": "helpful",
127
- "location": "map1"
128
- /* ์ „์ฒด ์ธ๋ฒคํ† ๋ฆฌ/ํ–‰๋™ ๋กœ๊ทธ๋Š” ํ•„์š” ์‹œ ๋ณ„๋„ ์ „๋‹ฌ */
129
- },
130
-
131
- "game_state": {
132
- "current_quest": "search_jason",
133
- "quest_stage": "in_progress",
134
- "location": "map1",
135
- "time_of_day": "evening"
136
- },
137
-
138
- "npc_state": {
139
- "id": "mother_abandoned_factory",
140
- "name": "์‹ค๋น„์•„",
141
- "persona_name": "Silvia",
142
- "dialogue_style": "emotional",
143
- "relationship": 0.35,
144
- "npc_mood": "grief"
145
- },
146
-
147
- "dialogue_history": [
148
- {
149
- "player": "ํ˜น์‹œ ์ด ๊ณต์žฅ์—์„œ ๋ณธ ๊ฑธ ๋งํ•ด์ค˜์š”.",
150
- "npc": "๊ทธ๋‚ ์„ ๋– ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ๋„ˆ๋ฌด ํž˜๋“ค์–ด์š”."
151
- }
152
- ]
153
- }
154
- }
155
- '''
156
-
157
- '''
158
- {
159
- "session_id": "abc123",
160
- "npc_id": "mother_abandoned_factory",
161
- "user_input": "์•„! ๋จธ๋ฆฌ๊ฐ€โ€ฆ ๊ธฐ์–ต์ด ๋– ์˜ฌ๋ž์–ด์š”.",
162
- "precheck_passed": true,
163
- "context": {
164
- "player_status": {
165
- "level": 7,
166
- "reputation": "helpful",
167
- "location": "map1",
168
-
169
- "trigger_items": ["photo_forgotten_party"], // game-server์—์„œ ์กฐ๊ฑด ํ•„ํ„ฐ ํ›„ key๋กœ ๋ณ€ํ™˜
170
- "trigger_actions": ["visited_factory"] // ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ key ๋ฌธ์ž์—ด
171
-
172
- /* ์›๋ณธ ์ „์ฒด inventory/actions ๋ฐฐ์—ด์€ ์„œ๋น„์Šค ํ•„์š” ์‹œ ๋ณ„๋„ ์ „๋‹ฌ ๊ฐ€๋Šฅ
173
- ํ•˜์ง€๋งŒ ai-server ์กฐ๊ฑด ํŒ์ •์—๋Š” trigger_*๋งŒ ์‚ฌ์šฉ */
174
- },
175
- "game_state": {
176
- "current_quest": "search_jason",
177
- "quest_stage": "in_progress",
178
- "location": "map1",
179
- "time_of_day": "evening"
180
- },
181
- "npc_config": {
182
- "id": "mother_abandoned_factory",
183
- "name": "์‹ค๋น„์•„",
184
- "persona_name": "Silvia",
185
- "dialogue_style": "emotional",
186
- "relationship": 0.35,
187
- "npc_mood": "grief",
188
- "trigger_values": {
189
- "in_progress": ["๊ธฐ์–ต", "์‚ฌ์ง„", "ํŒŒํ‹ฐ"]
190
- },
191
- "trigger_definitions": {
192
- "in_progress": {
193
- "required_text": ["๊ธฐ์–ต", "์‚ฌ์ง„"],
194
- "required_items": ["photo_forgotten_party"], // trigger_items์™€ ๋งค์นญ
195
- "required_actions": ["visited_factory"], // trigger_actions์™€ ๋งค์นญ
196
- "emotion_threshold": { "sad": 0.2 },
197
- "fallback_style": {
198
- "style": "guarded",
199
- "npc_emotion": "suspicious"
200
- }
201
- }
202
- }
203
- },
204
- "dialogue_history": [
205
- {
206
- "player": "ํ˜น์‹œ ์ด ๊ณต์žฅ์—์„œ ๋ณธ ๊ฑธ ๋งํ•ด์ค˜์š”.",
207
- "npc": "๊ทธ๋‚ ์„ ๋– ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ๋„ˆ๋ฌด ํž˜๋“ค์–ด์š”."
208
- }
209
- ]
210
- }
211
- }
212
-
213
- ------------------------------------------------------------------------------------------------------
214
-
215
- ์ด์ „ game-server ์š”์ฒญ ๊ตฌ์กฐ ์˜ˆ์‹œ:
216
- {
217
- "session_id": "abc123",
218
- "npc_id": "mother_abandoned_factory",
219
- "user_input": "์•„! ๋จธ๋ฆฌ๊ฐ€โ€ฆ ๊ธฐ์–ต์ด ๋– ์˜ฌ๋ž์–ด์š”.",
220
- "context": {
221
- "player_status": {
222
- "level": 7,
223
- "reputation": "helpful",
224
- "location": "map1",
225
- "items": ["photo_forgotten_party"],
226
- "actions": ["visited_factory", "talked_to_guard"]
227
- },
228
- "game_state": {
229
- "current_quest": "search_jason",
230
- "quest_stage": "in_progress",
231
- "location": "map1",
232
- "time_of_day": "evening"
233
- },
234
- "npc_config": {
235
- "id": "mother_abandoned_factory",
236
- "name": "์‹ค๋น„์•„",
237
- "persona_name": "Silvia",
238
- "dialogue_style": "emotional",
239
- "relationship": 0.35,
240
- "npc_mood": "grief",
241
- "trigger_values": {
242
- "in_progress": ["๊ธฐ์–ต", "์‚ฌ์ง„", "ํŒŒํ‹ฐ"]
243
- },
244
- "trigger_definitions": {
245
- "in_progress": {
246
- "required_text": ["๊ธฐ์–ต", "์‚ฌ์ง„"],
247
- "emotion_threshold": {"sad": 0.2},
248
- "fallback_style": {"style": "guarded", "npc_emotion": "suspicious"}
249
- }
250
- }
251
- },
252
- "dialogue_history": [
253
- {"player": "ํ˜น์‹œ ์ด ๊ณต์žฅ์—์„œ ๋ณธ ๊ฑธ ๋งํ•ด์ค˜์š”.", "npc": "๊ทธ๋‚ ์„ ๋– ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ๋„ˆ๋ฌด ํž˜๋“ค์–ด์š”."}
254
- ]
255
- }
256
- }
257
-
258
- '''
 
19
 
20
  async def load_models(app: FastAPI):
21
  global model_ready
22
+ print("๐Ÿš€ starting model loading...")
23
  fb_tokenizer, fb_model = load_fallback_model(FALLBACK_MODEL_NAME, FALLBACK_MODEL_DIR, token=HF_TOKEN)
24
  app.state.fallback_tokenizer = fb_tokenizer
25
  app.state.fallback_model = fb_model
 
32
  if not chroma_initialized():
33
  docs = load_game_docs_from_disk(str(docs_path))
34
  add_docs(docs)
35
+ print(f"โœ… finished inserting {len(docs)} documents into RAG DB")
36
  else:
37
+ print("๐Ÿ”„ already initialized RAG DB")
38
 
39
  model_ready = True
40
+ print("โœ… model loading complete, server is ready to accept requests")
41
 
42
  @asynccontextmanager
43
  async def lifespan(app: FastAPI):
44
  asyncio.create_task(load_models(app))
45
  yield
46
+ print("๐Ÿ›‘ shutting down...")
47
 
48
+ app = FastAPI(title="neuro-engine", lifespan=lifespan)
49
 
50
  app.add_middleware(
51
  CORSMiddleware,
 
65
  if start_tag in md_content and end_tag in md_content:
66
  short_md = md_content.split(start_tag)[1].split(end_tag)[0].strip()
67
  else:
68
+ short_md = md_content # fallback: all content if tags not found
69
 
70
  html_from_md = markdown.markdown(short_md)
71
  return templates.TemplateResponse("index.html", {"request": request, "readme_content": html_from_md})
 
103
  npc_config=npc_config_dict
104
  )
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/index.html CHANGED
@@ -1,104 +1,123 @@
1
  <!DOCTYPE html>
2
  <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <title>PersonaChatEngine AI Server</title>
6
- <style>
7
- /* ๋‹คํฌ๋ชจ๋“œ ๊ธฐ๋ณธ ์Šคํƒ€์ผ */
8
- body {
9
- font-family: 'Segoe UI', Arial, sans-serif;
10
- margin: 0;
11
- background-color: #0d1117;
12
- color: #e6edf3;
13
- }
14
- header {
15
- background-color: #161b22;
16
- padding: 20px 40px;
17
- text-align: center;
18
- border-bottom: 1px solid #30363d;
19
- }
20
- header h1 {
21
- margin: 0;
22
- font-size: 1.8em;
23
- background: linear-gradient(90deg, #58a6ff, #a371f7);
24
- -webkit-background-clip: text;
25
- -webkit-text-fill-color: transparent;
26
- }
27
- header p {
28
- margin: 5px 0 0;
29
- color: #8b949e;
30
- }
31
- main {
32
- max-width: 900px;
33
- margin: 40px auto;
34
- background-color: #161b22;
35
- padding: 30px;
36
- border-radius: 12px;
37
- box-shadow: 0 4px 20px rgba(0,0,0,0.3);
38
- border: 1px solid #30363d;
39
- }
40
- h1, h2, h3 {
41
- color: #58a6ff;
42
- }
43
- p, ul {
44
- line-height: 1.6;
45
- }
46
- ul {
47
- padding-left: 20px;
48
- }
49
- /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ */
50
- a.button {
51
- display: inline-block;
52
- margin: 15px 10px 0 0;
53
- padding: 12px 20px;
54
- background: linear-gradient(135deg, #58a6ff, #a371f7);
55
- color: white;
56
- text-decoration: none;
57
- border-radius: 8px;
58
- font-weight: bold;
59
- transition: transform 0.2s ease, box-shadow 0.2s ease;
60
- box-shadow: 0 4px 14px rgba(88, 166, 255, 0.4);
61
- }
62
- a.button:hover {
63
- transform: translateY(-2px);
64
- box-shadow: 0 6px 20px rgba(88, 166, 255, 0.6);
65
- }
66
- .button-container {
67
- display: flex;
68
- justify-content: center;
69
- gap: 25px;
70
- margin-top: 20px;
71
- }
72
-
73
- footer {
74
- text-align: center;
75
- font-size: 0.85em;
76
- color: #8b949e;
77
- margin: 40px 0 20px;
78
- }
79
- /* ๋งํฌ ์ƒ‰์ƒ */
80
- a {
81
- color: #58a6ff;
82
- }
83
- a:hover {
84
- text-decoration: underline;
85
- }
86
- </style>
87
- </head>
88
- <body>
89
- <header>
90
- <h1>PersonaChatEngine AI Server</h1>
91
- <p>๊ฒŒ์ž„ ๋‚ด NPC ๋Œ€ํ™” ์—”์ง„ API</p>
92
- </header>
93
- <main>
94
- {{ readme_content|safe }}
95
- <div class="button-container">
96
- <a class="button" href="/docs" target="_blank">๐Ÿš€ API ํ…Œ์ŠคํŠธํ•˜๊ธฐ</a>
97
- <a class="button" href="https://huggingface.co/spaces/m97j/PersonaChatEngine_ai_server/blob/main/README.md" target="_blank">๐Ÿ“„ ์ƒ์„ธ ๋ฌธ์„œ ๋ณด๊ธฐ</a>
98
- </div>
99
- </main>
100
- <footer>
101
- &copy; 2025 PersonaChatEngine Project
102
- </footer>
103
- </body>
104
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
  <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>CWIE Symbolic Processor</title>
7
+ <style>
8
+ /* dark mode default styles */
9
+ body {
10
+ font-family: 'Segoe UI', Arial, sans-serif;
11
+ margin: 0;
12
+ background-color: #0d1117;
13
+ color: #e6edf3;
14
+ }
15
+
16
+ header {
17
+ background-color: #161b22;
18
+ padding: 20px 40px;
19
+ text-align: center;
20
+ border-bottom: 1px solid #30363d;
21
+ }
22
+
23
+ header h1 {
24
+ margin: 0;
25
+ font-size: 1.8em;
26
+ background: linear-gradient(90deg, #58a6ff, #a371f7);
27
+ -webkit-background-clip: text;
28
+ -webkit-text-fill-color: transparent;
29
+ }
30
+
31
+ header p {
32
+ margin: 5px 0 0;
33
+ color: #8b949e;
34
+ }
35
+
36
+ main {
37
+ max-width: 900px;
38
+ margin: 40px auto;
39
+ background-color: #161b22;
40
+ padding: 30px;
41
+ border-radius: 12px;
42
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
43
+ border: 1px solid #30363d;
44
+ }
45
+
46
+ h1,
47
+ h2,
48
+ h3 {
49
+ color: #58a6ff;
50
+ }
51
+
52
+ p,
53
+ ul {
54
+ line-height: 1.6;
55
+ }
56
+
57
+ ul {
58
+ padding-left: 20px;
59
+ }
60
+
61
+ /* button styles */
62
+ a.button {
63
+ display: inline-block;
64
+ margin: 15px 10px 0 0;
65
+ padding: 12px 20px;
66
+ background: linear-gradient(135deg, #58a6ff, #a371f7);
67
+ color: white;
68
+ text-decoration: none;
69
+ border-radius: 8px;
70
+ font-weight: bold;
71
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
72
+ box-shadow: 0 4px 14px rgba(88, 166, 255, 0.4);
73
+ }
74
+
75
+ a.button:hover {
76
+ transform: translateY(-2px);
77
+ box-shadow: 0 6px 20px rgba(88, 166, 255, 0.6);
78
+ }
79
+
80
+ .button-container {
81
+ display: flex;
82
+ justify-content: center;
83
+ gap: 25px;
84
+ margin-top: 20px;
85
+ }
86
+
87
+ footer {
88
+ text-align: center;
89
+ font-size: 0.85em;
90
+ color: #8b949e;
91
+ margin: 40px 0 20px;
92
+ }
93
+
94
+ /* link colors */
95
+ a {
96
+ color: #58a6ff;
97
+ }
98
+
99
+ a:hover {
100
+ text-decoration: underline;
101
+ }
102
+ </style>
103
+ </head>
104
+
105
+ <body>
106
+ <header>
107
+ <h1>CWIE Symbolic Processor</h1>
108
+ <p>A symbolic processing engine for handling complex logical operations</p>
109
+ </header>
110
+ <main>
111
+ {{ readme_content|safe }}
112
+ <div class="button-container">
113
+ <a class="button" href="/docs" target="_blank">๐Ÿš€ API ํ…Œ์ŠคํŠธํ•˜๊ธฐ</a>
114
+ <a class="button" href="https://huggingface.co/spaces/m97j/symbolic-processor/blob/main/README.md"
115
+ target="_blank">๐Ÿ“„ ์ƒ์„ธ ๋ฌธ์„œ ๋ณด๊ธฐ</a>
116
+ </div>
117
+ </main>
118
+ <footer>
119
+ &copy; 2025-2026 CWIE Project
120
+ </footer>
121
+ </body>
122
+
123
+ </html>