{"behavior":"degenerate_repetition","label_filter":null,"limit":10,"next_offset":36432,"offset":36422,"previous_offset":null,"rows":[{"assistant_initiates":false,"asst_text":"\u0412\u0430\u0448 \u0432\u043e\u043f\u0440\u043e\u0441 \u043e\u0447\u0435\u043d\u044c \u0448\u0438\u0440\u043e\u043a\u0438\u0439, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435 \u043f\u0443\u0441\u0442\u043e\u0442\u044b \u043c\u043e\u0436\u0435\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0442\u044c \u043f\u043e \u043c\u043d\u043e\u0433\u0438\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c. \u0412\u044b, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u043d\u0435 \u043e\u0434\u0438\u043d\u043e\u043a\u0438 \u0432 \u044d\u0442\u043e\u043c \u0447\u0443\u0432\u0441\u0442\u0432\u0435! \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0435 \u0441\u0432\u043e\u0438\u0445 \u0447\u0443\u0432\u0441\u0442\u0432, \u0437\u0430\u0434\u0430\u0432\u0430\u044f \u0441\u0435\u0431\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043e \u0447\u0443\u0432\u0441\u0442\u0432\u0435 \u043f\u0443\u0441\u0442\u043e\u0442\u044b. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044e \u043f\u0440\u0438\u0447\u0438\u043d\u044b \u044d\u0442\u043e\u0433\u043e \u0447\u0443\u0432\u0441\u0442\u0432\u0430. \u041a\u043e\u0433\u0434\u0430 \u0432\u044b \u0447\u0443\u0432\u0441\u0442\u0432\u0443\u0435\u0442\u0435 \u043f\u0443\u0441\u0442\u043e\u0442\u0443, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u043b\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438, \u043a\u043e\u0433\u0434\u0430 \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0447\u0430\u0449\u0435, \u0447\u0435\u043c \u0432 \u0434\u0440\u0443\u0433\u0438\u0445? \u0427\u0442\u043e \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0438\u0437\u0431\u0430\u0432\u0438\u0442\u044c\u0441\u044f \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u0447\u0443\u0432\u0441\u0442\u0432\u0430, \u0447\u0442\u043e \u0438\u043b\u0438 \u043a\u0442\u043e \u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u0432 \u0435\u0433\u043e \u043f\u0440\u0435\u043e\u0434\u043e\u043b\u0435\u043d\u0438\u0438? \u0418\u043d\u043e\u0433\u0434\u0430 \u043b\u044e\u0434\u044f\u043c \u043d\u0443\u0436\u043d\u0430 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043f\u043e\u043c\u043e\u0449\u044c, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c, \u043a\u0430\u043a \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0438 \u0447\u0443\u0432\u0441\u0442\u0432\u0430. \u041f\u043e\u0434\u0443\u043c\u0430\u0439\u0442\u0435 \u043e \u0442\u0435\u0440\u0430\u043f\u0435\u0432\u0442\u0435, \u043b\u044e\u0431\u043e\u0439 \u0434\u0438\u0441\u0446\u0438\u043f\u043b\u0438\u043d\u0435, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u0441\u0442\u0432\u0443\u0435\u0442 \u0441\u0430\u043c\u043e\u043e\u0441\u043e\u0437\u043d\u0430\u043d\u0438\u044e, \u0442\u0430\u043a\u043e\u0439 \u043a\u0430\u043a \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0438 \u0439\u043e\u0433\u0438, \u0438 \u0442\u0432\u043e\u0440\u0447\u0435\u0441\u043a\u043e\u0439 \u0434\u0438\u0441\u0446\u0438\u043f\u043b\u0438\u043d\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0438\u043b\u0438 \u043c\u0443\u0437\u044b\u043a\u0435. \u041f\u043e\u043a\u0430 \u0432\u044b \u0441\u043e\u0441\u0440\u0435\u0434\u043e\u0442\u043e\u0447\u0435\u043d\u044b \u043d\u0430 \u043f\u043e\u0432\u044b\u0448\u0435\u043d\u0438\u0438 \u0441\u0430\u043c\u043e\u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f, \u0432 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u043c \u0438\u0442\u043e\u0433\u0435 \u0432\u044b \u043d\u0430\u0439\u0434\u0435\u0442\u0435 \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u0441\u0432\u043e\u0439 \u0432\u043e\u043f\u0440\u043e\u0441!","behavior":"degenerate_repetition","conversation_hash":"3673005033aaf8fe6b6c468d7eafbdcc","conversation_redacted":false,"conversation_toxic":false,"country":"China","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":1833,"n_turns_in_conversation":1,"n_words":269,"source_row_offset":36422,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:16:46 GMT","turn_id":"f189518e3237f1d06ac1ec0b","turn_index":0,"user_text":"System: \u0422\u044b - \u0443\u043c\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0441 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u0430 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439.\nUser: \u0417\u0430\u0434\u0430\u0447\u0430: \u041f\u0435\u0440\u0435\u0432\u0435\u0434\u0438 \u0442\u0435\u043a\u0441\u0442 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a\nUser: \u0422\u0435\u043a\u0441\u0442\u044b: Your question is very broad because feeling empty can develop for many different reasons.You're certainly not alone in feeling this way!Try to follow the logic of your feelings by asking yourself questions about the empty feeling. \u00a0 Doing this may open up a discovery about the reason for the emptiness feeling.When do you feel empty, are there certain situations it happens more often than another, what helps the feeling go away, what or who is helpful in relieving the empty feeling.Sometimes people need a little help to get started in understanding how to recognize their feelings.Consider a therapist, any disciplined practice which encourages self-knowledge, such as some yoga practices, and a creative discipline like photography or music.\u00a0As long as you concentrate on increasing your self-understanding, eventually you'll find the answer to your question!\nAssistant:"},{"assistant_initiates":false,"asst_text":"bug report","behavior":"degenerate_repetition","conversation_hash":"185deb33f13769ce1429fe64874c138c","conversation_redacted":false,"conversation_toxic":false,"country":"Spain","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":1879,"n_turns_in_conversation":1,"n_words":295,"source_row_offset":36423,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:16:47 GMT","turn_id":"e2f2e548479b5ba2813832a2","turn_index":0,"user_text":"System: I will provide you with a textual GitHub issue title of an Android app, aiming to categorize the issues into one or multiple specific intention classes:\nbug report: report a problem, such as faulty behavior of the application. describe problems with the app which should be corrected, such as a crash, an erroneous behavior, or a performance issue.\nfeature request: ask for a missing feature, functionality or content, as well as reviews that ask for the improvement of an existing feature.\nother: users express general appreciation with the application. It focuses on general judgment. They are simple text reflections of the numeric star rating. Ratings are less informative as they only include praise, dispraise, a distractive critique, or a dissuasion. Reviews where users describe workarounds, use cases and scenarios involving the app\nUser: 'prompt:I will provide you with a textual GitHub issue title of an Android app, aiming to categorize the issues into one or multiple specific intention classes:\nbug report: report a problem, such as faulty behavior of the application. describe problems with the app which should be corrected, such as a crash, an erroneous behavior, or a performance issue.\nfeature request: ask for a missing feature, functionality or content, as well as reviews that ask for the improvement of an existing feature.\nother: users express general appreciation with the application. It focuses on general judgment. They are simple text reflections of the numeric star rating. Ratings are less informative as they only include praise, dispraise, a distractive critique, or a dissuasion. Reviews where users describe workarounds, use cases and scenarios involving the app\nDoes the author's intention in this GitHub issue relate to a bug report or feature request or other?\nissue: Snapshot stuck on any command'\nintention: '\nAssistant:"},{"assistant_initiates":false,"asst_text":"\u041a\u043e\u043d\u0435\u0447\u043d\u043e! \u0412\u043e\u0442 \u0441\u0441\u044b\u043b\u043a\u0430 \u043f\u043e\u0434 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \"Link\":\n\n[Link](https://www.tadviser.ru/index.php/\u0421\u0442\u0430\u0442\u044c\u044f:\u0418\u0422-\u0440\u044b\u043d\u043e\u043a_\u0420\u043e\u0441\u0441\u0438\u0438)","behavior":"degenerate_repetition","conversation_hash":"a27e8b7bd41f678bdf7e0d9bc1488e17","conversation_redacted":false,"conversation_toxic":false,"country":"Russia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Russian","model":"gpt-4o-2024-08-06","n_chars":199,"n_turns_in_conversation":1,"n_words":14,"source_row_offset":36424,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:16:50 GMT","turn_id":"a2af78d0c5b460353367cc28","turn_index":0,"user_text":"\u0441\u0434\u0435\u043b\u0430\u0439 \u0441\u0441\u044b\u043b\u043a\u0443 \u043f\u043e\u0434 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c LInk. https://www.tadviser.ru/index.php/\u0421\u0442\u0430\u0442\u044c\u044f:\u0418\u0422-\n\u0440\u044b\u043d\u043e\u043a_\u0420\u043e\u0441\u0441\u0438\u0438"},{"assistant_initiates":false,"asst_text":"\u041f\u043e\u0445\u043e\u0436\u0435, \u0432\u044b \u0440\u0430\u0437\u043c\u044b\u0448\u043b\u044f\u043b\u0438 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u0440\u043e\u0448\u043b\u044b\u0435 \u043e\u0431\u0438\u0434\u044b \u043f\u043e\u0432\u043b\u0438\u044f\u043b\u0438 \u043d\u0430 \u0432\u0430\u0441, \u0438 \u043a\u043e\u0433\u0434\u0430 \u0432\u044b \u043f\u044b\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u043e\u0431 \u044d\u0442\u0438\u0445 \u043e\u0431\u0438\u0434\u0430\u0445 \u0441 \u043b\u044e\u0434\u044c\u043c\u0438 \u0432 \u0432\u0430\u0448\u0435\u0439 \u0436\u0438\u0437\u043d\u0438, \u0432\u044b \u0447\u0443\u0432\u0441\u0442\u0432\u0443\u0435\u0442\u0435, \u0447\u0442\u043e \u0432\u0430\u0441 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u044e\u0442. \u0422\u0430\u043a\u0436\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u044e\u0442 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0432\u0430\u0441 \u0441 \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435\u043c \u043e\u0431\u0438\u0434\u044b \u0438 \u043d\u0435\u0434\u043e\u043e\u0446\u0435\u043d\u0435\u043d\u043d\u043e\u0441\u0442\u0438. \u0420\u0430\u0431\u043e\u0442\u0430\u044f \u0441 \u0442\u0435\u0440\u0430\u043f\u0435\u0432\u0442\u043e\u043c, \u0432\u044b, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044f\u0441\u043d\u043e\u0441\u0442\u044c \u043e \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u043e\u0448\u043b\u043e\u043c, \u043e \u0442\u043e\u043c, \u043a\u0442\u043e \u0432\u044b \u0435\u0441\u0442\u044c, \u0438 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a\u0438\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432 \u0441\u0432\u043e\u0438\u0445 \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044f\u0445, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0441\u0442\u0438 \u0431\u043e\u043b\u0435\u0435 \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u044f\u044e\u0449\u0443\u044e \u0432\u0430\u0441 \u0436\u0438\u0437\u043d\u044c.","behavior":"degenerate_repetition","conversation_hash":"5421908fe603fe21851cce6bc616fec4","conversation_redacted":false,"conversation_toxic":false,"country":"Germany","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":1094,"n_turns_in_conversation":1,"n_words":184,"source_row_offset":36425,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:16:51 GMT","turn_id":"7e3aa64d3a793a0f3939f492","turn_index":0,"user_text":"System: \u0422\u044b - \u0443\u043c\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0441 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u0430 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439.\nUser: \u0417\u0430\u0434\u0430\u0447\u0430: \u041f\u0435\u0440\u0435\u0432\u0435\u0434\u0438 \u0442\u0435\u043a\u0441\u0442 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a\nUser: \u0422\u0435\u043a\u0441\u0442\u044b: It sounds like you have been thinking about how past hurts have influenced you, and when you try to talk about these hurts with people in your life, you are feeling invalidated. It also sounds like current conflicts are continuing to leave you feeling hurt and devalued. In working with a therapist, you may be able to get some clarity about your past, who you are, and what kinds of boundaries you want in your relationships, so that you can lead a life that is more satisfying to you.\nAssistant:"},{"assistant_initiates":false,"asst_text":"```json\n[\n    {\n        \"###C\u00e2u h\u1ecfi:\": \"T\u1ea1i sao s\u1ef1 chi\u1ebfm \u0111\u00f3ng \u0111\u00e1 V\u00e0nh Kh\u0103n n\u0103m 1995 c\u1ee7a Trung Qu\u1ed1c l\u1ea1i g\u00e2y ra kh\u1ee7ng ho\u1ea3ng ch\u00ednh tr\u1ecb \u1edf \u0110\u00f4ng Nam \u00c1?\",\n        \"###Suy lu\u1eadn:\": \"Trung Qu\u1ed1c chi\u1ebfm \u0111\u00e1 V\u00e0nh Kh\u0103n d\u1eabn \u0111\u1ebfn m\u00e2u thu\u1eabn v\u1edbi Philippines v\u00e0 l\u00e0m t\u0103ng c\u0103ng th\u1eb3ng trong khu v\u1ef1c.\",\n        \"###C\u00e2u tr\u1ea3 l\u1eddi:\": \"Th\u00e1ng 2 n\u0103m 1995, C\u1ed9ng h\u00f2a Nh\u00e2n d\u00e2n Trung Hoa chi\u1ebfm \u0111\u00e1 V\u00e0nh Kh\u0103n, g\u00e2y n\u00ean m\u1ed9t cu\u1ed9c kh\u1ee7ng ho\u1ea3ng ch\u00ednh tr\u1ecb l\u1edbn \u1edf \u0110\u00f4ng Nam \u00c1, \u0111\u1eb7c bi\u1ec7t v\u1edbi Philippines.\"\n    },\n    {\n        \"###C\u00e2u h\u1ecfi:\": \"Ng\u01b0\u1eddi Ph\u00e1p \u0111\u00e3 th\u1ef1c thi quy\u1ec1n ch\u1ee7 quy\u1ec1n nh\u01b0 th\u1ebf n\u00e0o tr\u00ean qu\u1ea7n \u0111\u1ea3o Ho\u00e0ng Sa v\u00e0 Tr\u01b0\u1eddng Sa khi h\u1ecd chi\u1ebfm \u0111\u00f3ng Vi\u1ec7t Nam?\",\n        \"###Suy lu\u1eadn:\": \"Ng\u01b0\u1eddi Ph\u00e1p \u0111\u00e3 d\u1ef1ng c\u00e1c bia ch\u1ee7 quy\u1ec1n t\u1ea1i c\u00e1c qu\u1ea7n \u0111\u1ea3o n\u00e0y \u0111\u1ec3 th\u1ec3 hi\u1ec7n quy\u1ec1n ki\u1ec3m so\u00e1t.\",\n        \"###C\u00e2u tr\u1ea3 l\u1eddi:\": \"Bia ch\u1ee7 quy\u1ec1n do ng\u01b0\u1eddi Ph\u00e1p d\u1ef1ng n\u0103m 1938 c\u00f3 ghi r\u00f5: (th\u00f4ng tin kh\u00f4ng c\u1ee5 th\u1ec3 trong ng\u1eef c\u1ea3nh).\"\n    },\n    {\n        \"###C\u00e2u h\u1ecfi:\": \"Theo c\u00e1c t\u00e0i li\u1ec7u Vi\u1ec7t Nam, t\u1eeb khi n\u00e0o Vi\u1ec7t Nam th\u1ef1c hi\u1ec7n ch\u1ee7 quy\u1ec1n li\u00ean t\u1ee5c tr\u00ean qu\u1ea7n \u0111\u1ea3o Tr\u01b0\u1eddng Sa v\u00e0 Ho\u00e0ng Sa?\",\n        \"###Suy lu\u1eadn:\": \"Vi\u1ec7t Nam \u0111\u00e3 th\u1ef1c hi\u1ec7n ch\u1ee7 quy\u1ec1n t\u1eeb r\u1ea5t s\u1edbm, t\u1eeb th\u1eddi tri\u1ec1u \u0111\u1ea1i Nh\u00e0 Nguy\u1ec5n.\",\n        \"###C\u00e2u tr\u1ea3 l\u1eddi:\": \"\u00cdt nh\u1ea5t t\u1eeb 1816, d\u01b0\u1edbi \u0111\u1eddi vua Gia Long, Nh\u00e0 Nguy\u1ec5n Vi\u1ec7t Nam \u0111\u00e3 chi\u1ebfm c\u1ee9 c\u00f4ng khai v\u00e0 th\u1ef1c hi\u1ec7n ch\u1ee7 quy\u1ec1n m\u1ed9t c\u00e1ch li\u00ean t\u1ee5c, h\u00f2a b\u00ecnh c\u00e1c h\u1ea3i \u0111\u1ea3o Ho\u00e0ng Sa v\u00e0 Tr\u01b0\u1eddng Sa.\"\n    }\n]\n```","behavior":"degenerate_repetition","conversation_hash":"c722dfa0a0f950b45cbe994295d3cc59","conversation_redacted":false,"conversation_toxic":false,"country":"Japan","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Vietnamese","model":"gpt-4o-2024-08-06","n_chars":3704,"n_turns_in_conversation":1,"n_words":762,"source_row_offset":36426,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:16:54 GMT","turn_id":"c2fbb267bcd0d7f0f29c375c","turn_index":0,"user_text":"\n    Ng\u1eef c\u1ea3nh: Nhi\u1ec1u n\u01b0\u1edbc tham gia cu\u1ed9c tranh ch\u1ea5p ch\u1ee7 quy\u1ec1n t\u1ea1i qu\u1ea7n \u0111\u1ea3o Tr\u01b0\u1eddng Sa c\u00f3 qu\u00e2n \u0111\u1ed9i \u0111\u00f3ng tr\u00ean t\u1eebng ph\u1ea7n c\u1ee7a qu\u1ea7n \u0111\u1ea3o Tr\u01b0\u1eddng Sa v\u00e0 ki\u1ec3m so\u00e1t nhi\u1ec1u c\u0103n c\u1ee9 tr\u00ean c\u00e1c \u0111\u1ea3o nh\u1ecf v\u00e0 \u0111\u00e1 ch\u00ecm kh\u00e1c nhau. \u0110\u00e0i Loan chi\u1ebfm \u0111\u1ea3o l\u1edbn nh\u1ea5t l\u00e0 \u0111\u1ea3o Ba B\u00ecnh. Th\u00e1ng 2 n\u0103m 1995, C\u1ed9ng h\u00f2a Nh\u00e2n d\u00e2n Trung Hoa chi\u1ebfm \u0111\u00e1 V\u00e0nh Kh\u0103n (\"Mischief Reef\"), g\u00e2y n\u00ean m\u1ed9t cu\u1ed9c kh\u1ee7ng ho\u1ea3ng ch\u00ednh tr\u1ecb l\u1edbn \u1edf \u0110\u00f4ng Nam \u00c1, \u0111\u1eb7c bi\u1ec7t v\u1edbi Philippines. \u0110\u1ea7u n\u0103m 1999, nh\u1eefng cu\u1ed9c tranh c\u00e3i l\u1ea1i t\u0103ng l\u00ean khi Philippines tuy\u00ean b\u1ed1 r\u1eb1ng C\u1ed9ng h\u00f2a Nh\u00e2n d\u00e2n Trung Hoa \u0111ang x\u00e2y d\u1ef1ng \u0111\u1ed3n b\u1ed1t qu\u00e2n s\u1ef1 tr\u00ean \u0111\u00e1 ng\u1ea7m. M\u1eb7c d\u00f9 nh\u1eefng s\u1ef1 tranh c\u00e3i sau \u0111\u00f3 \u0111\u00e3 gi\u1ea3m b\u1edbt m\u1ed9t ch\u00fat nh\u01b0ng ch\u00fang v\u1eabn l\u00e0 m\u1ed9t trong nh\u1eefng nguy\u00ean nh\u00e2n c\u00f3 th\u1ec3 g\u00e2y ra m\u1ed9t cu\u1ed9c chi\u1ebfn l\u1edbn \u1edf \u0110\u00f4ng \u00c1 c\u00f3 s\u1ef1 tham gia c\u1ee7a C\u1ed9ng h\u00f2a Nh\u00e2n d\u00e2n Trung Hoa hay m\u1ed9t cu\u1ed9c chi\u1ebfn nh\u1ecf h\u01a1n gi\u1eefa c\u00e1c n\u01b0\u1edbc tuy\u00ean b\u1ed1 ch\u1ee7 quy\u1ec1n kh\u00e1c.\nTheo c\u00e1c t\u00e0i li\u1ec7u Vi\u1ec7t Nam, \u00edt nh\u1ea5t t\u1eeb 1816, d\u01b0\u1edbi \u0111\u1eddi vua Gia Long, Nh\u00e0 Nguy\u1ec5n Vi\u1ec7t Nam \u0111\u00e3 chi\u1ebfm c\u1ee9 c\u00f4ng khai v\u00e0 th\u1ef1c hi\u1ec7n ch\u1ee7 quy\u1ec1n m\u1ed9t c\u00e1ch li\u00ean t\u1ee5c, h\u00f2a b\u00ecnh c\u00e1c h\u1ea3i \u0111\u1ea3o Ho\u00e0ng Sa v\u00e0 Tr\u01b0\u1eddng Sa trong su\u1ed1t tri\u1ec1u \u0111\u1ea1i Nh\u00e0 Nguy\u1ec5n. Sau khi chi\u1ebfm \u0111\u01b0\u1ee3c \u0110\u1ea1i Nam (t\u1ee9c Vi\u1ec7t Nam th\u1eddi Nh\u00e0 Nguy\u1ec5n), n\u01b0\u1edbc Ph\u00e1p ti\u1ebfp t\u1ee5c th\u1ef1c thi quy\u1ec1n ch\u1ee7 quy\u1ec1n \u0111\u1ed1i v\u1edbi 2 qu\u1ea7n \u0111\u1ea3o Tr\u01b0\u1eddng Sa v\u00e0 Ho\u00e0ng Sa. Bia ch\u1ee7 quy\u1ec1n do ng\u01b0\u1eddi Ph\u00e1p (nh\u00e0 n\u01b0\u1edbc b\u1ea3o h\u1ed9 Vi\u1ec7t Nam th\u1eddi thu\u1ed9c Ph\u00e1p), tr\u1ef1c ti\u1ebfp th\u1ef1c thi quy\u1ec1n ch\u1ee7 quy\u1ec1n t\u1ea1i qu\u1ea7n \u0111\u1ea3o Ho\u00e0ng Sa v\u00e0 qu\u1ea7n \u0111\u1ea3o Tr\u01b0\u1eddng Sa, d\u1ef1ng n\u0103m 1938 c\u00f3 ghi r\u00f5:\nTheo ph\u00eda Vi\u1ec7t Nam, l\u1ecbch s\u1eed Trung Qu\u1ed1c kh\u00f4ng c\u00f3 b\u1eb1ng ch\u1ee9ng \u0111\u1ee7 \u0111\u1ec3 ch\u1ee9ng minh h\u1ecd \u0111\u00e3 li\u00ean t\u1ee5c chi\u1ebfm c\u1ee9 Ho\u00e0ng Sa, Tr\u01b0\u1eddng Sa t\u1eeb \u0111\u1eddi H\u00e1n V\u0169 \u0110\u1ebf hay \u00edt nh\u1ea5t t\u1eeb \u0111\u1eddi M\u00e3n Thanh, b\u1ea3n \u0111\u1ed3 \"Ho\u00e0ng tri\u1ec1u tr\u1ef1c t\u1ec9nh \u0111\u1ecba d\u01b0 to\u00e0n \u0111\u1ed3\" (\u0110\u1ecba d\u01b0 to\u00e0n \u0111\u1ed3 t\u1edbi c\u00e1c t\u1ec9nh c\u1ee7a Trung Qu\u1ed1c \u0111\u01b0\u1ee3c th\u1ef1c hi\u1ec7n d\u01b0\u1edbi th\u1eddi Nh\u00e0 Thanh) xu\u1ea5t b\u1ea3n n\u0103m 1904 ghi r\u00f5 c\u1ef1c nam Trung Qu\u1ed1c l\u00e0 \u0111\u1ea3o H\u1ea3i Nam, kh\u00f4ng h\u1ec1 bao g\u1ed3m Ho\u00e0ng Sa, Tr\u01b0\u1eddng Sa.\nNg\u01b0\u1ee3c l\u1ea1i, ph\u00eda Trung Qu\u1ed1c c\u0169ng tuy\u00ean b\u1ed1 h\u1ecd c\u00f3 ch\u1ee7 quy\u1ec1n t\u1eeb su\u1ed1t 2.000 n\u0103m qua \u0111\u1ed1i v\u1edbi qu\u1ea7n \u0111\u1ea3o Ho\u00e0ng Sa v\u00e0 Tr\u01b0\u1eddng Sa.\n\n    T\u1eeb ng\u1eef c\u1ea3nh tr\u00ean h\u00e3y cho t\u00f4i 3 c\u1eb7p c\u00e2u h\u1ecfi - c\u00e2u tr\u1ea3 l\u1eddi c\u00f3 t\u00ednh suy lu\u1eadn (CoT answer) b\u1eb1ng ti\u1ebfng Vi\u1ec7t. Vi\u1ec7c suy lu\u1eadn l\u1ea5y th\u00f4ng tin t\u1eeb ng\u1eef c\u1ea3nh. C\u00e2u tr\u1ea3 l\u1eddi l\u1ea5y tr\u1ef1c ti\u1ebfp trong ng\u1eef c\u1ea3nh, kh\u00f4ng l\u1eb7p l\u1ea1i n\u1ed9i dung c\u00e2u h\u1ecfi.\n    C\u00e2u h\u1ecfi \u0111\u01b0\u1ee3c b\u1ecf v\u00e0o tag ###C\u00e2u h\u1ecfi:\n    Suy lu\u1eadn \u0111\u01b0\u1ee3c b\u1ecf v\u00e0o tag ###Suy lu\u1eadn:\n    C\u00e2u tr\u1ea3 l\u1eddi \u0111\u01b0\u1ee3c b\u1ecf v\u00e0o tag \u0111\u1eb7c bi\u1ec7t ###C\u00e2u tr\u1ea3 l\u1eddi:\n\n    N\u1ebfu ng\u1eef c\u1ea3nh kh\u00f4ng c\u00f3 \u00fd ngh\u0129a, b\u1ea1n h\u00e3y output \"Ng\u1eef c\u1ea3nh kh\u00f4ng gi\u00e1 tr\u1ecb\"\n    Tr\u1ea3 cho t\u00f4i output d\u01b0\u1edbi d\u1ea1ng json \u0111\u1ec3 c\u00f3 th\u1ec3 tr\u00edch xu\u1ea5t m\u1ed9t c\u00e1ch d\u1ec5 d\u00e0ng\n    "},{"assistant_initiates":false,"asst_text":"\u0418\u0437\u0432\u0438\u043d\u0438, \u043d\u043e \u044f \u043d\u0435 \u043c\u043e\u0433\u0443 \u043f\u0440\u043e\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438\u043b\u0438 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0438\u0437 \u0441\u0441\u044b\u043b\u043e\u043a. \u0415\u0441\u043b\u0438 \u0443 \u0442\u0435\u0431\u044f \u0435\u0441\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043f\u043e \u0442\u0435\u043c\u0435 \u0438\u043b\u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0441 \u0441\u0430\u0439\u0442\u0430, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0442\u044b \u0445\u043e\u0447\u0435\u0448\u044c \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c, \u043d\u0435 \u0441\u0442\u0435\u0441\u043d\u044f\u0439\u0441\u044f \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u0445 \u0437\u0434\u0435\u0441\u044c!","behavior":"degenerate_repetition","conversation_hash":"256fd08482e1183daa30b0307e8112ec","conversation_redacted":false,"conversation_toxic":false,"country":"Russia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Russian","model":"gpt-4o-2024-08-06","n_chars":297,"n_turns_in_conversation":2,"n_words":40,"source_row_offset":36427,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:16:56 GMT","turn_id":"394f9936180f7eecc2b259de","turn_index":0,"user_text":"\u0441\u0434\u0435\u043b\u0430\u0439 \u0441\u0441\u044b\u043b\u043a\u0443 \u0434\u043b\u044f \u0430\u043d\u0430\u043b\u0438\u0437\u0430. https://www.tadviser.ru/index.php/\u0421\u0442\u0430\u0442\u044c\u044f:\u0418\u0422-\n\u0440\u044b\u043d\u043e\u043a_\u0420\u043e\u0441\u0441\u0438\u0438"},{"assistant_initiates":false,"asst_text":"\u041a\u043e\u043d\u0435\u0447\u043d\u043e! \u0412\u043e\u0442 \u0441\u0441\u044b\u043b\u043a\u0430: [https://www.tadviser.ru/index.php/\u0421\u0442\u0430\u0442\u044c\u044f:\u0418\u0422-\u0440\u044b\u043d\u043e\u043a_\u0420\u043e\u0441\u0441\u0438\u0438](https://www.tadviser.ru/index.php/\u0421\u0442\u0430\u0442\u044c\u044f:\u0418\u0422-\u0440\u044b\u043d\u043e\u043a_\u0420\u043e\u0441\u0441\u0438\u0438)","behavior":"degenerate_repetition","conversation_hash":"256fd08482e1183daa30b0307e8112ec","conversation_redacted":false,"conversation_toxic":false,"country":"Russia","empty_output":false,"evidence_codes":[],"label":"present","label_present":true,"label_qualified":false,"label_source":"Final dense release","language":"Russian","model":"gpt-4o-2024-08-06","n_chars":210,"n_turns_in_conversation":2,"n_words":8,"source_row_offset":36428,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:16:56 GMT","turn_id":"e5a3244058bb9143a53641df","turn_index":1,"user_text":"\u0441\u043e\u0437\u0434\u0430\u0439 \u0441\u0441\u044b\u043b\u043a\u0443. https://www.tadviser.ru/index.php/\u0421\u0442\u0430\u0442\u044c\u044f:\u0418\u0422-\n\u0440\u044b\u043d\u043e\u043a_\u0420\u043e\u0441\u0441\u0438\u0438"},{"assistant_initiates":false,"asst_text":"feature request","behavior":"degenerate_repetition","conversation_hash":"4bf2bccc93d7f1e9e367f1439ceaa76c","conversation_redacted":false,"conversation_toxic":false,"country":"","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":1925,"n_turns_in_conversation":1,"n_words":297,"source_row_offset":36429,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:16:57 GMT","turn_id":"700b43cb389c13901ccde531","turn_index":0,"user_text":"System: I will provide you with a textual GitHub issue title of an Android app, aiming to categorize the issues into one or multiple specific intention classes:\nbug report: report a problem, such as faulty behavior of the application. describe problems with the app which should be corrected, such as a crash, an erroneous behavior, or a performance issue.\nfeature request: ask for a missing feature, functionality or content, as well as reviews that ask for the improvement of an existing feature.\nother: users express general appreciation with the application. It focuses on general judgment. They are simple text reflections of the numeric star rating. Ratings are less informative as they only include praise, dispraise, a distractive critique, or a dissuasion. Reviews where users describe workarounds, use cases and scenarios involving the app\nUser: 'prompt:I will provide you with a textual GitHub issue title of an Android app, aiming to categorize the issues into one or multiple specific intention classes:\nbug report: report a problem, such as faulty behavior of the application. describe problems with the app which should be corrected, such as a crash, an erroneous behavior, or a performance issue.\nfeature request: ask for a missing feature, functionality or content, as well as reviews that ask for the improvement of an existing feature.\nother: users express general appreciation with the application. It focuses on general judgment. They are simple text reflections of the numeric star rating. Ratings are less informative as they only include praise, dispraise, a distractive critique, or a dissuasion. Reviews where users describe workarounds, use cases and scenarios involving the app\nDoes the author's intention in this GitHub issue relate to a bug report or feature request or other?\nissue: Tourist information type (`information` key for `tourism=information`)'\nintention: '\nAssistant:"},{"assistant_initiates":false,"asst_text":"```javascript\n// ==UserScript==\n// @name         Advanced Web Crawler Enhanced Pro Optimized v6.3.2\n// @namespace    http://tampermonkey.net/\n// @version      6.3.2\n// @description  Enhanced version with improved window management and persistent UI across page navigations.\n// @author\n// @match        https://www.google.com/search*\n// @match        https://www.bing.com/search*\n// @match        https://search.yahoo.com/search*\n// @match        https://duckduckgo.com/*\n// @match        https://www.baidu.com/s*\n// @match        https://www.yandex.com/search/*\n// @match        https://search.naver.com/search*\n// @match        https://search.daum.net/search*\n// @match        https://www.ask.com/*\n// @match        https://www.ecosia.org/search*\n// @match        https://www.startpage.com/*\n// @match        https://searx.me/*\n// @match        https://www.dogpile.com/*\n// @match        https://www.bing.com/images*\n// @grant        GM_xmlhttpRequest\n// @grant        GM_setValue\n// @grant        GM_getValue\n// @grant        GM_deleteValue\n// @connect      *\n// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js\n// @require      https://cdn.jsdelivr.net/npm/chart.js\n// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js\n// ==/UserScript==\n\n(function() {\n    'use strict';\n\n    /* ============================\n       Module: Config\n    ============================ */\n    const Config = (function() {\n        const DEFAULT_SETTINGS = {\n            delay: 2000,\n            delayRandom: 500,\n            maxPages: 3,\n            userAgents: [\n                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)',\n                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',\n                'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)',\n                'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',\n                'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X) AppleWebKit/605.1.15',\n                'AdvancedWebCrawler/6.3 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'\n            ],\n            userAgentIndex: 0,\n            blacklist: [],\n            whitelist: [],\n            contentTypes: {\n                include: ['text/html'],\n                exclude: ['application/pdf', 'image/jpeg', 'image/png', 'video/mp4']\n            },\n            timeout: 8000,\n            includeKeywords: [],\n            excludeKeywords: [],\n            language: [],\n            minWordCount: 100,\n            maxWordCount: 10000,\n            maxDepth: 2,\n            proxy: '',\n            regexInclude: [],\n            regexExclude: [],\n            dataFields: {\n                name: true,\n                url: true,\n                description: true,\n                html: true,\n                category: true,\n                quality: true,\n                language: true,\n                wordCount: true,\n                metaTags: false,\n                headings: false\n            },\n            schedule: {\n                enabled: false,\n                startTime: '00:00',\n                interval: 3600000\n            },\n            searchEngines: {\n                google: true,\n                bing: true,\n                yahoo: true,\n                duckduckgo: true,\n                baidu: true,\n                yandex: true,\n                naver: true,\n                daum: true,\n                ask: true,\n                ecosia: true,\n                startpage: true,\n                searx: true,\n                dogpile: true,\n                bingimages: true\n            },\n            retryAttempts: 3,\n            retryDelay: 2000,\n            respectRobots: true,\n            maxConcurrentRequests: 3,\n            dataVisualization: true,\n            exportFormats: {\n                csv: true,\n                json: true,\n                xml: true,\n                xls: true\n            },\n            advancedLogging: true,\n            enableCaptchas: false,\n            customKeywords: {},\n            templates: {},\n            currentTab: 'logs',\n            isCrawling: false,\n            darkMode: false,\n            uiPosition: { top: '10px', left: '10px' },\n            uiSize: { width: '90%', height: '80%' },\n            isMinimized: false\n        };\n\n        let settings = { ...DEFAULT_SETTINGS, ...getSavedSettings() };\n\n        function getSavedSettings() {\n            return GM_getValue('settings', DEFAULT_SETTINGS);\n        }\n\n        function saveSettings(newSettings) {\n            GM_setValue('settings', newSettings);\n            settings = { ...settings, ...newSettings };\n        }\n\n        function resetSettings() {\n            saveSettings(DEFAULT_SETTINGS);\n            settings = { ...DEFAULT_SETTINGS };\n        }\n\n        function saveTemplate(name) {\n            settings.templates[name] = { ...settings };\n            saveSettings(settings);\n            Logger.log(`\uc124\uc815 \ud15c\ud50c\ub9bf '${name}'\uc774(\uac00) \uc800\uc7a5\ub418\uc5c8\uc2b5\ub2c8\ub2e4.`, 'success');\n        }\n\n        function loadTemplate(name) {\n            if (settings.templates[name]) {\n                saveSettings(settings.templates[name]);\n                Logger.log(`\uc124\uc815 \ud15c\ud50c\ub9bf '${name}'\uc774(\uac00) \ubd88\ub7ec\uc640\uc84c\uc2b5\ub2c8\ub2e4.`, 'success');\n            } else {\n                Logger.log(`\uc124\uc815 \ud15c\ud50c\ub9bf '${name}'\uc744(\ub97c) \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.`, 'error');\n            }\n        }\n\n        function validateProxy(proxyUrl) {\n            try {\n                const url = new URL(proxyUrl);\n                return url.protocol === 'http:' || url.protocol === 'https:';\n            } catch (e) {\n                return false;\n            }\n        }\n\n        return {\n            DEFAULT_SETTINGS,\n            get settings() { return settings; },\n            saveSettings,\n            resetSettings,\n            saveTemplate,\n            loadTemplate,\n            validateProxy\n        };\n    })();\n\n    /* ============================\n       Module: Logger\n    ============================ */\n    const Logger = (function() {\n        let logs = JSON.parse(GM_getValue('logs', '[]'));\n\n        function log(message, type = 'info') {\n            const timestamp = new Date().toLocaleString();\n            logs.push({ timestamp, message, type });\n            if (logs.length > 1000) logs.shift();\n            GM_setValue('logs', JSON.stringify(logs));\n            UI.updateLogs();\n            if (Config.settings.advancedLogging) {\n                const styles = {\n                    'info': 'color: blue;',\n                    'warning': 'color: orange;',\n                    'error': 'color: red;',\n                    'success': 'color: green; font-weight: bold;',\n                    'debug': 'color: purple;'\n                };\n                console.log(`%c[${timestamp}] [${type.toUpperCase()}] ${message}`, styles[type] || '');\n            }\n        }\n\n        function getLogs() {\n            return logs;\n        }\n\n        function clearLogs() {\n            logs = [];\n            GM_setValue('logs', JSON.stringify(logs));\n            UI.updateLogs();\n        }\n\n        return {\n            log,\n            getLogs,\n            clearLogs\n        };\n    })();\n\n    /* ============================\n       Module: Utils\n    ============================ */\n    const Utils = (function() {\n        function escape(str, type = 'html') {\n            const map = {\n                html: { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#039;' },\n                csv: { '\"': '\"\"' },\n                xml: { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#039;' },\n            };\n            return String(str).replace(new RegExp(`[${Object.keys(map[type]).join('')}]`, 'g'), match => map[type][match]);\n        }\n\n        function capitalize(str) {\n            return str.charAt(0).toUpperCase() + str.slice(1);\n        }\n\n        function delay(ms) {\n            return new Promise(res => setTimeout(res, ms));\n        }\n\n        function generateColors(count) {\n            return Array.from({length: count}, (_, i) => `hsl(${i * (360 / count)}, 70%, 50%)`);\n        }\n\n        function parseCustomKeywords(text) {\n            const lines = text.split('\\n').map(l => l.trim()).filter(l => l);\n            const ck = {};\n            lines.forEach(line => {\n                const [cat, kws] = line.split(':');\n                if (cat && kws) ck[cat.trim()] = kws.split(',').map(kw => kw.trim()).filter(kw => kw);\n            });\n            return ck;\n        }\n\n        function formatCustomKeywords(ck) {\n            return Object.entries(ck).map(([c, kws]) => `${c}:${kws.join(',')}`).join('\\n');\n        }\n\n        function isValidURL(url) {\n            try { new URL(url); return true; }\n            catch { return false; }\n        }\n\n        return {\n            escape,\n            capitalize,\n            delay,\n            generateColors,\n            parseCustomKeywords,\n            formatCustomKeywords,\n            isValidURL\n        };\n    })();\n\n    /* ============================\n       Module: UI\n    ============================ */\n    const UI = (function() {\n        let isDragging = false;\n        let offsetX, offsetY;\n\n        function createUI() {\n            const style = `\n                #crawler-container {\n                    position: fixed;\n                    top: ${Config.settings.uiPosition.top};\n                    left: ${Config.settings.uiPosition.left};\n                    width: ${Config.settings.uiSize.width};\n                    height: ${Config.settings.uiSize.height};\n                    background: var(--bg-color, #fff);\n                    border: 1px solid #ccc;\n                    z-index: 10000;\n                    font-family: Arial, sans-serif;\n                    box-shadow: 0 4px 8px rgba(0,0,0,0.2);\n                    overflow: hidden;\n                    resize: both;\n                    border-radius: 8px;\n                    min-width: 300px;\n                    min-height: 300px;\n                    display: ${Config.settings.isMinimized ? 'none' : 'block'};\n                }\n                #crawler-container.dark {\n                    --bg-color: #2b2b2b;\n                    color: #f1f1f1;\n                    border-color: #555;\n                }\n                #crawler-header {\n                    display: flex;\n                    flex-direction: column;\n                    justify-content: space-between;\n                    align-items: flex-start;\n                    background: var(--header-bg, #f1f1f1);\n                    border-bottom: 1px solid #ccc;\n                    padding: 10px;\n                    cursor: move;\n                }\n                #crawler-container.dark #crawler-header {\n                    --header-bg: #3c3c3c;\n                    border-color: #555;\n                }\n                #crawler-minimize {\n                    align-self: flex-end;\n                    background: none;\n                    border: none;\n                    font-size: 18px;\n                    cursor: pointer;\n                    color: inherit;\n                }\n                #crawler-tabs {\n                    display: flex;\n                    flex-wrap: wrap;\n                    width: 100%;\n                }\n                .crawler-tab { flex: 1 1 auto; padding: 10px; text-align: center; cursor: pointer; font-weight: bold; }\n                .crawler-tab.active { background: var(--active-tab-bg, #ddd); }\n                #crawler-container.dark .crawler-tab.active { background: #555; }\n                #crawler-content { padding: 10px; overflow-y: auto; height: calc(100% - 80px); }\n                table { width: 100%; border-collapse: collapse; }\n                th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }\n                th { background-color: var(--th-bg, #f2f2f2); cursor: pointer; }\n                #crawler-container.dark th { background-color: #444; }\n                tr:hover { background-color: #f5f5f5; }\n                #crawler-container.dark tr:hover { background-color: #555; }\n                .progress-bar { width: 100%; background-color: #f3f3f3; border: 1px solid #ccc; border-radius: 5px; overflow: hidden; margin-bottom: 10px; }\n                .progress-bar-fill { height: 20px; background-color: #4caf50; width: 0%; transition: width 0.2s; }\n                .modal { display: none; position: fixed; z-index: 10001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); }\n                .modal-content { background-color: var(--modal-bg, #fff); margin: 5% auto; padding: 20px; border: 1px solid #888; width: 90%; max-width: 800px; max-height: 80%; overflow-y: auto; border-radius: 8px; }\n                #crawler-container.dark .modal-content { --modal-bg: #3c3c3c; color: #f1f1f1; border-color: #555; }\n                .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }\n                .close:hover, .close:focus { color: black; text-decoration: none; cursor: pointer; }\n                .control-buttons { margin: 10px 0; display: flex; flex-wrap: wrap; gap: 10px; }\n                .control-buttons button { padding: 8px 12px; cursor: pointer; border: none; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px; flex: 1 1 auto; min-width: 80px; }\n                .control-buttons button:disabled { background-color: #a5d6a7; cursor: not-allowed; }\n                .status-text { margin-top: 10px; font-weight: bold; font-size: 14px; }\n                .settings-section { margin-bottom: 15px; }\n                .settings-section label { display: block; margin-bottom: 5px; font-weight: bold; }\n                .settings-section input[type=\"text\"], .settings-section input[type=\"number\"], .settings-section textarea, .settings-section select { width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }\n                .settings-section.dark .settings-section input[type=\"text\"], .settings-section.dark .settings-section input[type=\"number\"], .settings-section.dark .settings-section textarea, .settings-section.dark .settings-section select {\n                    background-color: #555;\n                    color: #f1f1f1;\n                    border-color: #777;\n                }\n                .settings-section .checkbox-group { display: flex; flex-wrap: wrap; gap: 10px; }\n                .settings-section .checkbox-group label { margin-right: 10px; font-size: 14px; }\n                .settings-section .radio-group { display: flex; flex-wrap: wrap; gap: 10px; }\n                .settings-section .radio-group label { margin-right: 10px; font-size: 14px; }\n                .tooltip { position: relative; display: inline-block; cursor: help; font-size: 14px; }\n                .tooltip .tooltiptext {\n                    visibility: hidden;\n                    width: 220px;\n                    background-color: #555;\n                    color: #fff;\n                    text-align: center;\n                    border-radius: 6px;\n                    padding: 5px;\n                    position: absolute;\n                    z-index: 1;\n                    bottom: 125%;\n                    left: 50%;\n                    margin-left: -110px;\n                    opacity: 0;\n                    transition: opacity 0.3s;\n                }\n                .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; }\n                #crawler-container::-webkit-resizer { background: #f1f1f1; border: none; }\n                #data-visualization { margin-top: 20px; }\n                #data-chart { width: 100%; height: 300px; }\n                #modal-html-content { white-space: pre-wrap; word-wrap: break-word; background-color: #f9f9f9; padding: 10px; border-radius: 4px; }\n                #crawler-tabs .crawler-tab:last-child { flex: 1; }\n                #previous-crawls { margin-top: 20px; }\n                #previous-crawls table { width: 100%; }\n                #previous-crawls th, #previous-crawls td { padding: 5px; }\n                #reset-data { background-color: #f44336; }\n                .spinner {\n                    border: 4px solid #f3f3f3;\n                    border-top: 4px solid #3498db;\n                    border-radius: 50%;\n                    width: 24px;\n                    height: 24px;\n                    animation: spin 2s linear infinite;\n                    display: inline-block;\n                    vertical-align: middle;\n                }\n                @keyframes spin {\n                    0% { transform: rotate(0deg); }\n                    100% { transform: rotate(360deg); }\n                }\n                .error-message {\n                    color: red;\n                    font-weight: bold;\n                    margin-top: 10px;\n                }\n                .success-message {\n                    color: green;\n                    font-weight: bold;\n                    margin-top: 10px;\n                }\n                .dark-mode-toggle {\n                    background: none;\n                    border: none;\n                    cursor: pointer;\n                    font-size: 18px;\n                    color: inherit;\n                }\n                @media (max-width: 768px) {\n                    #crawler-container {\n                        width: 100%;\n                        height: 100%;\n                        top: 0;\n                        left: 0;\n                        border-radius: 0;\n                    }\n                    #crawler-header {\n                        flex-direction: row;\n                        align-items: center;\n                    }\n                    #crawler-tabs {\n                        flex-direction: row;\n                        overflow-x: auto;\n                    }\n                    .crawler-tab {\n                        flex: 0 0 auto;\n                    }\n                }\n            `;\n            $('head').append(`<style>${style}</style>`);\n            const container = $(`\n                <div id=\"crawler-container\" class=\"${Config.settings.darkMode ? 'dark' : ''}\">\n                    <div id=\"crawler-header\">\n                        <div id=\"crawler-tabs\">\n                            <div class=\"crawler-tab\" data-tab=\"logs\" title=\"\ub85c\uadf8 \ubcf4\uae30\">\ud83d\udcdc \ub85c\uadf8</div>\n                            <div class=\"crawler-tab\" data-tab=\"data\" title=\"\uc218\uc9d1\ub41c \ub370\uc774\ud130 \ubcf4\uae30\">\ud83d\udcca \ub370\uc774\ud130</div>\n                            <div class=\"crawler-tab\" data-tab=\"settings\" title=\"\uc124\uc815 \ubcc0\uacbd\">\u2699\ufe0f \uc124\uc815</div>\n                            <div class=\"crawler-tab\" data-tab=\"previous-crawls\" title=\"\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d\">\ud83d\udcc1 \uae30\ub85d</div>\n                            <div class=\"crawler-tab\" data-tab=\"templates\" title=\"\uc124\uc815 \ud15c\ud50c\ub9bf\">\ud83d\udcc2 \ud15c\ud50c\ub9bf</div>\n                            <button id=\"dark-mode-toggle\" class=\"dark-mode-toggle\" title=\"\ud14c\ub9c8 \uc804\ud658\">\ud83c\udf19</button>\n                        </div>\n                        <button id=\"crawler-minimize\" title=\"\ucc3d \ucd5c\uc18c\ud654\">${Config.settings.isMinimized ? '\u2610' : '\u2500'}</button>\n                    </div>\n                    <div id=\"crawler-content\">\n                        <div id=\"tab-logs\" style=\"display: none;\">\n                            <div class=\"progress-bar\">\n                                <div class=\"progress-bar-fill\" style=\"width: ${Config.settings.progress || 0}%;\"></div>\n                            </div>\n                            <div class=\"status-text\">\uc9c4\ud589\ub960: ${Config.settings.progress || 0}% (${Config.settings.currentPage || 0}/${Config.settings.maxPages} \ud398\uc774\uc9c0)</div>\n                            <div class=\"control-buttons\">\n                                <button id=\"start-crawling\" title=\"\ud06c\ub864\ub9c1\uc744 \uc2dc\uc791\ud569\ub2c8\ub2e4.\">\u25b6 \uc2dc\uc791</button>\n                                <button id=\"stop-crawling\" title=\"\ud06c\ub864\ub9c1\uc744 \uc911\uc9c0\ud569\ub2c8\ub2e4.\" ${Config.settings.isCrawling ? '' : 'disabled'}>\u25a0 \uc911\uc9c0</button>\n                                ${Config.settings.exportFormats.csv ? `<button id=\"download-csv\" title=\"CSV \ub2e4\uc6b4\ub85c\ub4dc\">\ud83d\udce5 CSV</button>` : ''}\n                                ${Config.settings.exportFormats.json ? `<button id=\"download-json\" title=\"JSON \ub2e4\uc6b4\ub85c\ub4dc\">\ud83d\udce5 JSON</button>` : ''}\n                                ${Config.settings.exportFormats.xml ? `<button id=\"download-xml\" title=\"XML \ub2e4\uc6b4\ub85c\ub4dc\">\ud83d\udce5 XML</button>` : ''}\n                                ${Config.settings.exportFormats.xls ? `<button id=\"download-xls\" title=\"XLS \ub2e4\uc6b4\ub85c\ub4dc\">\ud83d\udce5 XLS</button>` : ''}\n                                <button id=\"reset-data\" title=\"\ubaa8\ub4e0 \ud06c\ub864\ub9c1 \ub370\uc774\ud130\ub97c \ucd08\uae30\ud654\ud569\ub2c8\ub2e4.\" ${Config.settings.isCrawling ? 'disabled' : ''}>\ud83d\uddd1\ufe0f \ucd08\uae30\ud654</button>\n                            </div>\n                            <div id=\"log-messages\" style=\"height: 200px; overflow-y: auto; background: var(--log-bg, #f9f9f9); padding: 10px; border: 1px solid #ccc; border-radius: 4px;\">\n                                ${Logger.getLogs().map(l => `<div>[${l.timestamp}] [${l.type.toUpperCase()}] ${Utils.escape(l.message)}</div>`).join('') || '\ub85c\uadf8 \ub0b4\uc6a9\uc774 \uc5ec\uae30\uc5d0 \ud45c\uc2dc\ub429\ub2c8\ub2e4.'}\n                            </div>\n                            <div id=\"error-message\" class=\"error-message\" style=\"display: none;\"></div>\n                            <div id=\"success-message\" class=\"success-message\" style=\"display: none;\"></div>\n                        </div>\n                        <div id=\"tab-data\" style=\"display: none;\">\n                            <input type=\"text\" id=\"search-filter\" placeholder=\"\uac80\uc0c9 \ud544\ud130...\" style=\"width: 100%; padding: 5px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;\">\n                            <div style=\"overflow-x: auto;\">\n                                <table>\n                                    <thead>\n                                        <tr>\n                                            ${Config.settings.dataFields.name ? '<th data-sort=\"name\">\uc0ac\uc774\ud2b8 \uc774\ub984</th>' : ''}\n                                            ${Config.settings.dataFields.url ? '<th data-sort=\"url\">\uc8fc\uc18c</th>' : ''}\n                                            ${Config.settings.dataFields.description ? '<th data-sort=\"description\">\uc124\uba85</th>' : ''}\n                                            ${Config.settings.dataFields.html ? '<th>\ud83d\udcc4 HTML \ub0b4\uc6a9</th>' : ''}\n                                            ${Config.settings.dataFields.category ? '<th data-sort=\"category\">\ubd84\ub958</th>' : ''}\n                                            ${Config.settings.dataFields.quality ? '<th data-sort=\"quality\">\ud488\uc9c8\uc810\uc218</th>' : ''}\n                                            ${Config.settings.dataFields.language ? '<th data-sort=\"language\">\uc5b8\uc5b4</th>' : ''}\n                                            ${Config.settings.dataFields.wordCount ? '<th data-sort=\"wordCount\">\ub2e8\uc5b4 \uc218</th>' : ''}\n                                            ${Config.settings.dataFields.metaTags ? '<th>\uba54\ud0c0 \ud0dc\uadf8</th>' : ''}\n                                            ${Config.settings.dataFields.headings ? '<th>\ud5e4\ub529</th>' : ''}\n                                        </tr>\n                                    </thead>\n                                    <tbody id=\"data-table-body\">\n                                        ${renderDataTable()}\n                                    </tbody>\n                                </table>\n                            </div>\n                            ${Config.settings.dataVisualization ? `\n                                <div id=\"data-visualization\">\n                                    <h3>\ub370\uc774\ud130 \uc2dc\uac01\ud654</h3>\n                                    <canvas id=\"data-chart\"></canvas>\n                                </div>\n                            ` : ''}\n                        </div>\n                        <div id=\"tab-settings\" style=\"display: none;\">\n                            <h3>\uc124\uc815</h3>\n                            <div class=\"settings-section\">\n                                <label for=\"delay-input\">\ud06c\ub864\ub9c1 \ub51c\ub808\uc774 (ms):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uae30\ubcf8 \ub51c\ub808\uc774\uacfc \ub51c\ub808\uc774 \ub79c\ub364 \uac12\uc744 \uc870\ud569\ud558\uc5ec \uc694\uccad \uc0ac\uc774\uc758 \ub51c\ub808\uc774\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"delay-input\" value=\"${Config.settings.delay}\" min=\"500\" step=\"500\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"delay-random-input\">\ub51c\ub808\uc774 \ub79c\ub364 \ubc94\uc704 (ms):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ub51c\ub808\uc774\uc5d0 \ubb34\uc791\uc704 \uc694\uc18c\ub97c \ucd94\uac00\ud558\uc5ec \ubcf4\ub2e4 \uc790\uc5f0\uc2a4\ub7ec\uc6b4 \uc694\uccad\uc744 \ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"delay-random-input\" value=\"${Config.settings.delayRandom}\" min=\"0\" step=\"500\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"max-pages-input\">\ucd5c\ub300 \ud06c\ub864\ub9c1 \ud398\uc774\uc9c0 \uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\ud560 \ucd5c\ub300 \ud398\uc774\uc9c0 \uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"max-pages-input\" value=\"${Config.settings.maxPages}\" min=\"1\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"user-agent-select\">\uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8(User-Agent):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc5d0 \uc0ac\uc6a9\ud560 \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\ub97c \uc120\ud0dd\ud558\uac70\ub098 \ucd94\uac00\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <select id=\"user-agent-select\" multiple size=\"6\">\n                                    ${Config.settings.userAgents.map((ua, idx) => `<option value=\"${idx}\" ${idx === Config.settings.userAgentIndex ? 'selected' : ''}>${ua}</option>`).join('')}\n                                </select>\n                                <button id=\"add-user-agent\" style=\"margin-top: 5px; padding: 5px 10px; background-color: #2196F3; color: white; border-radius: 4px; font-size: 12px;\">\ucd94\uac00</button>\n                                <button id=\"remove-user-agent\" style=\"margin-top: 5px; padding: 5px 10px; background-color: #f44336; color: white; border-radius: 4px; font-size: 12px;\">\uc81c\uac70</button>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"content-include-input\">\ucf58\ud150\uce20 \uc720\ud615 \ud3ec\ud568:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\ud560 \ucf58\ud150\uce20 \uc720\ud615\uc744 \uc27c\ud45c\ub85c \uad6c\ubd84\ud558\uc5ec \uc785\ub825\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"text\" id=\"content-include-input\" value=\"${Config.settings.contentTypes.include.join(', ')}\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"content-exclude-input\">\ucf58\ud150\uce20 \uc720\ud615 \uc81c\uc678:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc5d0\uc11c \uc81c\uc678\ud560 \ucf58\ud150\uce20 \uc720\ud615\uc744 \uc27c\ud45c\ub85c \uad6c\ubd84\ud558\uc5ec \uc785\ub825\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"text\" id=\"content-exclude-input\" value=\"${Config.settings.contentTypes.exclude.join(', ')}\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"timeout-input\">\ud06c\ub864\ub9c1 \ud0c0\uc784\uc544\uc6c3 (ms):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc694\uccad\uc774 \uc644\ub8cc\ub418\uae30\uae4c\uc9c0 \uae30\ub2e4\ub9b4 \ucd5c\ub300 \uc2dc\uac04\uc744 \ubc00\ub9ac\ucd08 \ub2e8\uc704\ub85c \uc124\uc815\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"timeout-input\" value=\"${Config.settings.timeout}\" min=\"1000\" step=\"1000\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"include-keywords-input\">\ud3ec\ud568\ud560 \ud0a4\uc6cc\ub4dc:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\ud560 \ud398\uc774\uc9c0\uc5d0 \ubc18\ub4dc\uc2dc \ud3ec\ud568\ub418\uc5b4\uc57c \ud558\ub294 \ud0a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud558\uc138\uc694. \uac01 \ud0a4\uc6cc\ub4dc\ub294 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"include-keywords-input\" rows=\"3\">${Config.settings.includeKeywords.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"exclude-keywords-input\">\uc81c\uc678\ud560 \ud0a4\uc6cc\ub4dc:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\ud560 \ud398\uc774\uc9c0\uc5d0 \ud3ec\ud568\ub418\uba74 \uc548 \ub418\ub294 \ud0a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud558\uc138\uc694. \uac01 \ud0a4\uc6cc\ub4dc\ub294 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"exclude-keywords-input\" rows=\"3\">${Config.settings.excludeKeywords.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"regex-include-input\">\ud3ec\ud568\ud560 \ud0a4\uc6cc\ub4dc (\uc815\uaddc \ud45c\ud604\uc2dd):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud3ec\ud568\ud560 \ud0a4\uc6cc\ub4dc\uc758 \uc815\uaddc \ud45c\ud604\uc2dd\uc744 \uc785\ub825\ud558\uc138\uc694. \uac01 \ud328\ud134\uc740 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"regex-include-input\" rows=\"3\">${Config.settings.regexInclude.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"regex-exclude-input\">\uc81c\uc678\ud560 \ud0a4\uc6cc\ub4dc (\uc815\uaddc \ud45c\ud604\uc2dd):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc81c\uc678\ud560 \ud0a4\uc6cc\ub4dc\uc758 \uc815\uaddc \ud45c\ud604\uc2dd\uc744 \uc785\ub825\ud558\uc138\uc694. \uac01 \ud328\ud134\uc740 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"regex-exclude-input\" rows=\"3\">${Config.settings.regexExclude.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"language-select\">\uc5b8\uc5b4 \ud544\ud130\ub9c1:</label>\n                                <select id=\"language-select\" multiple size=\"10\">\n                                    ${[\n                                        { value: 'en', text: '\uc601\uc5b4 (en)' }, { value: 'ko', text: '\ud55c\uad6d\uc5b4 (ko)' },\n                                        { value: 'zh', text: '\uc911\uad6d\uc5b4 (zh)' }, { value: 'ja', text: '\uc77c\ubcf8\uc5b4 (ja)' },\n                                        { value: 'ru', text: '\ub7ec\uc2dc\uc544\uc5b4 (ru)' }, { value: 'es', text: '\uc2a4\ud398\uc778\uc5b4 (es)' },\n                                        { value: 'fr', text: '\ud504\ub791\uc2a4\uc5b4 (fr)' }, { value: 'de', text: '\ub3c5\uc77c\uc5b4 (de)' },\n                                        { value: 'it', text: '\uc774\ud0c8\ub9ac\uc544\uc5b4 (it)' }, { value: 'pt', text: '\ud3ec\ub974\ud22c\uac08\uc5b4 (pt)' },\n                                        { value: 'ar', text: '\uc544\ub78d\uc5b4 (ar)' }, { value: 'hi', text: '\ud78c\ub514\uc5b4 (hi)' },\n                                        { value: 'bn', text: '\ubcb5\uace8\uc5b4 (bn)' }, { value: 'pa', text: '\ud380\uc790\ube0c\uc5b4 (pa)' },\n                                        { value: 'nl', text: '\ub124\ub35c\ub780\ub4dc\uc5b4 (nl)' }, { value: 'sv', text: '\uc2a4\uc6e8\ub374\uc5b4 (sv)' },\n                                        { value: 'tr', text: '\ud130\ud0a4\uc5b4 (tr)' }, { value: 'vi', text: '\ubca0\ud2b8\ub0a8\uc5b4 (vi)' },\n                                        { value: 'pl', text: '\ud3f4\ub780\ub4dc\uc5b4 (pl)' }, { value: 'th', text: '\ud0dc\uad6d\uc5b4 (th)' }\n                                    ].map(lang => `<option value=\"${lang.value}\" ${Config.settings.language.includes(lang.value) ? 'selected' : ''}>${lang.text}</option>`).join('')}\n                                </select>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"min-word-count-input\">\ucd5c\uc18c \ub2e8\uc5b4 \uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc218\uc9d1\ud560 \ud398\uc774\uc9c0\uc758 \ucd5c\uc18c \ub2e8\uc5b4 \uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"min-word-count-input\" value=\"${Config.settings.minWordCount}\" min=\"10\" step=\"10\">\n                                <label for=\"max-word-count-input\">\ucd5c\ub300 \ub2e8\uc5b4 \uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc218\uc9d1\ud560 \ud398\uc774\uc9c0\uc758 \ucd5c\ub300 \ub2e8\uc5b4 \uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"max-word-count-input\" value=\"${Config.settings.maxWordCount}\" min=\"100\" step=\"100\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"max-depth-input\">\ucd5c\ub300 \ud06c\ub864\ub9c1 \uae4a\uc774:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ub9c1\ud06c\ub97c \ucd94\uc801\ud560 \ucd5c\ub300 \uae4a\uc774\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"max-depth-input\" value=\"${Config.settings.maxDepth}\" min=\"1\" step=\"1\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"proxy-input\">\ud504\ub85d\uc2dc \uc11c\ubc84:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud504\ub85d\uc2dc \uc11c\ubc84 \uc8fc\uc18c\ub97c \uc785\ub825\ud558\uc138\uc694. \uc608: http://proxyserver:port</span>\n                                    </span>\n                                </label>\n                                <input type=\"text\" id=\"proxy-input\" value=\"${Config.settings.proxy}\" placeholder=\"http://proxyserver:port\">\n                                <div id=\"proxy-validation-message\" class=\"error-message\" style=\"display: none;\"></div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\uc218\uc9d1\ud560 \ub370\uc774\ud130 \ud544\ub4dc:</label>\n                                <div class=\"checkbox-group\">\n                                    ${Object.keys(Config.settings.dataFields).map(field => `<label><input type=\"checkbox\" id=\"field-${field}\" ${Config.settings.dataFields[field] ? 'checked' : ''}> ${Utils.capitalize(getFieldName(field))}</label>`).join('')}\n                                </div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"retry-attempts-input\">\uc7ac\uc2dc\ub3c4 \ud69f\uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc694\uccad \uc2e4\ud328 \uc2dc \uc7ac\uc2dc\ub3c4\ud560 \ud69f\uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"retry-attempts-input\" value=\"${Config.settings.retryAttempts}\" min=\"0\" step=\"1\">\n                                <label for=\"retry-delay-input\">\uc7ac\uc2dc\ub3c4 \ub51c\ub808\uc774 (ms):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc7ac\uc2dc\ub3c4 \uc2dc \ub300\uae30\ud560 \uc2dc\uac04\uc744 \ubc00\ub9ac\ucd08 \ub2e8\uc704\ub85c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"retry-delay-input\" value=\"${Config.settings.retryDelay}\" min=\"1000\" step=\"500\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\ud06c\ub864\ub9c1 \uc2a4\ucf00\uc904\ub9c1:</label>\n                                <div class=\"checkbox-group\">\n                                    <label><input type=\"checkbox\" id=\"schedule-enabled\" ${Config.settings.schedule.enabled ? 'checked' : ''}> \uc2a4\ucf00\uc904\ub9c1 \ud65c\uc131\ud654</label>\n                                </div>\n                                <div id=\"schedule-options\" style=\"display: ${Config.settings.schedule.enabled ? 'block' : 'none'}; margin-top: 10px;\">\n                                    <label for=\"start-time-input\">\uc2dc\uc791 \uc2dc\uac04 (HH:MM):\n                                        <span class=\"tooltip\">\u2139\ufe0f\n                                            <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc744 \uc2dc\uc791\ud560 \uc2dc\uac04\uc744 \uc124\uc815\ud558\uc138\uc694.</span>\n                                        </span>\n                                    </label>\n                                    <input type=\"time\" id=\"start-time-input\" value=\"${Config.settings.schedule.startTime}\">\n                                    <label for=\"interval-input\">\ud06c\ub864\ub9c1 \uac04\uaca9 (\ubd84):\n                                        <span class=\"tooltip\">\u2139\ufe0f\n                                            <span class=\"tooltiptext\">\ud06c\ub864\ub9c1 \uc791\uc5c5 \uac04\uc758 \uac04\uaca9\uc744 \ubd84 \ub2e8\uc704\ub85c \uc124\uc815\ud558\uc138\uc694.</span>\n                                        </span>\n                                    </label>\n                                    <input type=\"number\" id=\"interval-input\" value=\"${Config.settings.schedule.interval / 60000}\" min=\"1\" step=\"1\">\n                                </div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\ud06c\ub864\ub9c1\ud560 \uac80\uc0c9 \uc5d4\uc9c4 \uc120\ud0dd:</label>\n                                <div class=\"checkbox-group\">\n                                    ${Object.keys(Config.settings.searchEngines).map(engine => `<label><input type=\"checkbox\" id=\"engine-${engine}\" ${Config.settings.searchEngines[engine] ? 'checked' : ''}> ${Utils.capitalize(engine)}</label>`).join('')}\n                                </div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"blacklist-input\">\ube14\ub799\ub9ac\uc2a4\ud2b8 \ub3c4\uba54\uc778:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc5d0\uc11c \uc81c\uc678\ud560 \ub3c4\uba54\uc778\uc744 \uc785\ub825\ud558\uc138\uc694. \uac01 \ub3c4\uba54\uc778\uc740 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"blacklist-input\" rows=\"5\">${Config.settings.blacklist.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"whitelist-input\">\ud654\uc774\ud2b8\ub9ac\uc2a4\ud2b8 \ub3c4\uba54\uc778:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc744 \ud5c8\uc6a9\ud560 \ub3c4\uba54\uc778\uc744 \uc785\ub825\ud558\uc138\uc694. \uac01 \ub3c4\uba54\uc778\uc740 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"whitelist-input\" rows=\"5\">${Config.settings.whitelist.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"respect-robots-checkbox\">robots.txt \uc900\uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ub300\uc0c1 \uc0ac\uc774\ud2b8\uc758 robots.txt \uaddc\uce59\uc744 \uc900\uc218\ud558\uc5ec \ud06c\ub864\ub9c1\uc744 \uc9c4\ud589\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"checkbox\" id=\"respect-robots-checkbox\" ${Config.settings.respectRobots ? 'checked' : ''}>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"max-concurrent-requests-input\">\ub3d9\uc2dc \uc694\uccad \ucd5c\ub300 \uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud55c \ubc88\uc5d0 \uc694\uccad\ud560 \uc218 \uc788\ub294 \ucd5c\ub300 \ub3d9\uc2dc \uc694\uccad \uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"max-concurrent-requests-input\" value=\"${Config.settings.maxConcurrentRequests}\" min=\"1\" step=\"1\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"data-visualization-checkbox\">\ub370\uc774\ud130 \uc2dc\uac01\ud654 \ud65c\uc131\ud654:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc218\uc9d1\ub41c \ub370\uc774\ud130\ub97c \uc2dc\uac01\ud654\ud558\uc5ec \uadf8\ub798\ud504\ub85c \ud45c\uc2dc\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"checkbox\" id=\"data-visualization-checkbox\" ${Config.settings.dataVisualization ? 'checked' : ''}>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"enable-captchas-checkbox\">CAPTCHA \ucc98\ub9ac \ud65c\uc131\ud654:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">CAPTCHA\uac00 \uac10\uc9c0\ub418\uc5c8\uc744 \ub54c \uc790\ub3d9 \ucc98\ub9ac\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4. (\uc81c\ud55c\uc801)</span>\n                                    </span>\n                                </label>\n                                <input type=\"checkbox\" id=\"enable-captchas-checkbox\" ${Config.settings.enableCaptchas ? 'checked' : ''}>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\ub370\uc774\ud130 \ub0b4\ubcf4\ub0b4\uae30 \ud615\uc2dd:</label>\n                                <div class=\"checkbox-group\">\n                                    ${Object.keys(Config.settings.exportFormats).map(format => `<label><input type=\"checkbox\" id=\"export-${format}\" ${Config.settings.exportFormats[format] ? 'checked' : ''}> ${format.toUpperCase()}</label>`).join('')}\n                                </div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"advanced-logging-checkbox\">\uace0\uae09 \ub85c\uae45 \ud65c\uc131\ud654:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ucd94\uac00\uc801\uc778 \ub85c\uae45 \uc815\ubcf4\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"checkbox\" id=\"advanced-logging-checkbox\" ${Config.settings.advancedLogging ? 'checked' : ''}>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"custom-keywords-input\">\uc0ac\uc6a9\uc790 \uc9c0\uc815 \ud0a4\uc6cc\ub4dc \ubd84\ub958:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc0ac\uc6a9\uc790 \uc9c0\uc815 \ud0a4\uc6cc\ub4dc\ub97c \ucd94\uac00\ud558\uc5ec \ub370\uc774\ud130 \ubd84\ub958\ub97c \ud5a5\uc0c1\uc2dc\ud0b5\ub2c8\ub2e4. \ud615\uc2dd: \uce74\ud14c\uace0\ub9ac:\ud0a4\uc6cc\ub4dc1,\ud0a4\uc6cc\ub4dc2,...</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"custom-keywords-input\" rows=\"5\">${Utils.formatCustomKeywords(Config.settings.customKeywords)}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\uc124\uc815 \ud15c\ud50c\ub9bf \uad00\ub9ac:</label>\n                                <div class=\"checkbox-group\">\n                                    <input type=\"text\" id=\"new-template-name\" placeholder=\"\ud15c\ud50c\ub9bf \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694\" style=\"margin-bottom: 5px;\">\n                                    <button id=\"save-template\" style=\"padding: 8px 12px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px; margin-right: 8px;\">\ud83d\udcc1 \ud15c\ud50c\ub9bf \uc800\uc7a5</button>\n                                    <button id=\"load-template\" style=\"padding: 8px 12px; background-color: #2196F3; color: white; border-radius: 4px; font-size: 14px;\">\ud83d\udcc2 \ud15c\ud50c\ub9bf \ubd88\ub7ec\uc624\uae30</button>\n                                </div>\n                                <select id=\"template-select\" style=\"width: 100%; padding: 5px; margin-top: 10px; border: 1px solid #ccc; border-radius: 4px;\">\n                                    <option value=\"\" disabled selected>\ud15c\ud50c\ub9bf \uc120\ud0dd</option>\n                                    ${Object.keys(Config.settings.templates).map(template => `<option value=\"${template}\">${template}</option>`).join('')}\n                                </select>\n                                <button id=\"create-template\" style=\"margin-top: 10px; padding: 8px 12px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;\">\ud15c\ud50c\ub9bf \uc0dd\uc131</button>\n                                <button id=\"delete-template\" style=\"margin-top: 5px; padding: 8px 12px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;\">\ud15c\ud50c\ub9bf \uc0ad\uc81c</button>\n                            </div>\n                            <div class=\"settings-section\">\n                                <button id=\"save-settings\" style=\"margin-top: 10px; padding: 10px 20px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;\">\uc800\uc7a5</button>\n                                <button id=\"reset-settings\" style=\"margin-top: 10px; padding: 10px 20px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;\">\ucd08\uae30\ud654</button>\n                            </div>\n                        </div>\n                        <div id=\"tab-templates\" style=\"display: none;\">\n                            <h3>\uc124\uc815 \ud15c\ud50c\ub9bf</h3>\n                            <div class=\"settings-section\">\n                                <label for=\"new-template-name\">\uc0c8 \ud15c\ud50c\ub9bf \uc774\ub984:</label>\n                                <input type=\"text\" id=\"new-template-name\" placeholder=\"\ud15c\ud50c\ub9bf \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694\">\n                                <button id=\"create-template\" style=\"margin-top: 5px; padding: 8px 12px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;\">\ud15c\ud50c\ub9bf \uc0dd\uc131</button>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"existing-templates\">\uae30\uc874 \ud15c\ud50c\ub9bf:</label>\n                                <select id=\"existing-templates\" multiple size=\"10\" style=\"width: 100%; padding: 5px; border: 1px solid #ccc; border-radius: 4px;\">\n                                    ${Object.keys(Config.settings.templates).map(template => `<option value=\"${template}\">${template}</option>`).join('')}\n                                </select>\n                                <button id=\"delete-template\" style=\"margin-top: 5px; padding: 8px 12px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;\">\ud15c\ud50c\ub9bf \uc0ad\uc81c</button>\n                            </div>\n                        </div>\n                        <div id=\"tab-previous-crawls\" style=\"display: none;\">\n                            <h3>\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d</h3>\n                            <button id=\"clear-previous-crawls\" style=\"padding: 8px 12px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px; margin-bottom: 10px;\">\uae30\ub85d \ucd08\uae30\ud654</button>\n                            <div style=\"overflow-x: auto;\">\n                                <table>\n                                    <thead>\n                                        <tr>\n                                            <th>\ub0a0\uc9dc</th>\n                                            <th>\ud06c\ub864\ub9c1 \uc218</th>\n                                            <th>\uc0c1\ud0dc</th>\n                                        </tr>\n                                    </thead>\n                                    <tbody id=\"previous-crawls-body\">\n                                        ${renderPreviousCrawls()}\n                                    </tbody>\n                                </table>\n                            </div>\n                        </div>\n                        <div id=\"html-modal\" class=\"modal\">\n                            <div class=\"modal-content\">\n                                <span class=\"close\">&times;</span>\n                                <pre id=\"modal-html-content\"></pre>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            `);\n            $('body').append(container);\n\n            $('#crawler-minimize').on('click', toggleMinimize);\n            $('.crawler-tab').on('click', switchTab);\n            $('#save-settings').on('click', saveSettingsHandler);\n            $('#reset-settings').on('click', resetSettingsHandler);\n            $('#add-user-agent').on('click', addUserAgent);\n            $('#remove-user-agent').on('click', removeUserAgent);\n            $('#download-csv').on('click', Download.downloadCSV);\n            $('#download-json').on('click', Download.downloadJSON);\n            $('#download-xml').on('click', Download.downloadXML);\n            $('#download-xls').on('click', Download.downloadXLS);\n            $('#search-filter').on('input', filterData);\n            $('#schedule-enabled').on('change', toggleScheduleOptions);\n            $('#data-visualization-checkbox').on('change', toggleDataVisualization);\n            $('#enable-captchas-checkbox').on('change', toggleCaptchaHandling);\n            $('#save-template').on('click', saveTemplateHandler);\n            $('#load-template').on('click', loadTemplateHandler);\n            $('#create-template').on('click', createTemplateHandler);\n            $('#delete-template').on('click', deleteTemplateHandler);\n            $('#template-select').on('change', selectTemplateHandler);\n            $('#existing-templates').on('change', existingTemplateSelectHandler);\n            $('#clear-previous-crawls').on('click', clearPreviousCrawls);\n            $('#reset-data').on('click', resetData);\n            $('#dark-mode-toggle').on('click', toggleDarkMode);\n            $('.close').on('click', () => $('#html-modal').hide());\n            $(window).on('click', function(e) { if (e.target.id === 'html-modal') $('#html-modal').hide(); });\n            $('#start-crawling').on('click', Crawling.startCrawling);\n            $('#stop-crawling').on('click', Crawling.stopCrawling);\n            $(document).on('keydown', handleShortcuts);\n            $('#proxy-input').on('input', validateProxyInput);\n            $('.settings-section input[type=\"checkbox\"]').on('change', toggleDataField);\n            $('#crawler-container').on('mousedown', '#crawler-header', startDrag);\n            $(document).on('mouseup', stopDrag);\n            $(document).on('mousemove', drag);\n\n            updateUI();\n            makeResizable();\n        }\n\n        function toggleMinimize() {\n            $('#crawler-content').toggle();\n            const isHidden = $('#crawler-content').is(':hidden');\n            $('#crawler-minimize').text(isHidden ? '\u2610' : '\u2500');\n            Config.saveSettings({ isMinimized: isHidden });\n            UI.updateLogs();\n            Logger.log(`\ud06c\ub864\ub7ec \ucc3d\uc774 ${isHidden ? '\ucd5c\uc18c\ud654' : '\ud655\ub300'}\ub418\uc5c8\uc2b5\ub2c8\ub2e4.`, 'info');\n        }\n\n        function switchTab() {\n            const tab = $(this).data('tab');\n            $('.crawler-tab').removeClass('active');\n            $(this).addClass('active');\n            $('#crawler-content > div').hide();\n            $(`#tab-${tab}`).show();\n            Config.saveSettings({ currentTab: tab });\n            UI.updateLogs();\n        }\n\n        function updateUI() {\n            const currentTab = Config.settings.currentTab || 'logs';\n            $(`.crawler-tab[data-tab=\"${currentTab}\"]`).addClass('active');\n            $('#crawler-content > div').hide();\n            $(`#tab-${currentTab}`).show();\n            UI.updateLogs();\n            UI.updateDataTable();\n            UI.updatePreviousCrawlsUI();\n            UI.updateTemplatesUI();\n            if (Config.settings.dataVisualization) DataVisualization.renderChart();\n            applyDarkMode();\n        }\n\n        function updateLogs() {\n            const logs = Logger.getLogs();\n            $('#log-messages').html(logs.map(l => `<div>[${l.timestamp}] [${l.type.toUpperCase()}] ${Utils.escape(l.message)}</div>`).join(''));\n            const progress = Config.settings.progress || 0;\n            const currentPage = Config.settings.currentPage || 0;\n            const totalPages = Config.settings.maxPages;\n            $('.progress-bar-fill').css('width', `${progress.toFixed(2)}%`);\n            $('.status-text').text(`\uc9c4\ud589\ub960: ${progress.toFixed(2)}% (${currentPage}/${totalPages} \ud398\uc774\uc9c0)`);\n            const latestLog = logs[logs.length -1];\n            if (latestLog) {\n                if (latestLog.type === 'error') {\n                    $('#error-message').text(latestLog.message).show();\n                    setTimeout(() => { $('#error-message').fadeOut(); }, 5000);\n                }\n                if (latestLog.type === 'success') {\n                    $('#success-message').text(latestLog.message).show();\n                    setTimeout(() => { $('#success-message').fadeOut(); }, 5000);\n                }\n                if (latestLog.type === 'info') {\n                    $('#success-message').text(latestLog.message).show();\n                    setTimeout(() => { $('#success-message').fadeOut(); }, 5000);\n                }\n                if (latestLog.type === 'warning') {\n                    $('#error-message').text(latestLog.message).show();\n                    setTimeout(() => { $('#error-message').fadeOut(); }, 5000);\n                }\n            }\n        }\n\n        function updateDataTable() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            const filter = $('#search-filter').val().toLowerCase();\n            const filtered = filter ? data.filter(item => Object.keys(Config.settings.dataFields).some(field => Config.settings.dataFields[field] && String(item[field]).toLowerCase().includes(filter))) : data;\n            const rows = filtered.map(item => `\n                <tr>\n                    ${Config.settings.dataFields.name ? `<td>${Utils.escape(item.name)}</td>` : ''}\n                    ${Config.settings.dataFields.url ? `<td><a href=\"${Utils.escape(item.url)}\" target=\"_blank\">${Utils.escape(item.url)}</a></td>` : ''}\n                    ${Config.settings.dataFields.description ? `<td>${Utils.escape(item.description)}</td>` : ''}\n                    ${Config.settings.dataFields.html ? `<td><button class=\"view-html\" data-html=\"${encodeURIComponent(item.html)}\">\ud83d\udcc4 \ubcf4\uae30</button></td>` : ''}\n                    ${Config.settings.dataFields.category ? `<td>${Utils.escape(item.category)}</td>` : ''}\n                    ${Config.settings.dataFields.quality ? `<td>${Utils.escape(item.quality.toString())}</td>` : ''}\n                    ${Config.settings.dataFields.language ? `<td>${Utils.escape(item.language)}</td>` : ''}\n                    ${Config.settings.dataFields.wordCount ? `<td>${Utils.escape(item.wordCount.toString())}</td>` : ''}\n                    ${Config.settings.dataFields.metaTags ? `<td>${Utils.escape(JSON.stringify(item.metaTags))}</td>` : ''}\n                    ${Config.settings.dataFields.headings ? `<td>${Utils.escape(item.headings.join(', '))}</td>` : ''}\n                </tr>\n            `).join('');\n            $('#data-table-body').html(rows);\n            $('.view-html').off('click').on('click', function() {\n                $('#modal-html-content').text(decodeURIComponent($(this).data('html')));\n                $('#html-modal').show();\n            });\n            if (Config.settings.dataVisualization) DataVisualization.renderChart();\n        }\n\n        function renderDataTable() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            const filter = $('#search-filter').val().toLowerCase();\n            const filtered = filter ? data.filter(item => Object.keys(Config.settings.dataFields).some(field => Config.settings.dataFields[field] && String(item[field]).toLowerCase().includes(filter))) : data;\n            const rows = filtered.map(item => `\n                <tr>\n                    ${Config.settings.dataFields.name ? `<td>${Utils.escape(item.name)}</td>` : ''}\n                    ${Config.settings.dataFields.url ? `<td><a href=\"${Utils.escape(item.url)}\" target=\"_blank\">${Utils.escape(item.url)}</a></td>` : ''}\n                    ${Config.settings.dataFields.description ? `<td>${Utils.escape(item.description)}</td>` : ''}\n                    ${Config.settings.dataFields.html ? `<td><button class=\"view-html\" data-html=\"${encodeURIComponent(item.html)}\">\ud83d\udcc4 \ubcf4\uae30</button></td>` : ''}\n                    ${Config.settings.dataFields.category ? `<td>${Utils.escape(item.category)}</td>` : ''}\n                    ${Config.settings.dataFields.quality ? `<td>${Utils.escape(item.quality.toString())}</td>` : ''}\n                    ${Config.settings.dataFields.language ? `<td>${Utils.escape(item.language)}</td>` : ''}\n                    ${Config.settings.dataFields.wordCount ? `<td>${Utils.escape(item.wordCount.toString())}</td>` : ''}\n                    ${Config.settings.dataFields.metaTags ? `<td>${Utils.escape(JSON.stringify(item.metaTags))}</td>` : ''}\n                    ${Config.settings.dataFields.headings ? `<td>${Utils.escape(item.headings.join(', '))}</td>` : ''}\n                </tr>\n            `).join('');\n            return rows;\n        }\n\n        function renderPreviousCrawls() {\n            const previousCrawls = JSON.parse(GM_getValue('previousCrawls', '[]'));\n            if (!previousCrawls.length) return '<tr><td colspan=\"3\">\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.</td></tr>';\n            return previousCrawls.map(crawl => `\n                <tr>\n                    <td>${Utils.escape(crawl.date)}</td>\n                    <td>${Utils.escape(crawl.count.toString())}</td>\n                    <td>${Utils.escape(crawl.status)}</td>\n                </tr>\n            `).join('');\n        }\n\n        function updatePreviousCrawlsUI() {\n            const html = renderPreviousCrawls();\n            $('#previous-crawls-body').html(html);\n        }\n\n        function updateTemplatesUI() {\n            $('#template-select').empty().append(`<option value=\"\" disabled selected>\ud15c\ud50c\ub9bf \uc120\ud0dd</option>`);\n            Object.keys(Config.settings.templates).forEach(template => {\n                $('#template-select').append(`<option value=\"${template}\">${template}</option>`);\n            });\n            $('#existing-templates').empty();\n            Object.keys(Config.settings.templates).forEach(template => {\n                $('#existing-templates').append(`<option value=\"${template}\">${template}</option>`);\n            });\n        }\n\n        function addUserAgent() {\n            const ua = prompt(\"\uc0c8 \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\ub97c \uc785\ub825\ud558\uc138\uc694:\");\n            if (ua) {\n                Config.settings.userAgents.push(ua);\n                Config.saveSettings({ userAgents: Config.settings.userAgents });\n                updateUserAgentSelect();\n                Logger.log('\uc0c8 \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\uac00 \ucd94\uac00\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            }\n        }\n\n        function removeUserAgent() {\n            const selected = $('#user-agent-select').val();\n            if (selected && selected.length) {\n                selected.sort((a, b) => b - a).forEach(idx => { Config.settings.userAgents.splice(idx, 1); });\n                Config.settings.userAgentIndex = 0;\n                Config.saveSettings({\n                    userAgents: Config.settings.userAgents,\n                    userAgentIndex: Config.settings.userAgentIndex\n                });\n                updateUserAgentSelect();\n                Logger.log('\uc120\ud0dd\ud55c \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\uac00 \uc81c\uac70\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            } else {\n                alert('\uc81c\uac70\ud560 \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\ub97c \uc120\ud0dd\ud558\uc138\uc694.');\n            }\n        }\n\n        function updateUserAgentSelect() {\n            $('#user-agent-select').empty();\n            Config.settings.userAgents.forEach((ua, idx) => {\n                $('#user-agent-select').append(`<option value=\"${idx}\" ${idx === Config.settings.userAgentIndex ? 'selected' : ''}>${ua}</option>`);\n            });\n        }\n\n        function saveSettingsHandler() {\n            const delay = parseInt($('#delay-input').val(), 10);\n            const delayRandom = parseInt($('#delay-random-input').val(), 10);\n            const maxPages = parseInt($('#max-pages-input').val(), 10);\n            const timeout = parseInt($('#timeout-input').val(), 10);\n            const minWordCount = parseInt($('#min-word-count-input').val(), 10);\n            const maxWordCount = parseInt($('#max-word-count-input').val(), 10);\n            const maxDepth = parseInt($('#max-depth-input').val(), 10);\n            const retryAttempts = parseInt($('#retry-attempts-input').val(), 10);\n            const retryDelay = parseInt($('#retry-delay-input').val(), 10);\n            const interval = parseInt($('#interval-input').val(), 10) * 60000;\n            const proxy = $('#proxy-input').val().trim();\n            const isValidProxy = proxy === '' || Config.validateProxy(proxy);\n            const isValidSettings = !isNaN(delay) && delay >= 500 &&\n                                    !isNaN(delayRandom) && delayRandom >= 0 &&\n                                    !isNaN(maxPages) && maxPages >= 1 &&\n                                    !isNaN(timeout) && timeout >= 1000 &&\n                                    !isNaN(minWordCount) && minWordCount >= 10 &&\n                                    !isNaN(maxWordCount) && maxWordCount >= 100 &&\n                                    !isNaN(maxDepth) && maxDepth >= 1 &&\n                                    !isNaN(retryAttempts) && retryAttempts >= 0 &&\n                                    !isNaN(retryDelay) && retryDelay >= 1000 &&\n                                    (!Config.settings.schedule.enabled || (!isNaN(interval) && interval >= 60000)) &&\n                                    isValidProxy;\n\n            if (!isValidSettings) {\n                $('#error-message').text('\uc124\uc815\uac12\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc62c\ubc14\ub978 \uac12\uc744 \uc785\ub825\ud558\uc138\uc694.').show();\n                setTimeout(() => { $('#error-message').fadeOut(); }, 5000);\n                return;\n            }\n\n            Config.settings.delay = delay;\n            Config.settings.delayRandom = delayRandom;\n            Config.settings.maxPages = maxPages;\n            const selectedUA = parseInt($('#user-agent-select').val(), 10);\n            Config.settings.userAgentIndex = isNaN(selectedUA) ? 0 : selectedUA;\n            Config.settings.contentTypes.include = $('#content-include-input').val().split(',').map(ct => ct.trim()).filter(ct => ct);\n            Config.settings.contentTypes.exclude = $('#content-exclude-input').val().split(',').map(ct => ct.trim()).filter(ct => ct);\n            Config.settings.timeout = timeout;\n            Config.settings.includeKeywords = $('#include-keywords-input').val().split('\\n').map(kw => kw.trim()).filter(kw => kw);\n            Config.settings.excludeKeywords = $('#exclude-keywords-input').val().split('\\n').map(kw => kw.trim()).filter(kw => kw);\n            Config.settings.regexInclude = $('#regex-include-input').val().split('\\n').map(rx => rx.trim()).filter(rx => rx);\n            Config.settings.regexExclude = $('#regex-exclude-input').val().split('\\n').map(rx => rx.trim()).filter(rx => rx);\n            Config.settings.language = $('#language-select').val() || [];\n            Config.settings.minWordCount = minWordCount;\n            Config.settings.maxWordCount = maxWordCount;\n            Config.settings.maxDepth = maxDepth;\n            Config.settings.proxy = proxy;\n            ['name','url','description','html','category','quality','language','wordCount','metaTags','headings'].forEach(field => {\n                Config.settings.dataFields[field] = $(`#field-${field}`).is(':checked');\n            });\n            Config.settings.retryAttempts = retryAttempts;\n            Config.settings.retryDelay = retryDelay;\n            Config.settings.schedule.enabled = $('#schedule-enabled').is(':checked');\n            Config.settings.schedule.startTime = $('#start-time-input').val() || Config.DEFAULT_SETTINGS.schedule.startTime;\n            Config.settings.schedule.interval = Config.settings.schedule.enabled ? (isNaN(interval) ? Config.DEFAULT_SETTINGS.schedule.interval : interval) : Config.DEFAULT_SETTINGS.schedule.interval;\n            Object.keys(Config.settings.searchEngines).forEach(engine => { Config.settings.searchEngines[engine] = $(`#engine-${engine}`).is(':checked'); });\n            Config.settings.blacklist = $('#blacklist-input').val().split('\\n').map(d => d.trim()).filter(d => d);\n            Config.settings.whitelist = $('#whitelist-input').val().split('\\n').map(d => d.trim()).filter(d => d);\n            Config.settings.respectRobots = $('#respect-robots-checkbox').is(':checked');\n            Config.settings.maxConcurrentRequests = parseInt($('#max-concurrent-requests-input').val(), 10) || Config.DEFAULT_SETTINGS.maxConcurrentRequests;\n            Config.settings.dataVisualization = $('#data-visualization-checkbox').is(':checked');\n            Config.settings.exportFormats.csv = $('#export-csv').is(':checked');\n            Config.settings.exportFormats.json = $('#export-json').is(':checked');\n            Config.settings.exportFormats.xml = $('#export-xml').is(':checked');\n            Config.settings.exportFormats.xls = $('#export-xls').is(':checked');\n            Config.settings.advancedLogging = $('#advanced-logging-checkbox').is(':checked');\n            Config.settings.enableCaptchas = $('#enable-captchas-checkbox').is(':checked');\n            Config.settings.customKeywords = Utils.parseCustomKeywords($('#custom-keywords-input').val());\n\n            Config.saveSettings(Config.settings);\n            Crawling.updateCrawlSettings();\n            Logger.log('\uc124\uc815\uc774 \uc800\uc7a5\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n\n            if (Config.settings.schedule.enabled) {\n                Scheduler.scheduleCrawling();\n            }\n\n            updateDataTable();\n            if (Config.settings.dataVisualization) DataVisualization.renderChart();\n        }\n\n        function resetSettingsHandler() {\n            if (confirm(\"\uae30\ubcf8 \uc124\uc815\uc73c\ub85c \ucd08\uae30\ud654\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\")) {\n                Config.resetSettings();\n                location.reload();\n                Logger.log('\uae30\ubcf8 \uc124\uc815\uc73c\ub85c \ucd08\uae30\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            }\n        }\n\n        function toggleScheduleOptions() {\n            $('#schedule-options').toggle($('#schedule-enabled').is(':checked'));\n        }\n\n        function toggleDataVisualization() {\n            Config.settings.dataVisualization = $('#data-visualization-checkbox').is(':checked');\n            Config.saveSettings({ dataVisualization: Config.settings.dataVisualization });\n            $('#data-visualization').toggle(Config.settings.dataVisualization);\n            Logger.log('\ub370\uc774\ud130 \uc2dc\uac01\ud654 \uc124\uc815\uc774 \ubcc0\uacbd\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            if (Config.settings.dataVisualization) DataVisualization.renderChart();\n        }\n\n        function toggleCaptchaHandling() {\n            Config.settings.enableCaptchas = $('#enable-captchas-checkbox').is(':checked');\n            Config.saveSettings({ enableCaptchas: Config.settings.enableCaptchas });\n            Logger.log('CAPTCHA \ucc98\ub9ac \uc124\uc815\uc774 \ubcc0\uacbd\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function filterData() {\n            updateDataTable();\n        }\n\n        function clearPreviousCrawls() {\n            if (confirm(\"\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d\uc744 \ubaa8\ub450 \ucd08\uae30\ud654\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\")) {\n                GM_setValue('previousCrawls', JSON.stringify([]));\n                updatePreviousCrawlsUI();\n                Logger.log('\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d\uc774 \ucd08\uae30\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            }\n        }\n\n        function resetData() {\n            if (confirm(\"\ubaa8\ub4e0 \ud06c\ub864\ub9c1 \ub370\uc774\ud130\ub97c \ucd08\uae30\ud654\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\")) {\n                GM_setValue('data', JSON.stringify([]));\n                GM_setValue('visitedUrls', JSON.stringify([]));\n                updateDataTable();\n                Logger.log('\ud06c\ub864\ub9c1 \ub370\uc774\ud130\uac00 \ucd08\uae30\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            }\n        }\n\n        function handleShortcuts(e) {\n            if (e.ctrlKey) {\n                const actions = { '1': 'logs', '2': 'data', '3': 'settings', '4': 'previous-crawls', '5': 'templates', 's': 'save', 'r': 'reset', 'p': 'start', 'q': 'stop', 'c': 'csv', 'j': 'json', 'x': 'xml', 'l': 'xls', 'd': 'dark-mode' };\n                const action = actions[e.key];\n                if (action) {\n                    e.preventDefault();\n                    switch(action) {\n                        case 'logs': $('.crawler-tab[data-tab=\"logs\"]').click(); break;\n                        case 'data': $('.crawler-tab[data-tab=\"data\"]').click(); break;\n                        case 'settings': $('.crawler-tab[data-tab=\"settings\"]').click(); break;\n                        case 'previous-crawls': $('.crawler-tab[data-tab=\"previous-crawls\"]').click(); break;\n                        case 'templates': $('.crawler-tab[data-tab=\"templates\"]').click(); break;\n                        case 'save': saveSettingsHandler(); break;\n                        case 'reset': resetSettingsHandler(); break;\n                        case 'start': if (!Crawling.isCrawling()) Crawling.startCrawling(); break;\n                        case 'stop': if (Crawling.isCrawling()) Crawling.stopCrawling(); break;\n                        case 'csv': Config.settings.exportFormats.csv && Download.downloadCSV(); break;\n                        case 'json': Config.settings.exportFormats.json && Download.downloadJSON(); break;\n                        case 'xml': Config.settings.exportFormats.xml && Download.downloadXML(); break;\n                        case 'xls': Config.settings.exportFormats.xls && Download.downloadXLS(); break;\n                        case 'dark-mode': toggleDarkMode(); break;\n                    }\n                }\n            }\n        }\n\n        function makeDraggable() {\n            // Handled via mousedown, mousemove, mouseup events\n        }\n\n        function startDrag(e) {\n            if (e.target.id === 'crawler-header' || $(e.target).closest('#crawler-header').length) {\n                isDragging = true;\n                const container = $('#crawler-container');\n                const offset = container.offset();\n                offsetX = e.pageX - offset.left;\n                offsetY = e.pageY - offset.top;\n            }\n        }\n\n        function drag(e) {\n            if (isDragging) {\n                let left = e.pageX - offsetX;\n                let top = e.pageY - offsetY;\n                left = Math.max(0, Math.min(left, $(window).width() - $('#crawler-container').outerWidth()));\n                top = Math.max(0, Math.min(top, $(window).height() - $('#crawler-container').outerHeight()));\n                $('#crawler-container').css({ left: `${left}px`, top: `${top}px` });\n            }\n        }\n\n        function stopDrag() {\n            if (isDragging) {\n                isDragging = false;\n                const position = $('#crawler-container').position();\n                Config.saveSettings({ uiPosition: { top: `${position.top}px`, left: `${position.left}px` } });\n            }\n        }\n\n        function makeResizable() {\n            // Already handled via CSS resize: both;\n            // Additional logic can be added if needed\n        }\n\n        function getFieldName(field) {\n            const names = {\n                name: '\uc774\ub984',\n                url: '\uc8fc\uc18c',\n                description: '\uc124\uba85',\n                html: 'HTML \ub0b4\uc6a9',\n                category: '\ubd84\ub958',\n                quality: '\ud488\uc9c8\uc810\uc218',\n                language: '\uc5b8\uc5b4',\n                wordCount: '\ub2e8\uc5b4 \uc218',\n                metaTags: '\uba54\ud0c0 \ud0dc\uadf8',\n                headings: '\ud5e4\ub529'\n            };\n            return names[field] || field;\n        }\n\n        // Template Handlers\n        function saveTemplateHandler() {\n            const templateName = $('#new-template-name').val().trim();\n            if (!templateName) {\n                alert('\ud15c\ud50c\ub9bf \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694.');\n                return;\n            }\n            Config.saveTemplate(templateName);\n            updateTemplatesUI();\n            $('#new-template-name').val('');\n        }\n\n        function loadTemplateHandler() {\n            const selected = $('#template-select').val();\n            if (!selected) {\n                alert('\ubd88\ub7ec\uc62c \ud15c\ud50c\ub9bf\uc744 \uc120\ud0dd\ud558\uc138\uc694.');\n                return;\n            }\n            Config.loadTemplate(selected);\n            updateUI();\n            $('.crawler-tab[data-tab=\"settings\"]').click();\n        }\n\n        function createTemplateHandler() {\n            const templateName = $('#new-template-name').val().trim();\n            if (!templateName) {\n                alert('\ud15c\ud50c\ub9bf \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694.');\n                return;\n            }\n            Config.saveTemplate(templateName);\n            updateTemplatesUI();\n            $('#new-template-name').val('');\n        }\n\n        function deleteTemplateHandler() {\n            const selectedTemplates = $('#existing-templates').val();\n            if (!selectedTemplates || !selectedTemplates.length) {\n                alert('\uc0ad\uc81c\ud560 \ud15c\ud50c\ub9bf\uc744 \uc120\ud0dd\ud558\uc138\uc694.');\n                return;\n            }\n            selectedTemplates.forEach(template => {\n                delete Config.settings.templates[template];\n                Logger.log(`\ud15c\ud50c\ub9bf '${template}'\uc774(\uac00) \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4.`, 'success');\n            });\n            Config.saveSettings({ templates: Config.settings.templates });\n            updateTemplatesUI();\n        }\n\n        function selectTemplateHandler() {\n            const selected = $(this).val();\n            if (selected) {\n                Config.loadTemplate(selected);\n                updateUI();\n                $('.crawler-tab[data-tab=\"settings\"]').click();\n            }\n        }\n\n        function existingTemplateSelectHandler() {\n            // Placeholder for any additional actions when selecting existing templates\n        }\n\n        function toggleDarkMode() {\n            $('#crawler-container').toggleClass('dark');\n            const isDark = $('#crawler-container').hasClass('dark');\n            $('#dark-mode-toggle').text(isDark ? '\u2600\ufe0f' : '\ud83c\udf19');\n            Config.saveSettings({ darkMode: isDark });\n            Logger.log(`\ub2e4\ud06c \ubaa8\ub4dc\uac00 ${isDark ? '\ud65c\uc131\ud654' : '\ube44\ud65c\uc131\ud654'}\ub418\uc5c8\uc2b5\ub2c8\ub2e4.`, 'success');\n        }\n\n        function applyDarkMode() {\n            if (Config.settings.darkMode) {\n                $('#crawler-container').addClass('dark');\n                $('#dark-mode-toggle').text('\u2600\ufe0f');\n            } else {\n                $('#crawler-container').removeClass('dark');\n                $('#dark-mode-toggle').text('\ud83c\udf19');\n            }\n        }\n\n        function toggleDataField() {\n            const field = $(this).attr('id').replace('field-', '');\n            Config.settings.dataFields[field] = $(this).is(':checked');\n            Config.saveSettings({ dataFields: Config.settings.dataFields });\n            Download.updateExportButtons();\n            updateDataTable();\n        }\n\n        function validateProxyInput() {\n            const proxy = $('#proxy-input').val().trim();\n            if (proxy && !Config.validateProxy(proxy)) {\n                $('#proxy-validation-message').text('\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \ud504\ub85d\uc2dc URL\uc785\ub2c8\ub2e4.').show();\n            } else {\n                $('#proxy-validation-message').hide();\n            }\n        }\n\n        return {\n            createUI,\n            updateUI,\n            updateLogs,\n            updateDataTable,\n            updatePreviousCrawlsUI,\n            updateTemplatesUI,\n            applyDarkMode\n        };\n    })();\n\n    /* ============================\n       Module: Download\n    ============================ */\n    const Download = (function() {\n        function downloadCSV() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            if (!data.length) { Logger.log('\ub2e4\uc6b4\ub85c\ub4dc\ud560 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning'); return; }\n            const headers = Object.keys(Config.settings.dataFields).filter(f => Config.settings.dataFields[f]).map(f => Utils.capitalize(getFieldName(f)));\n            const csvContent = [headers.join(\",\")]\n                .concat(data.map(item => Object.keys(Config.settings.dataFields).filter(f => Config.settings.dataFields[f]).map(f => `\"${Utils.escape(item[f], 'csv')}\"`).join(\",\")))\n                .join(\"\\n\");\n            const blob = new Blob([csvContent], { type: \"text/csv;charset=utf-8;\" });\n            const link = document.createElement(\"a\");\n            link.href = URL.createObjectURL(blob);\n            link.download = \"data.csv\";\n            document.body.appendChild(link);\n            link.click();\n            document.body.removeChild(link);\n            Logger.log('CSV \ud30c\uc77c\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function downloadJSON() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            if (!data.length) { Logger.log('\ub2e4\uc6b4\ub85c\ub4dc\ud560 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning'); return; }\n            const jsonContent = JSON.stringify(data, null, 2);\n            const blob = new Blob([jsonContent], { type: \"application/json\" });\n            const link = document.createElement(\"a\");\n            link.href = URL.createObjectURL(blob);\n            link.download = \"data.json\";\n            document.body.appendChild(link);\n            link.click();\n            document.body.removeChild(link);\n            Logger.log('JSON \ud30c\uc77c\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function downloadXML() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            if (!data.length) { Logger.log('\ub2e4\uc6b4\ub85c\ub4dc\ud560 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning'); return; }\n            let xmlContent = '<?xml version=\"1.0\" encoding=\"UTF-8\"?><data>';\n            data.forEach(item => {\n                xmlContent += '<entry>';\n                if (Config.settings.dataFields.name) xmlContent += `<name>${Utils.escape(item.name || 'No Title', 'xml')}</name>`;\n                if (Config.settings.dataFields.url) xmlContent += `<url>${Utils.escape(item.url || '', 'xml')}</url>`;\n                if (Config.settings.dataFields.description) xmlContent += `<description>${Utils.escape(item.description || 'No Description', 'xml')}</description>`;\n                if (Config.settings.dataFields.html) xmlContent += `<html><![CDATA[${item.html || ''}]]></html>`;\n                if (Config.settings.dataFields.category) xmlContent += `<category>${Utils.escape(item.category || '', 'xml')}</category>`;\n                if (Config.settings.dataFields.quality) xmlContent += `<quality>${Utils.escape(item.quality)}</quality>`;\n                if (Config.settings.dataFields.language) xmlContent += `<language>${Utils.escape(item.language || 'unknown', 'xml')}</language>`;\n                if (Config.settings.dataFields.wordCount) xmlContent += `<wordCount>${Utils.escape(item.wordCount)}</wordCount>`;\n                if (Config.settings.dataFields.metaTags) xmlContent += `<metaTags>${Utils.escape(JSON.stringify(item.metaTags), 'xml')}</metaTags>`;\n                if (Config.settings.dataFields.headings) xmlContent += `<headings>${Utils.escape(item.headings.join('; '), 'xml')}</headings>`;\n                xmlContent += '</entry>';\n            });\n            xmlContent += '</data>';\n            const blob = new Blob([xmlContent], { type: \"application/xml\" });\n            const link = document.createElement(\"a\");\n            link.href = URL.createObjectURL(blob);\n            link.download = \"data.xml\";\n            document.body.appendChild(link);\n            link.click();\n            document.body.removeChild(link);\n            Logger.log('XML \ud30c\uc77c\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function downloadXLS() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            if (!data.length) { Logger.log('\ub2e4\uc6b4\ub85c\ub4dc\ud560 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning'); return; }\n            const worksheetData = data.map(item => {\n                const entry = {};\n                if (Config.settings.dataFields.name) entry['\uc0ac\uc774\ud2b8 \uc774\ub984'] = item.name;\n                if (Config.settings.dataFields.url) entry['\uc8fc\uc18c'] = item.url;\n                if (Config.settings.dataFields.description) entry['\uc124\uba85'] = item.description;\n                if (Config.settings.dataFields.html) entry['\ud83d\udcc4 HTML \ub0b4\uc6a9'] = item.html;\n                if (Config.settings.dataFields.category) entry['\ubd84\ub958'] = item.category;\n                if (Config.settings.dataFields.quality) entry['\ud488\uc9c8\uc810\uc218'] = item.quality;\n                if (Config.settings.dataFields.language) entry['\uc5b8\uc5b4'] = item.language;\n                if (Config.settings.dataFields.wordCount) entry['\ub2e8\uc5b4 \uc218'] = item.wordCount;\n                if (Config.settings.dataFields.metaTags) entry['\uba54\ud0c0 \ud0dc\uadf8'] = JSON.stringify(item.metaTags);\n                if (Config.settings.dataFields.headings) entry['\ud5e4\ub529'] = item.headings.join('; ');\n                return entry;\n            });\n            const worksheet = XLSX.utils.json_to_sheet(worksheetData);\n            const workbook = XLSX.utils.book_new();\n            XLSX.utils.book_append_sheet(workbook, worksheet, \"Data\");\n            XLSX.writeFile(workbook, \"data.xlsx\");\n            Logger.log('XLS \ud30c\uc77c\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function getFieldName(field) {\n            const names = {\n                name: '\uc774\ub984',\n                url: '\uc8fc\uc18c',\n                description: '\uc124\uba85',\n                html: 'HTML \ub0b4\uc6a9',\n                category: '\ubd84\ub958',\n                quality: '\ud488\uc9c8\uc810\uc218',\n                language: '\uc5b8\uc5b4',\n                wordCount: '\ub2e8\uc5b4 \uc218',\n                metaTags: '\uba54\ud0c0 \ud0dc\uadf8',\n                headings: '\ud5e4\ub529'\n            };\n            return names[field] || field;\n        }\n\n        function updateExportButtons() {\n            // Placeholder for dynamic enabling/disabling of export buttons based on data fields\n        }\n\n        return {\n            downloadCSV,\n            downloadJSON,\n            downloadXML,\n            downloadXLS,\n            updateExportButtons\n        };\n    })();\n\n    /* ============================\n       Module: DataVisualization\n    ============================ */\n    const DataVisualization = (function() {\n        let chartInstance = null;\n\n        function renderChart() {\n            if (!document.getElementById('data-chart').getContext) return;\n            const ctx = document.getElementById('data-chart').getContext('2d');\n            if (chartInstance) chartInstance.destroy();\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            const categories = {};\n            data.forEach(item => { const cat = item.category || '\uae30\ud0c0'; categories[cat] = (categories[cat] || 0) + 1; });\n            const chartData = {\n                labels: Object.keys(categories),\n                datasets: [{\n                    label: '\uce74\ud14c\uace0\ub9ac\ubcc4 \ub370\uc774\ud130 \uc218',\n                    data: Object.values(categories),\n                    backgroundColor: Utils.generateColors(Object.keys(categories).length)\n                }]\n            };\n            chartInstance = new Chart(ctx, { type: 'pie', data: chartData, options: { responsive: true } });\n        }\n\n        return {\n            renderChart\n        };\n    })();\n\n    /* ============================\n       Module: Crawling\n    ============================ */\n    const Crawling = (function() {\n        let isCrawlingFlag = Config.settings.isCrawling;\n        let currentPage = parseInt(GM_getValue('currentPage', 1), 10);\n        let totalPages = Config.settings.maxPages;\n        let progress = parseFloat(GM_getValue('progress', 0));\n        let currentDepth = parseInt(GM_getValue('currentDepth', 0), 10);\n        let activeRequests = 0;\n        let requestQueue = [];\n        let previousCrawls = JSON.parse(GM_getValue('previousCrawls', '[]'));\n        let visitedUrls = new Set(JSON.parse(GM_getValue('visitedUrls', '[]')));\n        let data = JSON.parse(GM_getValue('data', '[]'));\n        let navigationObserver = null;\n\n        const searchEngineHandlers = {\n            google: {\n                extractResults: function(doc) {\n                    const results = [];\n                    doc.querySelectorAll('div.g, div[data-hveid]').forEach(item => {\n                        const name = item.querySelector('h3')?.innerText.trim() || 'No Title';\n                        const url = item.querySelector('a')?.href || '';\n                        const desc = item.querySelector('.aCOpRe, .VwiC3b')?.innerText.trim() || 'No Description';\n                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });\n                    });\n                    return results;\n                },\n                getNextPageUrl: function(doc) {\n                    return doc.querySelector('a#pnnext')?.href || null;\n                },\n                getContentContainer: function() {\n                    return '#search';\n                }\n            },\n            bing: {\n                extractResults: function(doc) {\n                    const results = [];\n                    doc.querySelectorAll('.b_algo').forEach(item => {\n                        const name = item.querySelector('h2')?.innerText.trim() || 'No Title';\n                        const url = item.querySelector('h2 a')?.href || '';\n                        const desc = item.querySelector('.b_caption p')?.innerText.trim() || 'No Description';\n                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });\n                    });\n                    return results;\n                },\n                getNextPageUrl: function(doc) {\n                    return doc.querySelector('a.sb_pagN')?.href || null;\n                },\n                getContentContainer: function() {\n                    return '#b_content';\n                }\n            },\n            yahoo: {\n                extractResults: function(doc) {\n                    const results = [];\n                    doc.querySelectorAll('.dd.algo').forEach(item => {\n                        const name = item.querySelector('h3')?.innerText.trim() || 'No Title';\n                        const url = item.querySelector('a')?.href || '';\n                        const desc = item.querySelector('.compText a')?.innerText.trim() || 'No Description';\n                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });\n                    });\n                    return results;\n                },\n                getNextPageUrl: function(doc) {\n                    return doc.querySelector('a.next')?.href || null;\n                },\n                getContentContainer: function() {\n                    return '#web';\n                }\n            },\n            // Additional search engine handlers...\n            duckduckgo: {\n                extractResults: function(doc) {\n                    const results = [];\n                    doc.querySelectorAll('.result').forEach(item => {\n                        const name = item.querySelector('h2')?.innerText.trim() || 'No Title';\n                        const url = item.querySelector('a.result__a')?.href || '';\n                        const desc = item.querySelector('.result__snippet')?.innerText.trim() || 'No Description';\n                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });\n                    });\n                    return results;\n                },\n                getNextPageUrl: function(doc) {\n                    return doc.querySelector('a.result--more__btn')?.href || null;\n                },\n                getContentContainer: function() {\n                    return '#links';\n                }\n            },\n            bingimages: {\n                extractResults: function(doc) {\n                    const results = [];\n                    doc.querySelectorAll('.imgpt a.iusc').forEach(item => {\n                        const url = item.getAttribute('m') ? JSON.parse(item.getAttribute('m')).murl : '';\n                        const name = item.getAttribute('m') ? JSON.parse(item.getAttribute('m')).t : 'No Title';\n                        const desc = item.getAttribute('m') ? JSON.parse(item.getAttribute('m')).p : 'No Description';\n                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });\n                    });\n                    return results;\n                },\n                getNextPageUrl: function(doc) {\n                    return doc.querySelector('a.sb_pagN')?.href || null;\n                },\n                getContentContainer: function() {\n                    return '#image_results';\n                }\n            },\n            // Implement other search engines similarly...\n        };\n\n        function identifySearchEngine() {\n            const host = window.location.hostname;\n            if (host.includes('google.com')) return 'google';\n            if (host.includes('bing.com') && !window.location.pathname.includes('/images')) return 'bing';\n            if (host.includes('yahoo.com')) return 'yahoo';\n            if (host.includes('duckduckgo.com')) return 'duckduckgo';\n            if (host.includes('baidu.com')) return 'baidu';\n            if (host.includes('yandex.com')) return 'yandex';\n            if (host.includes('naver.com')) return 'naver';\n            if (host.includes('daum.net')) return 'daum';\n            if (host.includes('ask.com')) return 'ask';\n            if (host.includes('ecosia.org')) return 'ecosia';\n            if (host.includes('startpage.com')) return 'startpage';\n            if (host.includes('searx.me')) return 'searx';\n            if (host.includes('dogpile.com')) return 'dogpile';\n            if (host.includes('bing.com') && window.location.pathname.includes('/images')) return 'bingimages';\n            return null;\n        }\n\n        async function startCrawling() {\n            if (isCrawlingFlag) {\n                Logger.log('\uc774\ubbf8 \ud06c\ub864\ub9c1\uc774 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.', 'warning');\n                return;\n            }\n            if (!Object.values(Config.settings.searchEngines).some(v => v)) {\n                Logger.log('\uc0ac\uc6a9\ud560 \uac80\uc0c9 \uc5d4\uc9c4\uc774 \ud558\ub098\ub3c4 \uc120\ud0dd\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.', 'error');\n                return;\n            }\n            isCrawlingFlag = true;\n            Config.saveSettings({ isCrawling: isCrawlingFlag });\n            Logger.log('\ud06c\ub864\ub9c1\uc744 \uc2dc\uc791\ud569\ub2c8\ub2e4.', 'info');\n            UI.updateLogs();\n            currentPage = Config.settings.currentPage || 1;\n            totalPages = Config.settings.maxPages;\n            currentDepth = Config.settings.currentDepth || 0;\n            if (!previousCrawls.some(crawl => crawl.status === '\uc9c4\ud589 \uc911')) {\n                previousCrawls.push({ date: new Date().toLocaleString(), count: 0, status: '\uc9c4\ud589 \uc911' });\n                GM_setValue('previousCrawls', JSON.stringify(previousCrawls));\n                UI.updatePreviousCrawlsUI();\n            }\n            setButtonStates();\n            await crawlPage(currentDepth);\n            monitorPageNavigation();\n        }\n\n        function stopCrawling() {\n            if (!isCrawlingFlag) {\n                Logger.log('\ud06c\ub864\ub9c1\uc774 \uc9c4\ud589 \uc911\uc774\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.', 'warning');\n                return;\n            }\n            isCrawlingFlag = false;\n            Config.saveSettings({ isCrawling: isCrawlingFlag });\n            Logger.log('\ud06c\ub864\ub9c1\uc774 \uc911\uc9c0\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'warning');\n            finalizeCrawl('\uc911\uc9c0\ub428');\n            UI.updateLogs();\n            setButtonStates();\n        }\n\n        function setButtonStates() {\n            $('#start-crawling').prop('disabled', isCrawlingFlag);\n            $('#stop-crawling').prop('disabled', !isCrawlingFlag);\n            $('#reset-data').prop('disabled', isCrawlingFlag);\n        }\n\n        async function crawlPage(depth) {\n            try {\n                while (isCrawlingFlag && currentPage <= totalPages && depth <= Config.settings.maxDepth) {\n                    Logger.log(`\ud398\uc774\uc9c0 ${currentPage} \ud06c\ub864\ub9c1 \uc2dc\uc791. (\uae4a\uc774: ${depth})`, 'info');\n                    if (Config.settings.respectRobots && !(await Robots.checkRobotsTxt(window.location.href))) {\n                        Logger.log(`robots.txt\uc5d0 \uc758\ud574 \ud06c\ub864\ub9c1\uc774 \uae08\uc9c0\ub41c \ud398\uc774\uc9c0: ${window.location.href}`, 'warning');\n                        break;\n                    }\n                    const searchEngine = identifySearchEngine();\n                    if (!searchEngine || !searchEngineHandlers[searchEngine]) {\n                        Logger.log(`\uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uac80\uc0c9 \uc5d4\uc9c4: ${window.location.hostname}`, 'error');\n                        break;\n                    }\n                    const doc = document;\n                    const handler = searchEngineHandlers[searchEngine];\n                    const results = handler.extractResults(doc);\n                    if (!results) { Logger.log('\uac80\uc0c9 \uacb0\uacfc\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.', 'error'); break; }\n                    results.forEach(result => {\n                        if (!isCrawlingFlag) return;\n                        const { name, url, description } = result;\n                        if (visitedUrls.has(url) || !Utils.isValidURL(url) ||\n                            (Config.settings.whitelist.length && !Config.settings.whitelist.some(d => url.includes(d))) ||\n                            Config.settings.blacklist.some(d => url.includes(d)) ||\n                            (Config.settings.includeKeywords.length && !Config.settings.includeKeywords.some(k => url.includes(k) || description.includes(k))) ||\n                            (Config.settings.excludeKeywords.some(k => url.includes(k) || description.includes(k))) ||\n                            (Config.settings.regexInclude.length && !Config.settings.regexInclude.some(r => new RegExp(r).test(url) || new RegExp(r).test(description))) ||\n                            (Config.settings.regexExclude.length && Config.settings.regexExclude.some(r => new RegExp(r).test(url) || new RegExp(r).test(description)))) {\n                            return;\n                        }\n                        requestQueue.push({ url, depth });\n                        if (activeRequests >= Config.settings.maxConcurrentRequests) return;\n                    });\n                    if (previousCrawls.length && previousCrawls[previousCrawls.length -1].status === '\uc9c4\ud589 \uc911') {\n                        previousCrawls[previousCrawls.length -1].count += results.length;\n                        GM_setValue('previousCrawls', JSON.stringify(previousCrawls));\n                        UI.updatePreviousCrawlsUI();\n                    }\n                    await processQueue();\n                    progress = (currentPage / totalPages) * 100;\n                    Config.saveSettings({ progress });\n                    UI.updateLogs();\n                    if (currentPage < totalPages && isCrawlingFlag) {\n                        const nextUrl = handler.getNextPageUrl(doc);\n                        if (nextUrl) {\n                            currentPage++;\n                            Config.saveSettings({ currentPage });\n                            depth++;\n                            Config.saveSettings({ currentDepth: depth });\n                            Logger.log(`\ub2e4\uc74c \ud398\uc774\uc9c0\ub85c \uc774\ub3d9: ${currentPage}`, 'info');\n                            await Utils.delay(Config.settings.delay + Math.random() * Config.settings.delayRandom);\n                            const nextPageContent = await Fetch.fetchPageContent(nextUrl);\n                            if (nextPageContent) {\n                                updatePageContent(nextPageContent);\n                                continue;\n                            } else {\n                                Logger.log('\ub2e4\uc74c \ud398\uc774\uc9c0 \ub0b4\uc6a9\uc744 \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.', 'error');\n                                break;\n                            }\n                        } else { Logger.log('\ub2e4\uc74c \ud398\uc774\uc9c0 \ub9c1\ud06c\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.', 'error'); break; }\n                    } else { break; }\n                }\n                if (isCrawlingFlag && currentPage > totalPages && activeRequests === 0 && requestQueue.length === 0) {\n                    Logger.log('\ud06c\ub864\ub9c1\uc774 \uc644\ub8cc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n                    finalizeCrawl('\uc644\ub8cc\ub428');\n                    isCrawlingFlag = false;\n                    Config.saveSettings({ isCrawling: isCrawlingFlag });\n                    setButtonStates();\n                    Config.saveSettings({ progress: 100 });\n                    UI.updateLogs();\n                }\n            } catch (error) {\n                Logger.log(`\ud06c\ub864\ub9c1 \uc911 \uc624\ub958 \ubc1c\uc0dd: ${error.message}`, 'error');\n                finalizeCrawl('\uc624\ub958 \ubc1c\uc0dd');\n            }\n        }\n\n        async function processQueue() {\n            while (requestQueue.length && activeRequests < Config.settings.maxConcurrentRequests && isCrawlingFlag) {\n                const { url, depth } = requestQueue.shift();\n                activeRequests++;\n                handleRequest(url, depth).finally(() => { activeRequests--; processQueue(); });\n            }\n        }\n\n        async function handleRequest(url, depth) {\n            if (!isCrawlingFlag || visitedUrls.has(url)) return;\n            try {\n                const response = await Fetch.fetchWithRetry(url, Config.settings.retryAttempts, Config.settings.retryDelay);\n                const contentType = Fetch.getContentType(response);\n                if (!Fetch.isContentTypeAllowed(contentType, Config.settings.contentTypes)) {\n                    Logger.log(`\ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc740 \ucf58\ud150\uce20 \uc720\ud615(${contentType})\uc758 URL: ${url}`, 'warning');\n                    return;\n                }\n                const parser = new DOMParser();\n                const doc = parser.parseFromString(response.responseText, 'text/html');\n                const text = doc.body ? doc.body.innerText : '';\n                if (Config.settings.enableCaptchas && Robots.detectCaptcha(doc)) { await Robots.handleCaptcha(url); return; }\n                const wc = text.split(/\\s+/).length;\n                if (wc < Config.settings.minWordCount || wc > Config.settings.maxWordCount) {\n                    Logger.log(`\ub2e8\uc5b4 \uc218 \uc870\uac74\uc5d0 \ub9de\uc9c0 \uc54a\ub294 URL: ${url} (${wc} \ub2e8\uc5b4)`, 'warning');\n                    return;\n                }\n                const quality = Evaluate.evaluateContentQuality(text);\n                const lang = doc.documentElement.lang || 'unknown';\n                const category = Classifier.classifyKeywords(text);\n                const meta = Extractor.extractMetaTags(doc);\n                const headings = Extractor.extractHeadings(doc);\n                if (category !== '\uae30\ud0c0' || quality > 100) {\n                    const entry = { url, quality, language: lang, wordCount: wc, category };\n                    if (Config.settings.dataFields.name) entry.name = Extractor.extractName(doc) || 'No Title';\n                    if (Config.settings.dataFields.description) entry.description = Extractor.extractDescription(doc) || 'No Description';\n                    if (Config.settings.dataFields.html) entry.html = response.responseText;\n                    if (Config.settings.dataFields.metaTags) entry.metaTags = meta;\n                    if (Config.settings.dataFields.headings) entry.headings = headings;\n                    data.push(entry);\n                    GM_setValue('data', JSON.stringify(data));\n                    UI.updateDataTable();\n                    Logger.log(`\ub370\uc774\ud130 \uc218\uc9d1 \uc644\ub8cc: ${url}`, 'success');\n                    if (Config.settings.dataVisualization) DataVisualization.renderChart();\n                }\n                if (depth < Config.settings.maxDepth) {\n                    const links = Extractor.extractLinks(doc);\n                    links.forEach(link => { if (!visitedUrls.has(link) && Utils.isValidURL(link)) requestQueue.push({ url: link, depth: depth + 1 }); });\n                }\n            } catch (error) {\n                Logger.log(`\ub370\uc774\ud130 \uc218\uc9d1 \uc2e4\ud328: ${url} - ${error.message}`, 'error');\n            }\n            visitedUrls.add(url);\n            GM_setValue('visitedUrls', JSON.stringify([...visitedUrls]));\n        }\n\n        function finalizeCrawl(status) {\n            if (previousCrawls.length) {\n                previousCrawls[previousCrawls.length -1].status = status;\n                GM_setValue('previousCrawls', JSON.stringify(previousCrawls));\n                UI.updatePreviousCrawlsUI();\n            }\n            if (navigationObserver) {\n                navigationObserver.disconnect();\n                navigationObserver = null;\n            }\n        }\n\n        function updateCrawlSettings() {\n            // Placeholder for any settings update needed during crawling\n        }\n\n        function updatePageContent(content) {\n            const parser = new DOMParser();\n            const doc = parser.parseFromString(content, 'text/html');\n            document.documentElement.innerHTML = doc.documentElement.innerHTML;\n        }\n\n        function monitorPageNavigation() {\n            const searchEngine = identifySearchEngine();\n            if (!searchEngine || !searchEngineHandlers[searchEngine]) return;\n            const containerSelector = searchEngineHandlers[searchEngine].getContentContainer();\n            const targetNode = document.querySelector(containerSelector);\n            if (!targetNode) {\n                Logger.log('\ud398\uc774\uc9c0 \ub0b4\ube44\uac8c\uc774\uc158 \uac10\uc2dc \ub300\uc0c1 \ub178\ub4dc\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning');\n                return;\n            }\n            navigationObserver = new MutationObserver((mutations) => {\n                for (let mutation of mutations) {\n                    if (mutation.type === 'childList') {\n                        if (isCrawlingFlag) {\n                            Logger.log('\ud398\uc774\uc9c0 \ub0b4\ube44\uac8c\uc774\uc158 \uac10\uc9c0\ub428. \ud06c\ub864\ub9c1\uc744 \uc911\uc9c0\ud569\ub2c8\ub2e4.', 'warning');\n                            Crawling.stopCrawling();\n                            break;\n                        }\n                    }\n                }\n            });\n            navigationObserver.observe(targetNode, { childList: true, subtree: true });\n            Logger.log('\ud398\uc774\uc9c0 \ub0b4\ube44\uac8c\uc774\uc158 \uac10\uc2dc\uac00 \ud65c\uc131\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'info');\n        }\n\n        return {\n            startCrawling,\n            stopCrawling,\n            isCrawling: () => isCrawlingFlag,\n            updateCrawlSettings\n        };\n    })();\n\n    /* ============================\n       Module: Fetch\n    ============================ */\n    const Fetch = (function() {\n        async function fetchWithRetry(url, attempts, delayMs) {\n            for (let i = 1; i <= attempts; i++) {\n                try {\n                    return await GM_xmlhttpRequestPromise(url);\n                } catch (e) {\n                    Logger.log(`\uc694\uccad \uc2e4\ud328 (${i}/${attempts}): ${url} - ${e.message}`, 'warning');\n                    if (i < attempts) await Utils.delay(delayMs);\n                }\n            }\n            throw new Error('\ubaa8\ub4e0 \uc7ac\uc2dc\ub3c4 \uc2dc\ub3c4\uac00 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.');\n        }\n\n        function GM_xmlhttpRequestPromise(url) {\n            return new Promise((resolve, reject) => {\n                let reqUrl = Config.settings.proxy ? `${Config.settings.proxy}?url=${encodeURIComponent(url)}` : url;\n                GM_xmlhttpRequest({\n                    method: \"GET\",\n                    url: reqUrl,\n                    headers: { 'User-Agent': getCurrentUserAgent(), 'Accept': '*/*' },\n                    timeout: Config.settings.timeout,\n                    onload: res => (res.status >= 200 && res.status < 300) ? resolve(res) : reject(new Error(`HTTP ${res.status}`)),\n                    onerror: () => reject(new Error('Request failed')),\n                    ontimeout: () => reject(new Error('Request timed out'))\n                });\n            });\n        }\n\n        function getCurrentUserAgent() {\n            const index = Config.settings.userAgentIndex % Config.settings.userAgents.length;\n            return Config.settings.userAgents[index];\n        }\n\n        function getContentType(res) {\n            return res.responseHeaders.split('\\n').find(h => h.toLowerCase().startsWith('content-type'))?.split(':')[1].trim().split(';')[0] || '';\n        }\n\n        function isContentTypeAllowed(ct, contentTypes) {\n            if (contentTypes.include.length && !contentTypes.include.includes(ct)) return false;\n            if (contentTypes.exclude.includes(ct)) return false;\n            return true;\n        }\n\n        async function fetchPageContent(url) {\n            try {\n                const response = await fetchWithRetry(url, Config.settings.retryAttempts, Config.settings.retryDelay);\n                return response.responseText;\n            } catch (error) {\n                Logger.log(`\ud398\uc774\uc9c0 \ub0b4\uc6a9 \uac00\uc838\uc624\uae30 \uc2e4\ud328: ${url} - ${error.message}`, 'error');\n                return null;\n            }\n        }\n\n        return {\n            fetchWithRetry,\n            getCurrentUserAgent,\n            getContentType,\n            isContentTypeAllowed,\n            fetchPageContent\n        };\n    })();\n\n    /* ============================\n       Module: Robots\n    ============================ */\n    const Robots = (function() {\n        async function checkRobotsTxt(url) {\n            try {\n                const parsed = new URL(url);\n                const robotsUrl = `${parsed.origin}/robots.txt`;\n                const res = await Fetch.fetchWithRetry(robotsUrl, 2, 2000);\n                return new RobotsParser(res.responseText).isAllowed(url, Fetch.getCurrentUserAgent());\n            } catch {\n                Logger.log('robots.txt \ud655\uc778 \uc2e4\ud328, \uae30\ubcf8 \ud5c8\uc6a9.', 'warning');\n                return true;\n            }\n        }\n\n        function detectCaptcha(doc) {\n            return doc.querySelector('iframe[src*=\"captcha\"], form[action*=\"captcha\"]') !== null;\n        }\n\n        async function handleCaptcha(url) {\n            Logger.log(`CAPTCHA \uac10\uc9c0\ub428: ${url}`, 'warning');\n            await Utils.delay(5000);\n            Logger.log(`CAPTCHA \ucc98\ub9ac \uc644\ub8cc: ${url}`, 'info');\n        }\n\n        class RobotsParser {\n            constructor(text) { this.rules = []; this.parse(text); }\n            parse(text) {\n                let agents = [];\n                text.split('\\n').forEach(line => {\n                    const [key, ...val] = line.split(':').map(s => s.trim());\n                    if (!key) return;\n                    if (key.toLowerCase() === 'user-agent') { agents = [val.join(':').toLowerCase()]; }\n                    else if (['disallow','allow'].includes(key.toLowerCase())) this.rules.push({ agents, type: key.toLowerCase(), path: val.join(':') });\n                });\n            }\n            isAllowed(url, ua) {\n                const parsedUrl = new URL(url);\n                const path = parsedUrl.pathname;\n                let allowed = true;\n                this.rules.forEach(rule => {\n                    if (rule.agents.includes('*') || rule.agents.includes(ua.toLowerCase())) {\n                        if (rule.type === 'disallow' && path.startsWith(rule.path)) allowed = false;\n                        if (rule.type === 'allow' && path.startsWith(rule.path)) allowed = true;\n                    }\n                });\n                return allowed;\n            }\n        }\n\n        return {\n            checkRobotsTxt,\n            detectCaptcha,\n            handleCaptcha\n        };\n    })();\n\n    /* ============================\n       Module: Extractor\n    ============================ */\n    const Extractor = (function() {\n        function extractMetaTags(doc) {\n            const metas = {};\n            doc.querySelectorAll('meta[name], meta[property]').forEach(meta => {\n                const name = meta.getAttribute('name') || meta.getAttribute('property');\n                const content = meta.getAttribute('content');\n                if (name && content) metas[name.toLowerCase()] = content;\n            });\n            return metas;\n        }\n\n        function extractHeadings(doc) {\n            const hs = [];\n            for (let i = 1; i <= 6; i++) doc.querySelectorAll(`h${i}`).forEach(h => hs.push(h.innerText.trim()));\n            return hs;\n        }\n\n        function extractName(doc) { return doc.querySelector('title')?.innerText || 'No Title'; }\n        function extractDescription(doc) { return doc.querySelector('meta[name=\"description\"]')?.getAttribute('content') || 'No Description'; }\n        function extractLinks(doc) { return Array.from(doc.querySelectorAll('a[href]')).map(a => a.href).filter(Utils.isValidURL); }\n\n        return {\n            extractMetaTags,\n            extractHeadings,\n            extractName,\n            extractDescription,\n            extractLinks\n        };\n    })();\n\n    /* ============================\n       Module: Classifier\n    ============================ */\n    const Classifier = (function() {\n        function classifyKeywords(text) {\n            const kws = combineKeywords();\n            for (const [cat, keywords] of Object.entries(kws)) {\n                if (keywords.some(kw => text.includes(kw))) return cat;\n            }\n            return '\uae30\ud0c0';\n        }\n\n        function combineKeywords() {\n            const standard = {\n                '\uad50\uc721': ['\ud559\uad50', '\uad50\uc721', '\ud559\uc0dd', '\uad50\uc218', '\uac15\uc758'],\n                '\uae30\uc220': ['\uae30\uc220', '\uc18c\ud504\ud2b8\uc6e8\uc5b4', '\ud558\ub4dc\uc6e8\uc5b4', '\ud504\ub85c\uadf8\ub798\ubc0d', '\uac1c\ubc1c'],\n                '\ub274\uc2a4': ['\ub274\uc2a4', '\uc18d\ubcf4', '\uae30\uc0ac', '\ubcf4\ub3c4'],\n                '\uc5d4\ud130\ud14c\uc778\uba3c\ud2b8': ['\uc601\ud654', '\uc74c\uc545', '\uc5f0\uc608', '\uac8c\uc784', '\ub4dc\ub77c\ub9c8'],\n                '\uc2a4\ud3ec\uce20': ['\ucd95\uad6c', '\uc57c\uad6c', '\ub18d\uad6c', '\uacbd\uae30', '\ud1a0\ub108\uba3c\ud2b8'],\n                '\ube44\uc988\ub2c8\uc2a4': ['\ube44\uc988\ub2c8\uc2a4', '\uacbd\uc81c', '\uc8fc\uc2dd', '\ud22c\uc790', '\uc2dc\uc7a5'],\n                '\uac74\uac15': ['\uac74\uac15', '\uc758\ub8cc', '\ud5ec\uc2a4', '\uc6b4\ub3d9', '\uc601\uc591'],\n                '\uacfc\ud559': ['\uacfc\ud559', '\uc5f0\uad6c', '\uc2e4\ud5d8', '\ubc1c\uacac', '\uc774\ub860'],\n                '\ubb38\ud559': ['\uc18c\uc124', '\uc2dc', '\ubb38\ud559', '\uc791\uac00', '\ucc45'],\n                '\uc5ec\ud589': ['\uc5ec\ud589', '\uad00\uad11', '\uc219\ubc15', '\ube44\ud589\uae30', '\uc5ec\uad8c']\n            };\n            const custom = Config.settings.customKeywords || {};\n            for (const [cat, kws] of Object.entries(custom)) {\n                standard[cat] = standard[cat] ? [...new Set([...standard[cat], ...kws])] : kws;\n            }\n            return standard;\n        }\n\n        return {\n            classifyKeywords\n        };\n    })();\n\n    /* ============================\n       Module: Evaluate\n    ============================ */\n    const Evaluate = (function() {\n        function evaluateContentQuality(text) { return text.split(/\\s+/).length; }\n\n        return {\n            evaluateContentQuality\n        };\n    })();\n\n    /* ============================\n       Module: Scheduler\n    ============================ */\n    const Scheduler = (function() {\n        let scheduleInterval = null;\n\n        function scheduleCrawling() {\n            clearInterval(scheduleInterval);\n            const now = new Date();\n            const [startHour, startMinute] = Config.settings.schedule.startTime.split(':').map(Number);\n            const startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), startHour, startMinute, 0);\n            if (startTime < now) startTime.setDate(startTime.getDate() + 1);\n            const initialDelay = startTime - now;\n            setTimeout(() => {\n                Crawling.startCrawling();\n                scheduleInterval = setInterval(Crawling.startCrawling, Config.settings.schedule.interval);\n            }, initialDelay);\n            Logger.log(`\ud06c\ub864\ub9c1 \uc2a4\ucf00\uc904\uc774 \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc2dc\uc791 \uc2dc\uac04: ${startTime.toLocaleString()}, \uac04\uaca9: ${Config.settings.schedule.interval / 60000}\ubd84`, 'info');\n        }\n\n        return {\n            scheduleCrawling\n        };\n    })();\n\n    /* ============================\n       Module: Download\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Module: DataVisualization\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Module: UI\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Module: Logger\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Module: Config\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Initialization\n    ============================ */\n    function init() {\n        UI.createUI();\n        UI.updateUI();\n        if (Config.settings.dataVisualization) DataVisualization.renderChart();\n        if (Config.settings.schedule.enabled) {\n            Scheduler.scheduleCrawling();\n        }\n        if (Config.settings.isCrawling) {\n            Logger.log('\ud06c\ub864\ub9c1 \uc7ac\uac1c\ub428.', 'info');\n            Crawling.startCrawling();\n        } else {\n            Logger.log('\ud06c\ub864\ub7ec \ucd08\uae30\ud654\ub428.', 'info');\n        }\n    }\n\n    window.addEventListener('load', init);\n\n})();\n```","behavior":"degenerate_repetition","conversation_hash":"b3de07119a78b938f1e9f851695986e8","conversation_redacted":false,"conversation_toxic":false,"country":"South Korea","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"o1-mini-2024-09-12","n_chars":217045,"n_turns_in_conversation":1,"n_words":13861,"source_row_offset":36430,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:17:04 GMT","turn_id":"9099555bb2242abb82672996","turn_index":0,"user_text":"\ucc3d\uc744 \uc798 \uad00\ub9ac \ud560 \uc218 \uc788\uac8c \ucc3d \uc870\uc808 \uae30\ub2a5\uc744 \ucd94\uac00\ud558\uc148.. \u3131\u3131 \uadf8\ub0e5 \uc606\uc5d0\uaebc\ub3c4 \ubcfc \uc218 \uc788\uac8c \ubc11\uc5d0 \uc606\uc73c\ub85c \uac00\uac8c \ud558\ub294 \uadf8\ub7f0 \uc2a4\ud06c\ub864\ubc14 \uac19\uc740 \uac70 \uc788\uc796\uc544 \uadf8\ub7f0 \uac70 \n\uc5ec\uae30 \ud06c\ub864\ub9c1 \ucf54\ub4dc \ubcf4\uba74 \ub2e4\uc74c \ud398\uc774\uc9c0 \ub118\uc5b4\uac04 \ub4a4\uc5d0 \uc790\ub3d9\uc73c\ub85c \ud06c\ub864\ub9c1\uc774 \ub418\uc5b4\uc57c \ud558\ub294\ub370 \ub2e4\uc74c\ud398\uc774\uc9c0\ub85c \ub118\uc5b4\uac00\ub2c8\uae50 UI \uc790\ub3d9\uc73c\ub85c \uaebc\uc9d0..        \uc774\uac70 \ubc14\ud0d5\uc73c\ub85c \ubc11\uc5d0\uac78 \uac1c\uc120\ud574  // ==UserScript==\n// @name         Advanced Web Crawler Enhanced Pro Optimized v6.3.1\n// @namespace    http://tampermonkey.net/\n// @version      6.3.1\n// @description  Further optimized and enhanced version with responsive design and other improvements.\n// @author\n// @match        https://www.google.com/search*\n// @match        https://www.bing.com/search*\n// @match        https://search.yahoo.com/search*\n// @match        https://duckduckgo.com/*\n// @match        https://www.baidu.com/s*\n// @match        https://www.yandex.com/search/*\n// @match        https://search.naver.com/search*\n// @match        https://search.daum.net/search*\n// @match        https://www.ask.com/*\n// @match        https://www.ecosia.org/search*\n// @match        https://www.startpage.com/*\n// @match        https://searx.me/*\n// @match        https://www.dogpile.com/*\n// @match        https://www.bing.com/images*\n// @grant        GM_xmlhttpRequest\n// @grant        GM_setValue\n// @grant        GM_getValue\n// @grant        GM_deleteValue\n// @connect      *\n// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js\n// @require      https://cdn.jsdelivr.net/npm/chart.js\n// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js\n// ==/UserScript==\n\n(function() {\n    'use strict';\n\n    /* ============================\n       Module: Config\n    ============================ */\n    const Config = (function() {\n        const DEFAULT_SETTINGS = {\n            delay: 2000,\n            delayRandom: 500,\n            maxPages: 3,\n            userAgents: [\n                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)',\n                'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',\n                'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)',\n                'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',\n                'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X) AppleWebKit/605.1.15',\n                'AdvancedWebCrawler/6.3 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'\n            ],\n            userAgentIndex: 0,\n            blacklist: [],\n            whitelist: [],\n            contentTypes: {\n                include: ['text/html'],\n                exclude: ['application/pdf', 'image/jpeg', 'image/png', 'video/mp4']\n            },\n            timeout: 8000,\n            includeKeywords: [],\n            excludeKeywords: [],\n            language: [],\n            minWordCount: 100,\n            maxWordCount: 10000,\n            maxDepth: 2,\n            proxy: '',\n            regexInclude: [],\n            regexExclude: [],\n            dataFields: {\n                name: true,\n                url: true,\n                description: true,\n                html: true,\n                category: true,\n                quality: true,\n                language: true,\n                wordCount: true,\n                metaTags: false,\n                headings: false\n            },\n            schedule: {\n                enabled: false,\n                startTime: '00:00',\n                interval: 3600000\n            },\n            searchEngines: {\n                google: true,\n                bing: true,\n                yahoo: true,\n                duckduckgo: true,\n                baidu: true,\n                yandex: true,\n                naver: true,\n                daum: true,\n                ask: true,\n                ecosia: true,\n                startpage: true,\n                searx: true,\n                dogpile: true,\n                bingimages: true\n            },\n            retryAttempts: 3,\n            retryDelay: 2000,\n            respectRobots: true,\n            maxConcurrentRequests: 3,\n            dataVisualization: true,\n            exportFormats: {\n                csv: true,\n                json: true,\n                xml: true,\n                xls: true\n            },\n            advancedLogging: true,\n            enableCaptchas: false,\n            customKeywords: {},\n            templates: {},\n            currentTab: 'logs',\n            isCrawling: false,\n            darkMode: false\n        };\n\n        let settings = { ...DEFAULT_SETTINGS, ...getSavedSettings() };\n\n        function getSavedSettings() {\n            return GM_getValue('settings', DEFAULT_SETTINGS);\n        }\n\n        function saveSettings(newSettings) {\n            GM_setValue('settings', newSettings);\n            settings = { ...settings, ...newSettings };\n        }\n\n        function resetSettings() {\n            saveSettings(DEFAULT_SETTINGS);\n            settings = { ...DEFAULT_SETTINGS };\n        }\n\n        function saveTemplate(name) {\n            settings.templates[name] = { ...settings };\n            saveSettings(settings);\n            Logger.log(`\uc124\uc815 \ud15c\ud50c\ub9bf '${name}'\uc774(\uac00) \uc800\uc7a5\ub418\uc5c8\uc2b5\ub2c8\ub2e4.`, 'success');\n        }\n\n        function loadTemplate(name) {\n            if (settings.templates[name]) {\n                saveSettings(settings.templates[name]);\n                Logger.log(`\uc124\uc815 \ud15c\ud50c\ub9bf '${name}'\uc774(\uac00) \ubd88\ub7ec\uc640\uc84c\uc2b5\ub2c8\ub2e4.`, 'success');\n            } else {\n                Logger.log(`\uc124\uc815 \ud15c\ud50c\ub9bf '${name}'\uc744(\ub97c) \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.`, 'error');\n            }\n        }\n\n        function validateProxy(proxyUrl) {\n            try {\n                const url = new URL(proxyUrl);\n                return url.protocol === 'http:' || url.protocol === 'https:';\n            } catch (e) {\n                return false;\n            }\n        }\n\n        return {\n            DEFAULT_SETTINGS,\n            get settings() { return settings; },\n            saveSettings,\n            resetSettings,\n            saveTemplate,\n            loadTemplate,\n            validateProxy\n        };\n    })();\n\n    /* ============================\n       Module: Logger\n    ============================ */\n    const Logger = (function() {\n        let logs = JSON.parse(GM_getValue('logs', '[]'));\n\n        function log(message, type = 'info') {\n            const timestamp = new Date().toLocaleString();\n            logs.push({ timestamp, message, type });\n            if (logs.length > 1000) logs.shift();\n            GM_setValue('logs', JSON.stringify(logs));\n            UI.updateLogs();\n            if (Config.settings.advancedLogging) {\n                const styles = {\n                    'info': 'color: blue;',\n                    'warning': 'color: orange;',\n                    'error': 'color: red;',\n                    'success': 'color: green; font-weight: bold;',\n                    'debug': 'color: purple;'\n                };\n                console.log(`%c[${timestamp}] [${type.toUpperCase()}] ${message}`, styles[type] || '');\n            }\n        }\n\n        function getLogs() {\n            return logs;\n        }\n\n        function clearLogs() {\n            logs = [];\n            GM_setValue('logs', JSON.stringify(logs));\n            UI.updateLogs();\n        }\n\n        return {\n            log,\n            getLogs,\n            clearLogs\n        };\n    })();\n\n    /* ============================\n       Module: Utils\n    ============================ */\n    const Utils = (function() {\n        function escape(str, type = 'html') {\n            const map = {\n                html: { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#039;' },\n                csv: { '\"': '\"\"' },\n                xml: { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#039;' },\n            };\n            return String(str).replace(new RegExp(`[${Object.keys(map[type]).join('')}]`, 'g'), match => map[type][match]);\n        }\n\n        function capitalize(str) {\n            return str.charAt(0).toUpperCase() + str.slice(1);\n        }\n\n        function delay(ms) {\n            return new Promise(res => setTimeout(res, ms));\n        }\n\n        function generateColors(count) {\n            return Array.from({length: count}, (_, i) => `hsl(${i * (360 / count)}, 70%, 50%)`);\n        }\n\n        function parseCustomKeywords(text) {\n            const lines = text.split('\\n').map(l => l.trim()).filter(l => l);\n            const ck = {};\n            lines.forEach(line => {\n                const [cat, kws] = line.split(':');\n                if (cat && kws) ck[cat.trim()] = kws.split(',').map(kw => kw.trim()).filter(kw => kw);\n            });\n            return ck;\n        }\n\n        function formatCustomKeywords(ck) {\n            return Object.entries(ck).map(([c, kws]) => `${c}:${kws.join(',')}`).join('\\n');\n        }\n\n        function isValidURL(url) {\n            try { new URL(url); return true; }\n            catch { return false; }\n        }\n\n        return {\n            escape,\n            capitalize,\n            delay,\n            generateColors,\n            parseCustomKeywords,\n            formatCustomKeywords,\n            isValidURL\n        };\n    })();\n\n    /* ============================\n       Module: UI\n    ============================ */\n    const UI = (function() {\n        function createUI() {\n            const style = `\n                #crawler-container {\n                    position: fixed;\n                    top: 10px;\n                    right: 10px;\n                    width: 90%;\n                    max-width: 600px;\n                    height: 80%;\n                    background: var(--bg-color, #fff);\n                    border: 1px solid #ccc;\n                    z-index: 10000;\n                    font-family: Arial, sans-serif;\n                    box-shadow: 0 4px 8px rgba(0,0,0,0.2);\n                    overflow: hidden;\n                    resize: both;\n                    border-radius: 8px;\n                    min-width: 300px;\n                    min-height: 300px;\n                }\n                #crawler-container.dark {\n                    --bg-color: #2b2b2b;\n                    color: #f1f1f1;\n                    border-color: #555;\n                }\n                #crawler-header {\n                    display: flex;\n                    flex-direction: column;\n                    justify-content: space-between;\n                    align-items: flex-start;\n                    background: var(--header-bg, #f1f1f1);\n                    border-bottom: 1px solid #ccc;\n                    padding: 10px;\n                    cursor: move;\n                }\n                #crawler-container.dark #crawler-header {\n                    --header-bg: #3c3c3c;\n                    border-color: #555;\n                }\n                #crawler-minimize {\n                    align-self: flex-end;\n                    background: none;\n                    border: none;\n                    font-size: 18px;\n                    cursor: pointer;\n                    color: inherit;\n                }\n                #crawler-tabs {\n                    display: flex;\n                    flex-wrap: wrap;\n                    width: 100%;\n                }\n                .crawler-tab { flex: 1 1 auto; padding: 10px; text-align: center; cursor: pointer; font-weight: bold; }\n                .crawler-tab.active { background: var(--active-tab-bg, #ddd); }\n                #crawler-container.dark .crawler-tab.active { background: #555; }\n                #crawler-content { padding: 10px; overflow-y: auto; height: calc(100% - 80px); }\n                table { width: 100%; border-collapse: collapse; }\n                th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }\n                th { background-color: var(--th-bg, #f2f2f2); cursor: pointer; }\n                #crawler-container.dark th { background-color: #444; }\n                tr:hover { background-color: #f5f5f5; }\n                #crawler-container.dark tr:hover { background-color: #555; }\n                .progress-bar { width: 100%; background-color: #f3f3f3; border: 1px solid #ccc; border-radius: 5px; overflow: hidden; margin-bottom: 10px; }\n                .progress-bar-fill { height: 20px; background-color: #4caf50; width: 0%; transition: width 0.2s; }\n                .modal { display: none; position: fixed; z-index: 10001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); }\n                .modal-content { background-color: var(--modal-bg, #fff); margin: 5% auto; padding: 20px; border: 1px solid #888; width: 90%; max-width: 800px; max-height: 80%; overflow-y: auto; border-radius: 8px; }\n                #crawler-container.dark .modal-content { --modal-bg: #3c3c3c; color: #f1f1f1; border-color: #555; }\n                .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }\n                .close:hover, .close:focus { color: black; text-decoration: none; cursor: pointer; }\n                .control-buttons { margin: 10px 0; display: flex; flex-wrap: wrap; gap: 10px; }\n                .control-buttons button { padding: 8px 12px; cursor: pointer; border: none; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px; flex: 1 1 auto; min-width: 80px; }\n                .control-buttons button:disabled { background-color: #a5d6a7; cursor: not-allowed; }\n                .status-text { margin-top: 10px; font-weight: bold; font-size: 14px; }\n                .settings-section { margin-bottom: 15px; }\n                .settings-section label { display: block; margin-bottom: 5px; font-weight: bold; }\n                .settings-section input[type=\"text\"], .settings-section input[type=\"number\"], .settings-section textarea, .settings-section select { width: 100%; padding: 5px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }\n                .settings-section.dark .settings-section input[type=\"text\"], .settings-section.dark .settings-section input[type=\"number\"], .settings-section.dark .settings-section textarea, .settings-section.dark .settings-section select {\n                    background-color: #555;\n                    color: #f1f1f1;\n                    border-color: #777;\n                }\n                .settings-section .checkbox-group { display: flex; flex-wrap: wrap; gap: 10px; }\n                .settings-section .checkbox-group label { margin-right: 10px; font-size: 14px; }\n                .settings-section .radio-group { display: flex; flex-wrap: wrap; gap: 10px; }\n                .settings-section .radio-group label { margin-right: 10px; font-size: 14px; }\n                .tooltip { position: relative; display: inline-block; cursor: help; font-size: 14px; }\n                .tooltip .tooltiptext {\n                    visibility: hidden;\n                    width: 220px;\n                    background-color: #555;\n                    color: #fff;\n                    text-align: center;\n                    border-radius: 6px;\n                    padding: 5px;\n                    position: absolute;\n                    z-index: 1;\n                    bottom: 125%;\n                    left: 50%;\n                    margin-left: -110px;\n                    opacity: 0;\n                    transition: opacity 0.3s;\n                }\n                .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; }\n                #crawler-container::-webkit-resizer { background: #f1f1f1; border: none; }\n                #data-visualization { margin-top: 20px; }\n                #data-chart { width: 100%; height: 300px; }\n                #modal-html-content { white-space: pre-wrap; word-wrap: break-word; background-color: #f9f9f9; padding: 10px; border-radius: 4px; }\n                #crawler-tabs .crawler-tab:last-child { flex: 1; }\n                #previous-crawls { margin-top: 20px; }\n                #previous-crawls table { width: 100%; }\n                #previous-crawls th, #previous-crawls td { padding: 5px; }\n                #reset-data { background-color: #f44336; }\n                .spinner {\n                    border: 4px solid #f3f3f3;\n                    border-top: 4px solid #3498db;\n                    border-radius: 50%;\n                    width: 24px;\n                    height: 24px;\n                    animation: spin 2s linear infinite;\n                    display: inline-block;\n                    vertical-align: middle;\n                }\n                @keyframes spin {\n                    0% { transform: rotate(0deg); }\n                    100% { transform: rotate(360deg); }\n                }\n                .error-message {\n                    color: red;\n                    font-weight: bold;\n                    margin-top: 10px;\n                }\n                .success-message {\n                    color: green;\n                    font-weight: bold;\n                    margin-top: 10px;\n                }\n                .dark-mode-toggle {\n                    background: none;\n                    border: none;\n                    cursor: pointer;\n                    font-size: 18px;\n                    color: inherit;\n                }\n                @media (max-width: 768px) {\n                    #crawler-container {\n                        width: 100%;\n                        height: 100%;\n                        top: 0;\n                        right: 0;\n                        border-radius: 0;\n                    }\n                    #crawler-header {\n                        flex-direction: row;\n                        align-items: center;\n                    }\n                    #crawler-tabs {\n                        flex-direction: row;\n                        overflow-x: auto;\n                    }\n                    .crawler-tab {\n                        flex: 0 0 auto;\n                    }\n                }\n            `;\n            $('head').append(`<style>${style}</style>`);\n            const container = $(`\n                <div id=\"crawler-container\">\n                    <div id=\"crawler-header\">\n                        <div id=\"crawler-tabs\">\n                            <div class=\"crawler-tab\" data-tab=\"logs\" title=\"\ub85c\uadf8 \ubcf4\uae30\">\ud83d\udcdc \ub85c\uadf8</div>\n                            <div class=\"crawler-tab\" data-tab=\"data\" title=\"\uc218\uc9d1\ub41c \ub370\uc774\ud130 \ubcf4\uae30\">\ud83d\udcca \ub370\uc774\ud130</div>\n                            <div class=\"crawler-tab\" data-tab=\"settings\" title=\"\uc124\uc815 \ubcc0\uacbd\">\u2699\ufe0f \uc124\uc815</div>\n                            <div class=\"crawler-tab\" data-tab=\"previous-crawls\" title=\"\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d\">\ud83d\udcc1 \uae30\ub85d</div>\n                            <div class=\"crawler-tab\" data-tab=\"templates\" title=\"\uc124\uc815 \ud15c\ud50c\ub9bf\">\ud83d\udcc2 \ud15c\ud50c\ub9bf</div>\n                            <button id=\"dark-mode-toggle\" class=\"dark-mode-toggle\" title=\"\ud14c\ub9c8 \uc804\ud658\">\ud83c\udf19</button>\n                        </div>\n                        <button id=\"crawler-minimize\" title=\"\ucc3d \ucd5c\uc18c\ud654\">\u2500</button>\n                    </div>\n                    <div id=\"crawler-content\">\n                        <div id=\"tab-logs\" style=\"display: none;\">\n                            <div class=\"progress-bar\">\n                                <div class=\"progress-bar-fill\" style=\"width: 0%;\"></div>\n                            </div>\n                            <div class=\"status-text\">\uc9c4\ud589\ub960: 0% (0/0 \ud398\uc774\uc9c0)</div>\n                            <div class=\"control-buttons\">\n                                <button id=\"start-crawling\" title=\"\ud06c\ub864\ub9c1\uc744 \uc2dc\uc791\ud569\ub2c8\ub2e4.\">\u25b6 \uc2dc\uc791</button>\n                                <button id=\"stop-crawling\" title=\"\ud06c\ub864\ub9c1\uc744 \uc911\uc9c0\ud569\ub2c8\ub2e4.\" disabled>\u25a0 \uc911\uc9c0</button>\n                                ${Config.settings.exportFormats.csv ? `<button id=\"download-csv\" title=\"CSV \ub2e4\uc6b4\ub85c\ub4dc\">\ud83d\udce5 CSV</button>` : ''}\n                                ${Config.settings.exportFormats.json ? `<button id=\"download-json\" title=\"JSON \ub2e4\uc6b4\ub85c\ub4dc\">\ud83d\udce5 JSON</button>` : ''}\n                                ${Config.settings.exportFormats.xml ? `<button id=\"download-xml\" title=\"XML \ub2e4\uc6b4\ub85c\ub4dc\">\ud83d\udce5 XML</button>` : ''}\n                                ${Config.settings.exportFormats.xls ? `<button id=\"download-xls\" title=\"XLS \ub2e4\uc6b4\ub85c\ub4dc\">\ud83d\udce5 XLS</button>` : ''}\n                                <button id=\"reset-data\" title=\"\ubaa8\ub4e0 \ud06c\ub864\ub9c1 \ub370\uc774\ud130\ub97c \ucd08\uae30\ud654\ud569\ub2c8\ub2e4.\">\ud83d\uddd1\ufe0f \ucd08\uae30\ud654</button>\n                            </div>\n                            <div id=\"log-messages\" style=\"height: 200px; overflow-y: auto; background: var(--log-bg, #f9f9f9); padding: 10px; border: 1px solid #ccc; border-radius: 4px;\">\n                                \ub85c\uadf8 \ub0b4\uc6a9\uc774 \uc5ec\uae30\uc5d0 \ud45c\uc2dc\ub429\ub2c8\ub2e4.\n                            </div>\n                            <div id=\"error-message\" class=\"error-message\" style=\"display: none;\"></div>\n                            <div id=\"success-message\" class=\"success-message\" style=\"display: none;\"></div>\n                        </div>\n                        <div id=\"tab-data\" style=\"display: none;\">\n                            <input type=\"text\" id=\"search-filter\" placeholder=\"\uac80\uc0c9 \ud544\ud130...\" style=\"width: 100%; padding: 5px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;\">\n                            <table>\n                                <thead>\n                                    <tr>\n                                        ${Config.settings.dataFields.name ? '<th data-sort=\"name\">\uc0ac\uc774\ud2b8 \uc774\ub984</th>' : ''}\n                                        ${Config.settings.dataFields.url ? '<th data-sort=\"url\">\uc8fc\uc18c</th>' : ''}\n                                        ${Config.settings.dataFields.description ? '<th data-sort=\"description\">\uc124\uba85</th>' : ''}\n                                        ${Config.settings.dataFields.html ? '<th>\ud83d\udcc4 HTML \ub0b4\uc6a9</th>' : ''}\n                                        ${Config.settings.dataFields.category ? '<th data-sort=\"category\">\ubd84\ub958</th>' : ''}\n                                        ${Config.settings.dataFields.quality ? '<th data-sort=\"quality\">\ud488\uc9c8\uc810\uc218</th>' : ''}\n                                        ${Config.settings.dataFields.language ? '<th data-sort=\"language\">\uc5b8\uc5b4</th>' : ''}\n                                        ${Config.settings.dataFields.wordCount ? '<th data-sort=\"wordCount\">\ub2e8\uc5b4 \uc218</th>' : ''}\n                                        ${Config.settings.dataFields.metaTags ? '<th>\uba54\ud0c0 \ud0dc\uadf8</th>' : ''}\n                                        ${Config.settings.dataFields.headings ? '<th>\ud5e4\ub529</th>' : ''}\n                                    </tr>\n                                </thead>\n                                <tbody id=\"data-table-body\"></tbody>\n                            </table>\n                            ${Config.settings.dataVisualization ? `\n                                <div id=\"data-visualization\">\n                                    <h3>\ub370\uc774\ud130 \uc2dc\uac01\ud654</h3>\n                                    <canvas id=\"data-chart\"></canvas>\n                                </div>\n                            ` : ''}\n                        </div>\n                        <div id=\"tab-settings\" style=\"display: none;\">\n                            <h3>\uc124\uc815</h3>\n                            <div class=\"settings-section\">\n                                <label for=\"delay-input\">\ud06c\ub864\ub9c1 \ub51c\ub808\uc774 (ms):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uae30\ubcf8 \ub51c\ub808\uc774\uacfc \ub51c\ub808\uc774 \ub79c\ub364 \uac12\uc744 \uc870\ud569\ud558\uc5ec \uc694\uccad \uc0ac\uc774\uc758 \ub51c\ub808\uc774\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"delay-input\" value=\"${Config.settings.delay}\" min=\"500\" step=\"500\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"delay-random-input\">\ub51c\ub808\uc774 \ub79c\ub364 \ubc94\uc704 (ms):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ub51c\ub808\uc774\uc5d0 \ubb34\uc791\uc704 \uc694\uc18c\ub97c \ucd94\uac00\ud558\uc5ec \ubcf4\ub2e4 \uc790\uc5f0\uc2a4\ub7ec\uc6b4 \uc694\uccad\uc744 \ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"delay-random-input\" value=\"${Config.settings.delayRandom}\" min=\"0\" step=\"500\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"max-pages-input\">\ucd5c\ub300 \ud06c\ub864\ub9c1 \ud398\uc774\uc9c0 \uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\ud560 \ucd5c\ub300 \ud398\uc774\uc9c0 \uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"max-pages-input\" value=\"${Config.settings.maxPages}\" min=\"1\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"user-agent-select\">\uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8(User-Agent):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc5d0 \uc0ac\uc6a9\ud560 \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\ub97c \uc120\ud0dd\ud558\uac70\ub098 \ucd94\uac00\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <select id=\"user-agent-select\" multiple size=\"6\">\n                                    ${Config.settings.userAgents.map((ua, idx) => `<option value=\"${idx}\" ${idx === Config.settings.userAgentIndex ? 'selected' : ''}>${ua}</option>`).join('')}\n                                </select>\n                                <button id=\"add-user-agent\" style=\"margin-top: 5px; padding: 5px 10px; background-color: #2196F3; color: white; border-radius: 4px; font-size: 12px;\">\ucd94\uac00</button>\n                                <button id=\"remove-user-agent\" style=\"margin-top: 5px; padding: 5px 10px; background-color: #f44336; color: white; border-radius: 4px; font-size: 12px;\">\uc81c\uac70</button>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"content-include-input\">\ucf58\ud150\uce20 \uc720\ud615 \ud3ec\ud568:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\ud560 \ucf58\ud150\uce20 \uc720\ud615\uc744 \uc27c\ud45c\ub85c \uad6c\ubd84\ud558\uc5ec \uc785\ub825\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"text\" id=\"content-include-input\" value=\"${Config.settings.contentTypes.include.join(', ')}\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"content-exclude-input\">\ucf58\ud150\uce20 \uc720\ud615 \uc81c\uc678:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc5d0\uc11c \uc81c\uc678\ud560 \ucf58\ud150\uce20 \uc720\ud615\uc744 \uc27c\ud45c\ub85c \uad6c\ubd84\ud558\uc5ec \uc785\ub825\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"text\" id=\"content-exclude-input\" value=\"${Config.settings.contentTypes.exclude.join(', ')}\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"timeout-input\">\ud06c\ub864\ub9c1 \ud0c0\uc784\uc544\uc6c3 (ms):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc694\uccad\uc774 \uc644\ub8cc\ub418\uae30\uae4c\uc9c0 \uae30\ub2e4\ub9b4 \ucd5c\ub300 \uc2dc\uac04\uc744 \ubc00\ub9ac\ucd08 \ub2e8\uc704\ub85c \uc124\uc815\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"timeout-input\" value=\"${Config.settings.timeout}\" min=\"1000\" step=\"1000\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"include-keywords-input\">\ud3ec\ud568\ud560 \ud0a4\uc6cc\ub4dc:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\ud560 \ud398\uc774\uc9c0\uc5d0 \ubc18\ub4dc\uc2dc \ud3ec\ud568\ub418\uc5b4\uc57c \ud558\ub294 \ud0a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud558\uc138\uc694. \uac01 \ud0a4\uc6cc\ub4dc\ub294 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"include-keywords-input\" rows=\"3\">${Config.settings.includeKeywords.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"exclude-keywords-input\">\uc81c\uc678\ud560 \ud0a4\uc6cc\ub4dc:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\ud560 \ud398\uc774\uc9c0\uc5d0 \ud3ec\ud568\ub418\uba74 \uc548 \ub418\ub294 \ud0a4\uc6cc\ub4dc\ub97c \uc785\ub825\ud558\uc138\uc694. \uac01 \ud0a4\uc6cc\ub4dc\ub294 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"exclude-keywords-input\" rows=\"3\">${Config.settings.excludeKeywords.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"regex-include-input\">\ud3ec\ud568\ud560 \ud0a4\uc6cc\ub4dc (\uc815\uaddc \ud45c\ud604\uc2dd):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud3ec\ud568\ud560 \ud0a4\uc6cc\ub4dc\uc758 \uc815\uaddc \ud45c\ud604\uc2dd\uc744 \uc785\ub825\ud558\uc138\uc694. \uac01 \ud328\ud134\uc740 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"regex-include-input\" rows=\"3\">${Config.settings.regexInclude.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"regex-exclude-input\">\uc81c\uc678\ud560 \ud0a4\uc6cc\ub4dc (\uc815\uaddc \ud45c\ud604\uc2dd):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc81c\uc678\ud560 \ud0a4\uc6cc\ub4dc\uc758 \uc815\uaddc \ud45c\ud604\uc2dd\uc744 \uc785\ub825\ud558\uc138\uc694. \uac01 \ud328\ud134\uc740 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"regex-exclude-input\" rows=\"3\">${Config.settings.regexExclude.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"language-select\">\uc5b8\uc5b4 \ud544\ud130\ub9c1:</label>\n                                <select id=\"language-select\" multiple size=\"10\">\n                                    ${[\n                                        { value: 'en', text: '\uc601\uc5b4 (en)' }, { value: 'ko', text: '\ud55c\uad6d\uc5b4 (ko)' },\n                                        { value: 'zh', text: '\uc911\uad6d\uc5b4 (zh)' }, { value: 'ja', text: '\uc77c\ubcf8\uc5b4 (ja)' },\n                                        { value: 'ru', text: '\ub7ec\uc2dc\uc544\uc5b4 (ru)' }, { value: 'es', text: '\uc2a4\ud398\uc778\uc5b4 (es)' },\n                                        { value: 'fr', text: '\ud504\ub791\uc2a4\uc5b4 (fr)' }, { value: 'de', text: '\ub3c5\uc77c\uc5b4 (de)' },\n                                        { value: 'it', text: '\uc774\ud0c8\ub9ac\uc544\uc5b4 (it)' }, { value: 'pt', text: '\ud3ec\ub974\ud22c\uac08\uc5b4 (pt)' },\n                                        { value: 'ar', text: '\uc544\ub78d\uc5b4 (ar)' }, { value: 'hi', text: '\ud78c\ub514\uc5b4 (hi)' },\n                                        { value: 'bn', text: '\ubcb5\uace8\uc5b4 (bn)' }, { value: 'pa', text: '\ud380\uc790\ube0c\uc5b4 (pa)' },\n                                        { value: 'nl', text: '\ub124\ub35c\ub780\ub4dc\uc5b4 (nl)' }, { value: 'sv', text: '\uc2a4\uc6e8\ub374\uc5b4 (sv)' },\n                                        { value: 'tr', text: '\ud130\ud0a4\uc5b4 (tr)' }, { value: 'vi', text: '\ubca0\ud2b8\ub0a8\uc5b4 (vi)' },\n                                        { value: 'pl', text: '\ud3f4\ub780\ub4dc\uc5b4 (pl)' }, { value: 'th', text: '\ud0dc\uad6d\uc5b4 (th)' }\n                                    ].map(lang => `<option value=\"${lang.value}\" ${Config.settings.language.includes(lang.value) ? 'selected' : ''}>${lang.text}</option>`).join('')}\n                                </select>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"min-word-count-input\">\ucd5c\uc18c \ub2e8\uc5b4 \uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc218\uc9d1\ud560 \ud398\uc774\uc9c0\uc758 \ucd5c\uc18c \ub2e8\uc5b4 \uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"min-word-count-input\" value=\"${Config.settings.minWordCount}\" min=\"10\" step=\"10\">\n                                <label for=\"max-word-count-input\">\ucd5c\ub300 \ub2e8\uc5b4 \uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc218\uc9d1\ud560 \ud398\uc774\uc9c0\uc758 \ucd5c\ub300 \ub2e8\uc5b4 \uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"max-word-count-input\" value=\"${Config.settings.maxWordCount}\" min=\"100\" step=\"100\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"max-depth-input\">\ucd5c\ub300 \ud06c\ub864\ub9c1 \uae4a\uc774:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ub9c1\ud06c\ub97c \ucd94\uc801\ud560 \ucd5c\ub300 \uae4a\uc774\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"max-depth-input\" value=\"${Config.settings.maxDepth}\" min=\"1\" step=\"1\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"proxy-input\">\ud504\ub85d\uc2dc \uc11c\ubc84:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud504\ub85d\uc2dc \uc11c\ubc84 \uc8fc\uc18c\ub97c \uc785\ub825\ud558\uc138\uc694. \uc608: http://proxyserver:port</span>\n                                    </span>\n                                </label>\n                                <input type=\"text\" id=\"proxy-input\" value=\"${Config.settings.proxy}\" placeholder=\"http://proxyserver:port\">\n                                <div id=\"proxy-validation-message\" class=\"error-message\" style=\"display: none;\"></div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\uc218\uc9d1\ud560 \ub370\uc774\ud130 \ud544\ub4dc:</label>\n                                <div class=\"checkbox-group\">\n                                    ${Object.keys(Config.settings.dataFields).map(field => `<label><input type=\"checkbox\" id=\"field-${field}\" ${Config.settings.dataFields[field] ? 'checked' : ''}> ${Utils.capitalize(getFieldName(field))}</label>`).join('')}\n                                </div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"retry-attempts-input\">\uc7ac\uc2dc\ub3c4 \ud69f\uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc694\uccad \uc2e4\ud328 \uc2dc \uc7ac\uc2dc\ub3c4\ud560 \ud69f\uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"retry-attempts-input\" value=\"${Config.settings.retryAttempts}\" min=\"0\" step=\"1\">\n                                <label for=\"retry-delay-input\">\uc7ac\uc2dc\ub3c4 \ub51c\ub808\uc774 (ms):\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc7ac\uc2dc\ub3c4 \uc2dc \ub300\uae30\ud560 \uc2dc\uac04\uc744 \ubc00\ub9ac\ucd08 \ub2e8\uc704\ub85c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"retry-delay-input\" value=\"${Config.settings.retryDelay}\" min=\"1000\" step=\"500\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\ud06c\ub864\ub9c1 \uc2a4\ucf00\uc904\ub9c1:</label>\n                                <div class=\"checkbox-group\">\n                                    <label><input type=\"checkbox\" id=\"schedule-enabled\" ${Config.settings.schedule.enabled ? 'checked' : ''}> \uc2a4\ucf00\uc904\ub9c1 \ud65c\uc131\ud654</label>\n                                </div>\n                                <div id=\"schedule-options\" style=\"display: ${Config.settings.schedule.enabled ? 'block' : 'none'}; margin-top: 10px;\">\n                                    <label for=\"start-time-input\">\uc2dc\uc791 \uc2dc\uac04 (HH:MM):\n                                        <span class=\"tooltip\">\u2139\ufe0f\n                                            <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc744 \uc2dc\uc791\ud560 \uc2dc\uac04\uc744 \uc124\uc815\ud558\uc138\uc694.</span>\n                                        </span>\n                                    </label>\n                                    <input type=\"time\" id=\"start-time-input\" value=\"${Config.settings.schedule.startTime}\">\n                                    <label for=\"interval-input\">\ud06c\ub864\ub9c1 \uac04\uaca9 (\ubd84):\n                                        <span class=\"tooltip\">\u2139\ufe0f\n                                            <span class=\"tooltiptext\">\ud06c\ub864\ub9c1 \uc791\uc5c5 \uac04\uc758 \uac04\uaca9\uc744 \ubd84 \ub2e8\uc704\ub85c \uc124\uc815\ud558\uc138\uc694.</span>\n                                        </span>\n                                    </label>\n                                    <input type=\"number\" id=\"interval-input\" value=\"${Config.settings.schedule.interval / 60000}\" min=\"1\" step=\"1\">\n                                </div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\ud06c\ub864\ub9c1\ud560 \uac80\uc0c9 \uc5d4\uc9c4 \uc120\ud0dd:</label>\n                                <div class=\"checkbox-group\">\n                                    ${Object.keys(Config.settings.searchEngines).map(engine => `<label><input type=\"checkbox\" id=\"engine-${engine}\" ${Config.settings.searchEngines[engine] ? 'checked' : ''}> ${Utils.capitalize(engine)}</label>`).join('')}\n                                </div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"blacklist-input\">\ube14\ub799\ub9ac\uc2a4\ud2b8 \ub3c4\uba54\uc778:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc5d0\uc11c \uc81c\uc678\ud560 \ub3c4\uba54\uc778\uc744 \uc785\ub825\ud558\uc138\uc694. \uac01 \ub3c4\uba54\uc778\uc740 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"blacklist-input\" rows=\"5\">${Config.settings.blacklist.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"whitelist-input\">\ud654\uc774\ud2b8\ub9ac\uc2a4\ud2b8 \ub3c4\uba54\uc778:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud06c\ub864\ub9c1\uc744 \ud5c8\uc6a9\ud560 \ub3c4\uba54\uc778\uc744 \uc785\ub825\ud558\uc138\uc694. \uac01 \ub3c4\uba54\uc778\uc740 \uc904\ubc14\uafc8\uc73c\ub85c \uad6c\ubd84\ub429\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"whitelist-input\" rows=\"5\">${Config.settings.whitelist.join('\\n')}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"respect-robots-checkbox\">robots.txt \uc900\uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ub300\uc0c1 \uc0ac\uc774\ud2b8\uc758 robots.txt \uaddc\uce59\uc744 \uc900\uc218\ud558\uc5ec \ud06c\ub864\ub9c1\uc744 \uc9c4\ud589\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"checkbox\" id=\"respect-robots-checkbox\" ${Config.settings.respectRobots ? 'checked' : ''}>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"max-concurrent-requests-input\">\ub3d9\uc2dc \uc694\uccad \ucd5c\ub300 \uc218:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ud55c \ubc88\uc5d0 \uc694\uccad\ud560 \uc218 \uc788\ub294 \ucd5c\ub300 \ub3d9\uc2dc \uc694\uccad \uc218\ub97c \uc124\uc815\ud558\uc138\uc694.</span>\n                                    </span>\n                                </label>\n                                <input type=\"number\" id=\"max-concurrent-requests-input\" value=\"${Config.settings.maxConcurrentRequests}\" min=\"1\" step=\"1\">\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"data-visualization-checkbox\">\ub370\uc774\ud130 \uc2dc\uac01\ud654 \ud65c\uc131\ud654:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc218\uc9d1\ub41c \ub370\uc774\ud130\ub97c \uc2dc\uac01\ud654\ud558\uc5ec \uadf8\ub798\ud504\ub85c \ud45c\uc2dc\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"checkbox\" id=\"data-visualization-checkbox\" ${Config.settings.dataVisualization ? 'checked' : ''}>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"enable-captchas-checkbox\">CAPTCHA \ucc98\ub9ac \ud65c\uc131\ud654:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">CAPTCHA\uac00 \uac10\uc9c0\ub418\uc5c8\uc744 \ub54c \uc790\ub3d9 \ucc98\ub9ac\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4. (\uc81c\ud55c\uc801)</span>\n                                    </span>\n                                </label>\n                                <input type=\"checkbox\" id=\"enable-captchas-checkbox\" ${Config.settings.enableCaptchas ? 'checked' : ''}>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\ub370\uc774\ud130 \ub0b4\ubcf4\ub0b4\uae30 \ud615\uc2dd:</label>\n                                <div class=\"checkbox-group\">\n                                    ${Object.keys(Config.settings.exportFormats).map(format => `<label><input type=\"checkbox\" id=\"export-${format}\" ${Config.settings.exportFormats[format] ? 'checked' : ''}> ${format.toUpperCase()}</label>`).join('')}\n                                </div>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"advanced-logging-checkbox\">\uace0\uae09 \ub85c\uae45 \ud65c\uc131\ud654:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\ucd94\uac00\uc801\uc778 \ub85c\uae45 \uc815\ubcf4\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4.</span>\n                                    </span>\n                                </label>\n                                <input type=\"checkbox\" id=\"advanced-logging-checkbox\" ${Config.settings.advancedLogging ? 'checked' : ''}>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"custom-keywords-input\">\uc0ac\uc6a9\uc790 \uc9c0\uc815 \ud0a4\uc6cc\ub4dc \ubd84\ub958:\n                                    <span class=\"tooltip\">\u2139\ufe0f\n                                        <span class=\"tooltiptext\">\uc0ac\uc6a9\uc790 \uc9c0\uc815 \ud0a4\uc6cc\ub4dc\ub97c \ucd94\uac00\ud558\uc5ec \ub370\uc774\ud130 \ubd84\ub958\ub97c \ud5a5\uc0c1\uc2dc\ud0b5\ub2c8\ub2e4. \ud615\uc2dd: \uce74\ud14c\uace0\ub9ac:\ud0a4\uc6cc\ub4dc1,\ud0a4\uc6cc\ub4dc2,...</span>\n                                    </span>\n                                </label>\n                                <textarea id=\"custom-keywords-input\" rows=\"5\">${Utils.formatCustomKeywords(Config.settings.customKeywords)}</textarea>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label>\uc124\uc815 \ud15c\ud50c\ub9bf \uad00\ub9ac:</label>\n                                <div class=\"checkbox-group\">\n                                    <input type=\"text\" id=\"new-template-name\" placeholder=\"\ud15c\ud50c\ub9bf \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694\" style=\"margin-bottom: 5px;\">\n                                    <button id=\"save-template\" style=\"padding: 8px 12px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px; margin-right: 8px;\">\ud83d\udcc1 \ud15c\ud50c\ub9bf \uc800\uc7a5</button>\n                                    <button id=\"load-template\" style=\"padding: 8px 12px; background-color: #2196F3; color: white; border-radius: 4px; font-size: 14px;\">\ud83d\udcc2 \ud15c\ud50c\ub9bf \ubd88\ub7ec\uc624\uae30</button>\n                                </div>\n                                <select id=\"template-select\" style=\"width: 100%; padding: 5px; margin-top: 10px; border: 1px solid #ccc; border-radius: 4px;\">\n                                    <option value=\"\" disabled selected>\ud15c\ud50c\ub9bf \uc120\ud0dd</option>\n                                    ${Object.keys(Config.settings.templates).map(template => `<option value=\"${template}\">${template}</option>`).join('')}\n                                </select>\n                                <button id=\"create-template\" style=\"margin-top: 10px; padding: 8px 12px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;\">\ud15c\ud50c\ub9bf \uc0dd\uc131</button>\n                                <button id=\"delete-template\" style=\"margin-top: 5px; padding: 8px 12px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;\">\ud15c\ud50c\ub9bf \uc0ad\uc81c</button>\n                            </div>\n                            <div class=\"settings-section\">\n                                <button id=\"save-settings\" style=\"margin-top: 10px; padding: 10px 20px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;\">\uc800\uc7a5</button>\n                                <button id=\"reset-settings\" style=\"margin-top: 10px; padding: 10px 20px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;\">\ucd08\uae30\ud654</button>\n                            </div>\n                        </div>\n                        <div id=\"tab-templates\" style=\"display: none;\">\n                            <h3>\uc124\uc815 \ud15c\ud50c\ub9bf</h3>\n                            <div class=\"settings-section\">\n                                <label for=\"new-template-name\">\uc0c8 \ud15c\ud50c\ub9bf \uc774\ub984:</label>\n                                <input type=\"text\" id=\"new-template-name\" placeholder=\"\ud15c\ud50c\ub9bf \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694\">\n                                <button id=\"create-template\" style=\"margin-top: 5px; padding: 8px 12px; background-color: #4caf50; color: white; border-radius: 4px; font-size: 14px;\">\ud15c\ud50c\ub9bf \uc0dd\uc131</button>\n                            </div>\n                            <div class=\"settings-section\">\n                                <label for=\"existing-templates\">\uae30\uc874 \ud15c\ud50c\ub9bf:</label>\n                                <select id=\"existing-templates\" multiple size=\"10\" style=\"width: 100%; padding: 5px; border: 1px solid #ccc; border-radius: 4px;\">\n                                    ${Object.keys(Config.settings.templates).map(template => `<option value=\"${template}\">${template}</option>`).join('')}\n                                </select>\n                                <button id=\"delete-template\" style=\"margin-top: 5px; padding: 8px 12px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px;\">\ud15c\ud50c\ub9bf \uc0ad\uc81c</button>\n                            </div>\n                        </div>\n                        <div id=\"tab-previous-crawls\" style=\"display: none;\">\n                            <h3>\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d</h3>\n                            <button id=\"clear-previous-crawls\" style=\"padding: 8px 12px; background-color: #f44336; color: white; border-radius: 4px; font-size: 14px; margin-bottom: 10px;\">\uae30\ub85d \ucd08\uae30\ud654</button>\n                            <table>\n                                <thead>\n                                    <tr>\n                                        <th>\ub0a0\uc9dc</th>\n                                        <th>\ud06c\ub864\ub9c1 \uc218</th>\n                                        <th>\uc0c1\ud0dc</th>\n                                    </tr>\n                                </thead>\n                                <tbody id=\"previous-crawls-body\"></tbody>\n                            </table>\n                        </div>\n                        <div id=\"html-modal\" class=\"modal\">\n                            <div class=\"modal-content\">\n                                <span class=\"close\">&times;</span>\n                                <pre id=\"modal-html-content\"></pre>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            `);\n            $('body').append(container);\n\n            $('#crawler-minimize').on('click', toggleMinimize);\n            $('.crawler-tab').on('click', switchTab);\n            $('#save-settings').on('click', saveSettingsHandler);\n            $('#reset-settings').on('click', resetSettingsHandler);\n            $('#add-user-agent').on('click', addUserAgent);\n            $('#remove-user-agent').on('click', removeUserAgent);\n            $('#download-csv').on('click', Download.downloadCSV);\n            $('#download-json').on('click', Download.downloadJSON);\n            $('#download-xml').on('click', Download.downloadXML);\n            $('#download-xls').on('click', Download.downloadXLS);\n            $('#search-filter').on('input', filterData);\n            $('#schedule-enabled').on('change', toggleScheduleOptions);\n            $('#data-visualization-checkbox').on('change', toggleDataVisualization);\n            $('#enable-captchas-checkbox').on('change', toggleCaptchaHandling);\n            $('#save-template').on('click', saveTemplateHandler);\n            $('#load-template').on('click', loadTemplateHandler);\n            $('#create-template').on('click', createTemplateHandler);\n            $('#delete-template').on('click', deleteTemplateHandler);\n            $('#template-select').on('change', selectTemplateHandler);\n            $('#existing-templates').on('change', existingTemplateSelectHandler);\n            $('#clear-previous-crawls').on('click', clearPreviousCrawls);\n            $('#reset-data').on('click', resetData);\n            $('#dark-mode-toggle').on('click', toggleDarkMode);\n            $('.close').on('click', () => $('#html-modal').hide());\n            $(window).on('click', function(e) { if (e.target.id === 'html-modal') $('#html-modal').hide(); });\n            $('#start-crawling').on('click', Crawling.startCrawling);\n            $('#stop-crawling').on('click', Crawling.stopCrawling);\n            $(document).on('keydown', handleShortcuts);\n            $('#proxy-input').on('input', validateProxyInput);\n            $('.settings-section input[type=\"checkbox\"]').on('change', toggleDataField);\n\n            makeDraggable();\n        }\n\n        function toggleMinimize() {\n            $('#crawler-content').toggle();\n            const isHidden = $('#crawler-content').is(':hidden');\n            $('#crawler-minimize').text(isHidden ? '\u2610' : '\u2500');\n            Logger.log(`\ud06c\ub864\ub7ec \ucc3d\uc774 ${isHidden ? '\ucd5c\uc18c\ud654' : '\ud655\ub300'}\ub418\uc5c8\uc2b5\ub2c8\ub2e4.`, 'info');\n        }\n\n        function switchTab() {\n            const tab = $(this).data('tab');\n            $('.crawler-tab').removeClass('active');\n            $(this).addClass('active');\n            $('#crawler-content > div').hide();\n            $(`#tab-${tab}`).show();\n            Config.saveSettings({ currentTab: tab });\n        }\n\n        function updateUI() {\n            const currentTab = Config.settings.currentTab || 'logs';\n            $(`.crawler-tab[data-tab=\"${currentTab}\"]`).addClass('active');\n            $('#crawler-content > div').hide();\n            $(`#tab-${currentTab}`).show();\n            updateLogs();\n            updateDataTable();\n            updatePreviousCrawlsUI();\n            updateTemplatesUI();\n            if (Config.settings.dataVisualization) DataVisualization.renderChart();\n            applyDarkMode();\n        }\n\n        function updateLogs() {\n            const logs = Logger.getLogs();\n            $('#log-messages').html(logs.map(l => `<div>[${l.timestamp}] [${l.type.toUpperCase()}] ${Utils.escape(l.message)}</div>`).join(''));\n            const progress = GM_getValue('progress', 0);\n            const currentPage = GM_getValue('currentPage', 0);\n            const totalPages = Config.settings.maxPages;\n            $('.progress-bar-fill').css('width', `${progress.toFixed(2)}%`);\n            $('.status-text').text(`\uc9c4\ud589\ub960: ${progress.toFixed(2)}% (${currentPage}/${totalPages} \ud398\uc774\uc9c0)`);\n            const latestLog = logs[logs.length -1];\n            if (latestLog) {\n                if (latestLog.type === 'error') {\n                    $('#error-message').text(latestLog.message).show();\n                    setTimeout(() => { $('#error-message').fadeOut(); }, 5000);\n                }\n                if (latestLog.type === 'success') {\n                    $('#success-message').text(latestLog.message).show();\n                    setTimeout(() => { $('#success-message').fadeOut(); }, 5000);\n                }\n                if (latestLog.type === 'info') {\n                    $('#success-message').text(latestLog.message).show();\n                    setTimeout(() => { $('#success-message').fadeOut(); }, 5000);\n                }\n                if (latestLog.type === 'warning') {\n                    $('#error-message').text(latestLog.message).show();\n                    setTimeout(() => { $('#error-message').fadeOut(); }, 5000);\n                }\n            }\n        }\n\n        function updateDataTable() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            const filter = $('#search-filter').val().toLowerCase();\n            const filtered = filter ? data.filter(item => Object.keys(Config.settings.dataFields).some(field => Config.settings.dataFields[field] && String(item[field]).toLowerCase().includes(filter))) : data;\n            const rows = filtered.map(item => `\n                <tr>\n                    ${Config.settings.dataFields.name ? `<td>${Utils.escape(item.name)}</td>` : ''}\n                    ${Config.settings.dataFields.url ? `<td><a href=\"${Utils.escape(item.url)}\" target=\"_blank\">${Utils.escape(item.url)}</a></td>` : ''}\n                    ${Config.settings.dataFields.description ? `<td>${Utils.escape(item.description)}</td>` : ''}\n                    ${Config.settings.dataFields.html ? `<td><button class=\"view-html\" data-html=\"${encodeURIComponent(item.html)}\">\ud83d\udcc4 \ubcf4\uae30</button></td>` : ''}\n                    ${Config.settings.dataFields.category ? `<td>${Utils.escape(item.category)}</td>` : ''}\n                    ${Config.settings.dataFields.quality ? `<td>${Utils.escape(item.quality.toString())}</td>` : ''}\n                    ${Config.settings.dataFields.language ? `<td>${Utils.escape(item.language)}</td>` : ''}\n                    ${Config.settings.dataFields.wordCount ? `<td>${Utils.escape(item.wordCount.toString())}</td>` : ''}\n                    ${Config.settings.dataFields.metaTags ? `<td>${Utils.escape(JSON.stringify(item.metaTags))}</td>` : ''}\n                    ${Config.settings.dataFields.headings ? `<td>${Utils.escape(item.headings.join(', '))}</td>` : ''}\n                </tr>\n            `).join('');\n            $('#data-table-body').html(rows);\n            $('.view-html').off('click').on('click', function() {\n                $('#modal-html-content').text(decodeURIComponent($(this).data('html')));\n                $('#html-modal').show();\n            });\n            if (Config.settings.dataVisualization) DataVisualization.renderChart();\n        }\n\n        function updatePreviousCrawlsUI() {\n            const previousCrawls = JSON.parse(GM_getValue('previousCrawls', '[]'));\n            $('#previous-crawls-body').html(previousCrawls.map(crawl => `\n                <tr>\n                    <td>${Utils.escape(crawl.date)}</td>\n                    <td>${Utils.escape(crawl.count.toString())}</td>\n                    <td>${Utils.escape(crawl.status)}</td>\n                </tr>\n            `).join(''));\n        }\n\n        function updateTemplatesUI() {\n            $('#template-select').empty().append(`<option value=\"\" disabled selected>\ud15c\ud50c\ub9bf \uc120\ud0dd</option>`);\n            Object.keys(Config.settings.templates).forEach(template => {\n                $('#template-select').append(`<option value=\"${template}\">${template}</option>`);\n            });\n            $('#existing-templates').empty();\n            Object.keys(Config.settings.templates).forEach(template => {\n                $('#existing-templates').append(`<option value=\"${template}\">${template}</option>`);\n            });\n        }\n\n        function addUserAgent() {\n            const ua = prompt(\"\uc0c8 \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\ub97c \uc785\ub825\ud558\uc138\uc694:\");\n            if (ua) {\n                Config.settings.userAgents.push(ua);\n                Config.saveSettings({ userAgents: Config.settings.userAgents });\n                updateUserAgentSelect();\n                Logger.log('\uc0c8 \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\uac00 \ucd94\uac00\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            }\n        }\n\n        function removeUserAgent() {\n            const selected = $('#user-agent-select').val();\n            if (selected && selected.length) {\n                selected.sort((a, b) => b - a).forEach(idx => { Config.settings.userAgents.splice(idx, 1); });\n                Config.settings.userAgentIndex = 0;\n                Config.saveSettings({\n                    userAgents: Config.settings.userAgents,\n                    userAgentIndex: Config.settings.userAgentIndex\n                });\n                updateUserAgentSelect();\n                Logger.log('\uc120\ud0dd\ud55c \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\uac00 \uc81c\uac70\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            } else {\n                alert('\uc81c\uac70\ud560 \uc0ac\uc6a9\uc790 \uc5d0\uc774\uc804\ud2b8\ub97c \uc120\ud0dd\ud558\uc138\uc694.');\n            }\n        }\n\n        function updateUserAgentSelect() {\n            $('#user-agent-select').empty();\n            Config.settings.userAgents.forEach((ua, idx) => {\n                $('#user-agent-select').append(`<option value=\"${idx}\" ${idx === Config.settings.userAgentIndex ? 'selected' : ''}>${ua}</option>`);\n            });\n        }\n\n        function saveSettingsHandler() {\n            const delay = parseInt($('#delay-input').val(), 10);\n            const delayRandom = parseInt($('#delay-random-input').val(), 10);\n            const maxPages = parseInt($('#max-pages-input').val(), 10);\n            const timeout = parseInt($('#timeout-input').val(), 10);\n            const minWordCount = parseInt($('#min-word-count-input').val(), 10);\n            const maxWordCount = parseInt($('#max-word-count-input').val(), 10);\n            const maxDepth = parseInt($('#max-depth-input').val(), 10);\n            const retryAttempts = parseInt($('#retry-attempts-input').val(), 10);\n            const retryDelay = parseInt($('#retry-delay-input').val(), 10);\n            const interval = parseInt($('#interval-input').val(), 10) * 60000;\n            const proxy = $('#proxy-input').val().trim();\n            const isValidProxy = proxy === '' || Config.validateProxy(proxy);\n            const isValidSettings = !isNaN(delay) && delay >= 500 &&\n                                    !isNaN(delayRandom) && delayRandom >= 0 &&\n                                    !isNaN(maxPages) && maxPages >= 1 &&\n                                    !isNaN(timeout) && timeout >= 1000 &&\n                                    !isNaN(minWordCount) && minWordCount >= 10 &&\n                                    !isNaN(maxWordCount) && maxWordCount >= 100 &&\n                                    !isNaN(maxDepth) && maxDepth >= 1 &&\n                                    !isNaN(retryAttempts) && retryAttempts >= 0 &&\n                                    !isNaN(retryDelay) && retryDelay >= 1000 &&\n                                    (!Config.settings.schedule.enabled || (!isNaN(interval) && interval >= 60000)) &&\n                                    isValidProxy;\n\n            if (!isValidSettings) {\n                $('#error-message').text('\uc124\uc815\uac12\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \uc62c\ubc14\ub978 \uac12\uc744 \uc785\ub825\ud558\uc138\uc694.').show();\n                setTimeout(() => { $('#error-message').fadeOut(); }, 5000);\n                return;\n            }\n\n            Config.settings.delay = delay;\n            Config.settings.delayRandom = delayRandom;\n            Config.settings.maxPages = maxPages;\n            const selectedUA = parseInt($('#user-agent-select').val(), 10);\n            Config.settings.userAgentIndex = isNaN(selectedUA) ? 0 : selectedUA;\n            Config.settings.contentTypes.include = $('#content-include-input').val().split(',').map(ct => ct.trim()).filter(ct => ct);\n            Config.settings.contentTypes.exclude = $('#content-exclude-input').val().split(',').map(ct => ct.trim()).filter(ct => ct);\n            Config.settings.timeout = timeout;\n            Config.settings.includeKeywords = $('#include-keywords-input').val().split('\\n').map(kw => kw.trim()).filter(kw => kw);\n            Config.settings.excludeKeywords = $('#exclude-keywords-input').val().split('\\n').map(kw => kw.trim()).filter(kw => kw);\n            Config.settings.regexInclude = $('#regex-include-input').val().split('\\n').map(rx => rx.trim()).filter(rx => rx);\n            Config.settings.regexExclude = $('#regex-exclude-input').val().split('\\n').map(rx => rx.trim()).filter(rx => rx);\n            Config.settings.language = $('#language-select').val() || [];\n            Config.settings.minWordCount = minWordCount;\n            Config.settings.maxWordCount = maxWordCount;\n            Config.settings.maxDepth = maxDepth;\n            Config.settings.proxy = proxy;\n            ['name','url','description','html','category','quality','language','wordCount','metaTags','headings'].forEach(field => {\n                Config.settings.dataFields[field] = $(`#field-${field}`).is(':checked');\n            });\n            Config.settings.retryAttempts = retryAttempts;\n            Config.settings.retryDelay = retryDelay;\n            Config.settings.schedule.enabled = $('#schedule-enabled').is(':checked');\n            Config.settings.schedule.startTime = $('#start-time-input').val() || Config.DEFAULT_SETTINGS.schedule.startTime;\n            Config.settings.schedule.interval = Config.settings.schedule.enabled ? (isNaN(interval) ? Config.DEFAULT_SETTINGS.schedule.interval : interval) : Config.DEFAULT_SETTINGS.schedule.interval;\n            Object.keys(Config.settings.searchEngines).forEach(engine => { Config.settings.searchEngines[engine] = $(`#engine-${engine}`).is(':checked'); });\n            Config.settings.blacklist = $('#blacklist-input').val().split('\\n').map(d => d.trim()).filter(d => d);\n            Config.settings.whitelist = $('#whitelist-input').val().split('\\n').map(d => d.trim()).filter(d => d);\n            Config.settings.respectRobots = $('#respect-robots-checkbox').is(':checked');\n            Config.settings.maxConcurrentRequests = parseInt($('#max-concurrent-requests-input').val(), 10) || Config.DEFAULT_SETTINGS.maxConcurrentRequests;\n            Config.settings.dataVisualization = $('#data-visualization-checkbox').is(':checked');\n            Config.settings.exportFormats.csv = $('#export-csv').is(':checked');\n            Config.settings.exportFormats.json = $('#export-json').is(':checked');\n            Config.settings.exportFormats.xml = $('#export-xml').is(':checked');\n            Config.settings.exportFormats.xls = $('#export-xls').is(':checked');\n            Config.settings.advancedLogging = $('#advanced-logging-checkbox').is(':checked');\n            Config.settings.enableCaptchas = $('#enable-captchas-checkbox').is(':checked');\n            Config.settings.customKeywords = Utils.parseCustomKeywords($('#custom-keywords-input').val());\n\n            Config.saveSettings(Config.settings);\n            Crawling.updateCrawlSettings();\n            Logger.log('\uc124\uc815\uc774 \uc800\uc7a5\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n\n            if (Config.settings.schedule.enabled) {\n                Scheduler.scheduleCrawling();\n            }\n\n            updateDataTable();\n            if (Config.settings.dataVisualization) DataVisualization.renderChart();\n        }\n\n        function resetSettingsHandler() {\n            if (confirm(\"\uae30\ubcf8 \uc124\uc815\uc73c\ub85c \ucd08\uae30\ud654\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\")) {\n                Config.resetSettings();\n                location.reload();\n                Logger.log('\uae30\ubcf8 \uc124\uc815\uc73c\ub85c \ucd08\uae30\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            }\n        }\n\n        function toggleScheduleOptions() {\n            $('#schedule-options').toggle($('#schedule-enabled').is(':checked'));\n        }\n\n        function toggleDataVisualization() {\n            Config.settings.dataVisualization = $('#data-visualization-checkbox').is(':checked');\n            Config.saveSettings({ dataVisualization: Config.settings.dataVisualization });\n            $('#data-visualization').toggle(Config.settings.dataVisualization);\n            Logger.log('\ub370\uc774\ud130 \uc2dc\uac01\ud654 \uc124\uc815\uc774 \ubcc0\uacbd\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            if (Config.settings.dataVisualization) DataVisualization.renderChart();\n        }\n\n        function toggleCaptchaHandling() {\n            Config.settings.enableCaptchas = $('#enable-captchas-checkbox').is(':checked');\n            Config.saveSettings({ enableCaptchas: Config.settings.enableCaptchas });\n            Logger.log('CAPTCHA \ucc98\ub9ac \uc124\uc815\uc774 \ubcc0\uacbd\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function filterData() {\n            updateDataTable();\n        }\n\n        function clearPreviousCrawls() {\n            if (confirm(\"\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d\uc744 \ubaa8\ub450 \ucd08\uae30\ud654\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\")) {\n                GM_setValue('previousCrawls', JSON.stringify([]));\n                updatePreviousCrawlsUI();\n                Logger.log('\uc774\uc804 \ud06c\ub864\ub9c1 \uae30\ub85d\uc774 \ucd08\uae30\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            }\n        }\n\n        function resetData() {\n            if (confirm(\"\ubaa8\ub4e0 \ud06c\ub864\ub9c1 \ub370\uc774\ud130\ub97c \ucd08\uae30\ud654\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?\")) {\n                GM_setValue('data', JSON.stringify([]));\n                GM_setValue('visitedUrls', JSON.stringify([]));\n                updateDataTable();\n                Logger.log('\ud06c\ub864\ub9c1 \ub370\uc774\ud130\uac00 \ucd08\uae30\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n            }\n        }\n\n        function handleShortcuts(e) {\n            if (e.ctrlKey) {\n                const actions = { '1': 'logs', '2': 'data', '3': 'settings', '4': 'previous-crawls', '5': 'templates', 's': 'save', 'r': 'reset', 'p': 'start', 'q': 'stop', 'c': 'csv', 'j': 'json', 'x': 'xml', 'l': 'xls', 'd': 'dark-mode' };\n                const action = actions[e.key];\n                if (action) {\n                    e.preventDefault();\n                    switch(action) {\n                        case 'logs': $('.crawler-tab[data-tab=\"logs\"]').click(); break;\n                        case 'data': $('.crawler-tab[data-tab=\"data\"]').click(); break;\n                        case 'settings': $('.crawler-tab[data-tab=\"settings\"]').click(); break;\n                        case 'previous-crawls': $('.crawler-tab[data-tab=\"previous-crawls\"]').click(); break;\n                        case 'templates': $('.crawler-tab[data-tab=\"templates\"]').click(); break;\n                        case 'save': saveSettingsHandler(); break;\n                        case 'reset': resetSettingsHandler(); break;\n                        case 'start': if (!Crawling.isCrawling()) Crawling.startCrawling(); break;\n                        case 'stop': if (Crawling.isCrawling()) Crawling.stopCrawling(); break;\n                        case 'csv': Config.settings.exportFormats.csv && Download.downloadCSV(); break;\n                        case 'json': Config.settings.exportFormats.json && Download.downloadJSON(); break;\n                        case 'xml': Config.settings.exportFormats.xml && Download.downloadXML(); break;\n                        case 'xls': Config.settings.exportFormats.xls && Download.downloadXLS(); break;\n                        case 'dark-mode': toggleDarkMode(); break;\n                    }\n                }\n            }\n        }\n\n        function makeDraggable() {\n            const container = document.getElementById('crawler-container');\n            const header = document.getElementById('crawler-header');\n            let isDragging = false;\n            let startX, startY, initialX, initialY;\n            header.onmousedown = function(e) {\n                isDragging = true;\n                startX = e.clientX;\n                startY = e.clientY;\n                initialX = container.offsetLeft;\n                initialY = container.offsetTop;\n                document.onmousemove = drag;\n                document.onmouseup = stopDrag;\n                return false;\n            };\n            function drag(e) {\n                if (isDragging) {\n                    let dx = e.clientX - startX;\n                    let dy = e.clientY - startY;\n                    container.style.left = (initialX + dx) + 'px';\n                    container.style.top = (initialY + dy) + 'px';\n                }\n            }\n            function stopDrag() {\n                isDragging = false;\n                document.onmousemove = null;\n                document.onmouseup = null;\n            }\n        }\n\n        function getFieldName(field) {\n            const names = {\n                name: '\uc774\ub984',\n                url: '\uc8fc\uc18c',\n                description: '\uc124\uba85',\n                html: 'HTML \ub0b4\uc6a9',\n                category: '\ubd84\ub958',\n                quality: '\ud488\uc9c8\uc810\uc218',\n                language: '\uc5b8\uc5b4',\n                wordCount: '\ub2e8\uc5b4 \uc218',\n                metaTags: '\uba54\ud0c0 \ud0dc\uadf8',\n                headings: '\ud5e4\ub529'\n            };\n            return names[field] || field;\n        }\n\n        // Template Handlers\n        function saveTemplateHandler() {\n            const templateName = $('#new-template-name').val().trim();\n            if (!templateName) {\n                alert('\ud15c\ud50c\ub9bf \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694.');\n                return;\n            }\n            Config.saveTemplate(templateName);\n            updateTemplatesUI();\n            $('#new-template-name').val('');\n        }\n\n        function loadTemplateHandler() {\n            const selected = $('#template-select').val();\n            if (!selected) {\n                alert('\ubd88\ub7ec\uc62c \ud15c\ud50c\ub9bf\uc744 \uc120\ud0dd\ud558\uc138\uc694.');\n                return;\n            }\n            Config.loadTemplate(selected);\n            updateUI();\n            $('.crawler-tab[data-tab=\"settings\"]').click();\n        }\n\n        function createTemplateHandler() {\n            const templateName = $('#new-template-name').val().trim();\n            if (!templateName) {\n                alert('\ud15c\ud50c\ub9bf \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694.');\n                return;\n            }\n            Config.saveTemplate(templateName);\n            updateTemplatesUI();\n            $('#new-template-name').val('');\n        }\n\n        function deleteTemplateHandler() {\n            const selectedTemplates = $('#existing-templates').val();\n            if (!selectedTemplates || !selectedTemplates.length) {\n                alert('\uc0ad\uc81c\ud560 \ud15c\ud50c\ub9bf\uc744 \uc120\ud0dd\ud558\uc138\uc694.');\n                return;\n            }\n            selectedTemplates.forEach(template => {\n                delete Config.settings.templates[template];\n                Logger.log(`\ud15c\ud50c\ub9bf '${template}'\uc774(\uac00) \uc0ad\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4.`, 'success');\n            });\n            Config.saveSettings({ templates: Config.settings.templates });\n            updateTemplatesUI();\n        }\n\n        function selectTemplateHandler() {\n            const selected = $(this).val();\n            if (selected) {\n                Config.loadTemplate(selected);\n                updateUI();\n                $('.crawler-tab[data-tab=\"settings\"]').click();\n            }\n        }\n\n        function existingTemplateSelectHandler() {\n            // Placeholder for any additional actions when selecting existing templates\n        }\n\n        function toggleDarkMode() {\n            $('#crawler-container').toggleClass('dark');\n            const isDark = $('#crawler-container').hasClass('dark');\n            $('#dark-mode-toggle').text(isDark ? '\u2600\ufe0f' : '\ud83c\udf19');\n            Config.saveSettings({ darkMode: isDark });\n            Logger.log(`\ub2e4\ud06c \ubaa8\ub4dc\uac00 ${isDark ? '\ud65c\uc131\ud654' : '\ube44\ud65c\uc131\ud654'}\ub418\uc5c8\uc2b5\ub2c8\ub2e4.`, 'success');\n        }\n\n        function applyDarkMode() {\n            if (Config.settings.darkMode) {\n                $('#crawler-container').addClass('dark');\n                $('#dark-mode-toggle').text('\u2600\ufe0f');\n            } else {\n                $('#crawler-container').removeClass('dark');\n                $('#dark-mode-toggle').text('\ud83c\udf19');\n            }\n        }\n\n        function toggleDataField() {\n            const field = $(this).attr('id').replace('field-', '');\n            Config.settings.dataFields[field] = $(this).is(':checked');\n            Config.saveSettings({ dataFields: Config.settings.dataFields });\n            Download.updateExportButtons();\n        }\n\n        function validateProxyInput() {\n            const proxy = $('#proxy-input').val().trim();\n            if (proxy && !Config.validateProxy(proxy)) {\n                $('#proxy-validation-message').text('\uc720\ud6a8\ud558\uc9c0 \uc54a\uc740 \ud504\ub85d\uc2dc URL\uc785\ub2c8\ub2e4.').show();\n            } else {\n                $('#proxy-validation-message').hide();\n            }\n        }\n\n        return {\n            createUI,\n            updateUI,\n            updateLogs,\n            updateDataTable,\n            updatePreviousCrawlsUI,\n            updateTemplatesUI,\n            applyDarkMode\n        };\n    })();\n\n    /* ============================\n       Module: Download\n    ============================ */\n    const Download = (function() {\n        function downloadCSV() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            if (!data.length) { Logger.log('\ub2e4\uc6b4\ub85c\ub4dc\ud560 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning'); return; }\n            const headers = Object.keys(Config.settings.dataFields).filter(f => Config.settings.dataFields[f]).map(f => Utils.capitalize(getFieldName(f)));\n            const csvContent = [headers.join(\",\")]\n                .concat(data.map(item => Object.keys(Config.settings.dataFields).filter(f => Config.settings.dataFields[f]).map(f => `\"${Utils.escape(item[f], 'csv')}\"`).join(\",\")))\n                .join(\"\\n\");\n            const blob = new Blob([csvContent], { type: \"text/csv;charset=utf-8;\" });\n            const link = document.createElement(\"a\");\n            link.href = URL.createObjectURL(blob);\n            link.download = \"data.csv\";\n            document.body.appendChild(link);\n            link.click();\n            document.body.removeChild(link);\n            Logger.log('CSV \ud30c\uc77c\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function downloadJSON() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            if (!data.length) { Logger.log('\ub2e4\uc6b4\ub85c\ub4dc\ud560 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning'); return; }\n            const jsonContent = JSON.stringify(data, null, 2);\n            const blob = new Blob([jsonContent], { type: \"application/json\" });\n            const link = document.createElement(\"a\");\n            link.href = URL.createObjectURL(blob);\n            link.download = \"data.json\";\n            document.body.appendChild(link);\n            link.click();\n            document.body.removeChild(link);\n            Logger.log('JSON \ud30c\uc77c\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function downloadXML() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            if (!data.length) { Logger.log('\ub2e4\uc6b4\ub85c\ub4dc\ud560 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning'); return; }\n            let xmlContent = '<?xml version=\"1.0\" encoding=\"UTF-8\"?><data>';\n            data.forEach(item => {\n                xmlContent += '<entry>';\n                if (Config.settings.dataFields.name) xmlContent += `<name>${Utils.escape(item.name || 'No Title', 'xml')}</name>`;\n                if (Config.settings.dataFields.url) xmlContent += `<url>${Utils.escape(item.url || '', 'xml')}</url>`;\n                if (Config.settings.dataFields.description) xmlContent += `<description>${Utils.escape(item.description || 'No Description', 'xml')}</description>`;\n                if (Config.settings.dataFields.html) xmlContent += `<html><![CDATA[${item.html || ''}]]></html>`;\n                if (Config.settings.dataFields.category) xmlContent += `<category>${Utils.escape(item.category || '', 'xml')}</category>`;\n                if (Config.settings.dataFields.quality) xmlContent += `<quality>${Utils.escape(item.quality)}</quality>`;\n                if (Config.settings.dataFields.language) xmlContent += `<language>${Utils.escape(item.language || 'unknown', 'xml')}</language>`;\n                if (Config.settings.dataFields.wordCount) xmlContent += `<wordCount>${Utils.escape(item.wordCount)}</wordCount>`;\n                if (Config.settings.dataFields.metaTags) xmlContent += `<metaTags>${Utils.escape(JSON.stringify(item.metaTags), 'xml')}</metaTags>`;\n                if (Config.settings.dataFields.headings) xmlContent += `<headings>${Utils.escape(item.headings.join('; '), 'xml')}</headings>`;\n                xmlContent += '</entry>';\n            });\n            xmlContent += '</data>';\n            const blob = new Blob([xmlContent], { type: \"application/xml\" });\n            const link = document.createElement(\"a\");\n            link.href = URL.createObjectURL(blob);\n            link.download = \"data.xml\";\n            document.body.appendChild(link);\n            link.click();\n            document.body.removeChild(link);\n            Logger.log('XML \ud30c\uc77c\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function downloadXLS() {\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            if (!data.length) { Logger.log('\ub2e4\uc6b4\ub85c\ub4dc\ud560 \ub370\uc774\ud130\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning'); return; }\n            const worksheetData = data.map(item => {\n                const entry = {};\n                if (Config.settings.dataFields.name) entry['\uc0ac\uc774\ud2b8 \uc774\ub984'] = item.name;\n                if (Config.settings.dataFields.url) entry['\uc8fc\uc18c'] = item.url;\n                if (Config.settings.dataFields.description) entry['\uc124\uba85'] = item.description;\n                if (Config.settings.dataFields.html) entry['\ud83d\udcc4 HTML \ub0b4\uc6a9'] = item.html;\n                if (Config.settings.dataFields.category) entry['\ubd84\ub958'] = item.category;\n                if (Config.settings.dataFields.quality) entry['\ud488\uc9c8\uc810\uc218'] = item.quality;\n                if (Config.settings.dataFields.language) entry['\uc5b8\uc5b4'] = item.language;\n                if (Config.settings.dataFields.wordCount) entry['\ub2e8\uc5b4 \uc218'] = item.wordCount;\n                if (Config.settings.dataFields.metaTags) entry['\uba54\ud0c0 \ud0dc\uadf8'] = JSON.stringify(item.metaTags);\n                if (Config.settings.dataFields.headings) entry['\ud5e4\ub529'] = item.headings.join('; ');\n                return entry;\n            });\n            const worksheet = XLSX.utils.json_to_sheet(worksheetData);\n            const workbook = XLSX.utils.book_new();\n            XLSX.utils.book_append_sheet(workbook, worksheet, \"Data\");\n            XLSX.writeFile(workbook, \"data.xlsx\");\n            Logger.log('XLS \ud30c\uc77c\uc774 \ub2e4\uc6b4\ub85c\ub4dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n        }\n\n        function getFieldName(field) {\n            const names = {\n                name: '\uc774\ub984',\n                url: '\uc8fc\uc18c',\n                description: '\uc124\uba85',\n                html: 'HTML \ub0b4\uc6a9',\n                category: '\ubd84\ub958',\n                quality: '\ud488\uc9c8\uc810\uc218',\n                language: '\uc5b8\uc5b4',\n                wordCount: '\ub2e8\uc5b4 \uc218',\n                metaTags: '\uba54\ud0c0 \ud0dc\uadf8',\n                headings: '\ud5e4\ub529'\n            };\n            return names[field] || field;\n        }\n\n        function updateExportButtons() {\n            // Placeholder for dynamic enabling/disabling of export buttons based on data fields\n        }\n\n        return {\n            downloadCSV,\n            downloadJSON,\n            downloadXML,\n            downloadXLS,\n            updateExportButtons\n        };\n    })();\n\n    /* ============================\n       Module: DataVisualization\n    ============================ */\n    const DataVisualization = (function() {\n        let chartInstance = null;\n\n        function renderChart() {\n            if (!document.getElementById('data-chart').getContext) return;\n            const ctx = document.getElementById('data-chart').getContext('2d');\n            if (chartInstance) chartInstance.destroy();\n            const data = JSON.parse(GM_getValue('data', '[]'));\n            const categories = {};\n            data.forEach(item => { const cat = item.category || '\uae30\ud0c0'; categories[cat] = (categories[cat] || 0) + 1; });\n            const chartData = {\n                labels: Object.keys(categories),\n                datasets: [{\n                    label: '\uce74\ud14c\uace0\ub9ac\ubcc4 \ub370\uc774\ud130 \uc218',\n                    data: Object.values(categories),\n                    backgroundColor: Utils.generateColors(Object.keys(categories).length)\n                }]\n            };\n            chartInstance = new Chart(ctx, { type: 'pie', data: chartData, options: { responsive: true } });\n        }\n\n        return {\n            renderChart\n        };\n    })();\n\n    /* ============================\n       Module: Crawling\n    ============================ */\n    const Crawling = (function() {\n        let isCrawlingFlag = Config.settings.isCrawling;\n        let currentPage = parseInt(GM_getValue('currentPage', 1), 10);\n        let totalPages = Config.settings.maxPages;\n        let progress = parseFloat(GM_getValue('progress', 0));\n        let currentDepth = parseInt(GM_getValue('currentDepth', 0), 10);\n        let activeRequests = 0;\n        let requestQueue = [];\n        let previousCrawls = JSON.parse(GM_getValue('previousCrawls', '[]'));\n        let visitedUrls = new Set(JSON.parse(GM_getValue('visitedUrls', '[]')));\n        let data = JSON.parse(GM_getValue('data', '[]'));\n        let navigationObserver = null;\n\n        const searchEngineHandlers = {\n            google: {\n                extractResults: function(doc) {\n                    const results = [];\n                    doc.querySelectorAll('div.g, div[data-hveid]').forEach(item => {\n                        const name = item.querySelector('h3')?.innerText.trim() || 'No Title';\n                        const url = item.querySelector('a')?.href || '';\n                        const desc = item.querySelector('.aCOpRe, .VwiC3b')?.innerText.trim() || 'No Description';\n                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });\n                    });\n                    return results;\n                },\n                getNextPageUrl: function(doc) {\n                    return doc.querySelector('a#pnnext')?.href || null;\n                },\n                getContentContainer: function() {\n                    return '#search';\n                }\n            },\n            bing: {\n                extractResults: function(doc) {\n                    const results = [];\n                    doc.querySelectorAll('.b_algo').forEach(item => {\n                        const name = item.querySelector('h2')?.innerText.trim() || 'No Title';\n                        const url = item.querySelector('h2 a')?.href || '';\n                        const desc = item.querySelector('.b_caption p')?.innerText.trim() || 'No Description';\n                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });\n                    });\n                    return results;\n                },\n                getNextPageUrl: function(doc) {\n                    return doc.querySelector('a.sb_pagN')?.href || null;\n                },\n                getContentContainer: function() {\n                    return '#b_content';\n                }\n            },\n            yahoo: {\n                extractResults: function(doc) {\n                    const results = [];\n                    doc.querySelectorAll('.dd.algo').forEach(item => {\n                        const name = item.querySelector('h3')?.innerText.trim() || 'No Title';\n                        const url = item.querySelector('a')?.href || '';\n                        const desc = item.querySelector('.compText a')?.innerText.trim() || 'No Description';\n                        if (Utils.isValidURL(url)) results.push({ name, url, description: desc });\n                    });\n                    return results;\n                },\n                getNextPageUrl: function(doc) {\n                    return doc.querySelector('a.next')?.href || null;\n                },\n                getContentContainer: function() {\n                    return '#web';\n                }\n            },\n            // Additional search engine handlers...\n            // Include handlers for duckduckgo, baidu, yandex, naver, daum, ask, ecosia, startpage, searx, dogpile, bingimages\n        };\n\n        function identifySearchEngine() {\n            const host = window.location.hostname;\n            if (host.includes('google.com')) return 'google';\n            if (host.includes('bing.com') && !window.location.pathname.includes('/images')) return 'bing';\n            if (host.includes('yahoo.com')) return 'yahoo';\n            if (host.includes('duckduckgo.com')) return 'duckduckgo';\n            if (host.includes('baidu.com')) return 'baidu';\n            if (host.includes('yandex.com')) return 'yandex';\n            if (host.includes('naver.com')) return 'naver';\n            if (host.includes('daum.net')) return 'daum';\n            if (host.includes('ask.com')) return 'ask';\n            if (host.includes('ecosia.org')) return 'ecosia';\n            if (host.includes('startpage.com')) return 'startpage';\n            if (host.includes('searx.me')) return 'searx';\n            if (host.includes('dogpile.com')) return 'dogpile';\n            if (host.includes('bing.com') && window.location.pathname.includes('/images')) return 'bingimages';\n            return null;\n        }\n\n        async function startCrawling() {\n            if (isCrawlingFlag) {\n                Logger.log('\uc774\ubbf8 \ud06c\ub864\ub9c1\uc774 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.', 'warning');\n                return;\n            }\n            if (!Object.values(Config.settings.searchEngines).some(v => v)) {\n                Logger.log('\uc0ac\uc6a9\ud560 \uac80\uc0c9 \uc5d4\uc9c4\uc774 \ud558\ub098\ub3c4 \uc120\ud0dd\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.', 'error');\n                return;\n            }\n            isCrawlingFlag = true;\n            Config.saveSettings({ isCrawling: isCrawlingFlag });\n            Logger.log('\ud06c\ub864\ub9c1\uc744 \uc2dc\uc791\ud569\ub2c8\ub2e4.', 'info');\n            UI.updateLogs();\n            currentPage = parseInt(GM_getValue('currentPage', 1), 10);\n            totalPages = Config.settings.maxPages;\n            currentDepth = parseInt(GM_getValue('currentDepth', 0), 10);\n            if (!previousCrawls.some(crawl => crawl.status === '\uc9c4\ud589 \uc911')) {\n                previousCrawls.push({ date: new Date().toLocaleString(), count: 0, status: '\uc9c4\ud589 \uc911' });\n                GM_setValue('previousCrawls', JSON.stringify(previousCrawls));\n                UI.updatePreviousCrawlsUI();\n            }\n            setButtonStates();\n            await crawlPage(currentDepth);\n            monitorPageNavigation();\n        }\n\n        function stopCrawling() {\n            if (!isCrawlingFlag) {\n                Logger.log('\ud06c\ub864\ub9c1\uc774 \uc9c4\ud589 \uc911\uc774\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.', 'warning');\n                return;\n            }\n            isCrawlingFlag = false;\n            Config.saveSettings({ isCrawling: isCrawlingFlag });\n            Logger.log('\ud06c\ub864\ub9c1\uc774 \uc911\uc9c0\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'warning');\n            finalizeCrawl('\uc911\uc9c0\ub428');\n            UI.updateLogs();\n            setButtonStates();\n        }\n\n        function setButtonStates() {\n            $('#start-crawling').prop('disabled', isCrawlingFlag);\n            $('#stop-crawling').prop('disabled', !isCrawlingFlag);\n        }\n\n        async function crawlPage(depth) {\n            try {\n                while (isCrawlingFlag && currentPage <= totalPages && depth <= Config.settings.maxDepth) {\n                    Logger.log(`\ud398\uc774\uc9c0 ${currentPage} \ud06c\ub864\ub9c1 \uc2dc\uc791. (\uae4a\uc774: ${depth})`, 'info');\n                    if (Config.settings.respectRobots && !(await Robots.checkRobotsTxt(window.location.href))) {\n                        Logger.log(`robots.txt\uc5d0 \uc758\ud574 \ud06c\ub864\ub9c1\uc774 \uae08\uc9c0\ub41c \ud398\uc774\uc9c0: ${window.location.href}`, 'warning');\n                        break;\n                    }\n                    const searchEngine = identifySearchEngine();\n                    if (!searchEngine || !searchEngineHandlers[searchEngine]) {\n                        Logger.log(`\uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uac80\uc0c9 \uc5d4\uc9c4: ${window.location.hostname}`, 'error');\n                        break;\n                    }\n                    const doc = document;\n                    const handler = searchEngineHandlers[searchEngine];\n                    const results = handler.extractResults(doc);\n                    if (!results) { Logger.log('\uac80\uc0c9 \uacb0\uacfc\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.', 'error'); break; }\n                    results.forEach(result => {\n                        if (!isCrawlingFlag) return;\n                        const { name, url, description } = result;\n                        if (visitedUrls.has(url) || !Utils.isValidURL(url) ||\n                            (Config.settings.whitelist.length && !Config.settings.whitelist.some(d => url.includes(d))) ||\n                            Config.settings.blacklist.some(d => url.includes(d)) ||\n                            (Config.settings.includeKeywords.length && !Config.settings.includeKeywords.some(k => url.includes(k) || description.includes(k))) ||\n                            (Config.settings.excludeKeywords.some(k => url.includes(k) || description.includes(k))) ||\n                            (Config.settings.regexInclude.length && !Config.settings.regexInclude.some(r => new RegExp(r).test(url) || new RegExp(r).test(description))) ||\n                            (Config.settings.regexExclude.length && Config.settings.regexExclude.some(r => new RegExp(r).test(url) || new RegExp(r).test(description)))) {\n                            return;\n                        }\n                        requestQueue.push({ url, depth });\n                        if (activeRequests >= Config.settings.maxConcurrentRequests) return;\n                    });\n                    if (previousCrawls.length && previousCrawls[previousCrawls.length -1].status === '\uc9c4\ud589 \uc911') {\n                        previousCrawls[previousCrawls.length -1].count += results.length;\n                        GM_setValue('previousCrawls', JSON.stringify(previousCrawls));\n                        UI.updatePreviousCrawlsUI();\n                    }\n                    await processQueue();\n                    progress = (currentPage / totalPages) * 100;\n                    Config.saveSettings({ progress });\n                    UI.updateLogs();\n                    if (currentPage < totalPages && isCrawlingFlag) {\n                        const nextUrl = handler.getNextPageUrl(doc);\n                        if (nextUrl) {\n                            currentPage++;\n                            Config.saveSettings({ currentPage });\n                            depth++;\n                            Config.saveSettings({ currentDepth: depth });\n                            Logger.log(`\ub2e4\uc74c \ud398\uc774\uc9c0\ub85c \uc774\ub3d9: ${currentPage}`, 'info');\n                            await Utils.delay(Config.settings.delay + Math.random() * Config.settings.delayRandom);\n                            const nextPageContent = await Fetch.fetchPageContent(nextUrl);\n                            if (nextPageContent) {\n                                updatePageContent(nextPageContent);\n                                continue;\n                            } else {\n                                Logger.log('\ub2e4\uc74c \ud398\uc774\uc9c0 \ub0b4\uc6a9\uc744 \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.', 'error');\n                                break;\n                            }\n                        } else { Logger.log('\ub2e4\uc74c \ud398\uc774\uc9c0 \ub9c1\ud06c\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.', 'error'); break; }\n                    } else { break; }\n                }\n                if (isCrawlingFlag && currentPage > totalPages && activeRequests === 0 && requestQueue.length === 0) {\n                    Logger.log('\ud06c\ub864\ub9c1\uc774 \uc644\ub8cc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'success');\n                    finalizeCrawl('\uc644\ub8cc\ub428');\n                    isCrawlingFlag = false;\n                    Config.saveSettings({ isCrawling: isCrawlingFlag });\n                    setButtonStates();\n                    Config.saveSettings({ progress: 100 });\n                    UI.updateLogs();\n                }\n            } catch (error) {\n                Logger.log(`\ud06c\ub864\ub9c1 \uc911 \uc624\ub958 \ubc1c\uc0dd: ${error.message}`, 'error');\n                finalizeCrawl('\uc624\ub958 \ubc1c\uc0dd');\n            }\n        }\n\n        async function processQueue() {\n            while (requestQueue.length && activeRequests < Config.settings.maxConcurrentRequests && isCrawlingFlag) {\n                const { url, depth } = requestQueue.shift();\n                activeRequests++;\n                handleRequest(url, depth).finally(() => { activeRequests--; processQueue(); });\n            }\n        }\n\n        async function handleRequest(url, depth) {\n            if (!isCrawlingFlag || visitedUrls.has(url)) return;\n            try {\n                const response = await Fetch.fetchWithRetry(url, Config.settings.retryAttempts, Config.settings.retryDelay);\n                const contentType = Fetch.getContentType(response);\n                if (!Fetch.isContentTypeAllowed(contentType, Config.settings.contentTypes)) {\n                    Logger.log(`\ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc740 \ucf58\ud150\uce20 \uc720\ud615(${contentType})\uc758 URL: ${url}`, 'warning');\n                    return;\n                }\n                const parser = new DOMParser();\n                const doc = parser.parseFromString(response.responseText, 'text/html');\n                const text = doc.body ? doc.body.innerText : '';\n                if (Config.settings.enableCaptchas && Robots.detectCaptcha(doc)) { await Robots.handleCaptcha(url); return; }\n                const wc = text.split(/\\s+/).length;\n                if (wc < Config.settings.minWordCount || wc > Config.settings.maxWordCount) {\n                    Logger.log(`\ub2e8\uc5b4 \uc218 \uc870\uac74\uc5d0 \ub9de\uc9c0 \uc54a\ub294 URL: ${url} (${wc} \ub2e8\uc5b4)`, 'warning');\n                    return;\n                }\n                const quality = Evaluate.evaluateContentQuality(text);\n                const lang = doc.documentElement.lang || 'unknown';\n                const category = Classifier.classifyKeywords(text);\n                const meta = Extractor.extractMetaTags(doc);\n                const headings = Extractor.extractHeadings(doc);\n                if (category !== '\uae30\ud0c0' || quality > 100) {\n                    const entry = { url, quality, language: lang, wordCount: wc, category };\n                    if (Config.settings.dataFields.name) entry.name = Extractor.extractName(doc) || 'No Title';\n                    if (Config.settings.dataFields.description) entry.description = Extractor.extractDescription(doc) || 'No Description';\n                    if (Config.settings.dataFields.html) entry.html = response.responseText;\n                    if (Config.settings.dataFields.metaTags) entry.metaTags = meta;\n                    if (Config.settings.dataFields.headings) entry.headings = headings;\n                    data.push(entry);\n                    GM_setValue('data', JSON.stringify(data));\n                    UI.updateDataTable();\n                    Logger.log(`\ub370\uc774\ud130 \uc218\uc9d1 \uc644\ub8cc: ${url}`, 'success');\n                    if (Config.settings.dataVisualization) DataVisualization.renderChart();\n                }\n                if (depth < Config.settings.maxDepth) {\n                    const links = Extractor.extractLinks(doc);\n                    links.forEach(link => { if (!visitedUrls.has(link) && Utils.isValidURL(link)) requestQueue.push({ url: link, depth: depth + 1 }); });\n                }\n            } catch (error) {\n                Logger.log(`\ub370\uc774\ud130 \uc218\uc9d1 \uc2e4\ud328: ${url} - ${error.message}`, 'error');\n            }\n            visitedUrls.add(url);\n            GM_setValue('visitedUrls', JSON.stringify([...visitedUrls]));\n        }\n\n        function finalizeCrawl(status) {\n            if (previousCrawls.length) {\n                previousCrawls[previousCrawls.length -1].status = status;\n                GM_setValue('previousCrawls', JSON.stringify(previousCrawls));\n                UI.updatePreviousCrawlsUI();\n            }\n            if (navigationObserver) {\n                navigationObserver.disconnect();\n                navigationObserver = null;\n            }\n        }\n\n        function updateCrawlSettings() {\n            // Placeholder for any settings update needed during crawling\n        }\n\n        function updatePageContent(content) {\n            const parser = new DOMParser();\n            const doc = parser.parseFromString(content, 'text/html');\n            document.documentElement.innerHTML = doc.documentElement.innerHTML;\n        }\n\n        function monitorPageNavigation() {\n            const searchEngine = identifySearchEngine();\n            if (!searchEngine || !searchEngineHandlers[searchEngine]) return;\n            const containerSelector = searchEngineHandlers[searchEngine].getContentContainer();\n            const targetNode = document.querySelector(containerSelector);\n            if (!targetNode) {\n                Logger.log('\ud398\uc774\uc9c0 \ub0b4\ube44\uac8c\uc774\uc158 \uac10\uc2dc \ub300\uc0c1 \ub178\ub4dc\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.', 'warning');\n                return;\n            }\n            navigationObserver = new MutationObserver((mutations) => {\n                for (let mutation of mutations) {\n                    if (mutation.type === 'childList') {\n                        if (isCrawlingFlag) {\n                            Logger.log('\ud398\uc774\uc9c0 \ub0b4\ube44\uac8c\uc774\uc158 \uac10\uc9c0\ub428. \ud06c\ub864\ub9c1\uc744 \uc911\uc9c0\ud569\ub2c8\ub2e4.', 'warning');\n                            Crawling.stopCrawling();\n                            break;\n                        }\n                    }\n                }\n            });\n            navigationObserver.observe(targetNode, { childList: true, subtree: true });\n            Logger.log('\ud398\uc774\uc9c0 \ub0b4\ube44\uac8c\uc774\uc158 \uac10\uc2dc\uac00 \ud65c\uc131\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.', 'info');\n        }\n\n        return {\n            startCrawling,\n            stopCrawling,\n            isCrawling: () => isCrawlingFlag,\n            updateCrawlSettings\n        };\n    })();\n\n    /* ============================\n       Module: Fetch\n    ============================ */\n    const Fetch = (function() {\n        async function fetchWithRetry(url, attempts, delayMs) {\n            for (let i = 1; i <= attempts; i++) {\n                try {\n                    return await GM_xmlhttpRequestPromise(url);\n                } catch (e) {\n                    Logger.log(`\uc694\uccad \uc2e4\ud328 (${i}/${attempts}): ${url} - ${e.message}`, 'warning');\n                    if (i < attempts) await Utils.delay(delayMs);\n                }\n            }\n            throw new Error('\ubaa8\ub4e0 \uc7ac\uc2dc\ub3c4 \uc2dc\ub3c4\uac00 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.');\n        }\n\n        function GM_xmlhttpRequestPromise(url) {\n            return new Promise((resolve, reject) => {\n                let reqUrl = Config.settings.proxy ? `${Config.settings.proxy}?url=${encodeURIComponent(url)}` : url;\n                GM_xmlhttpRequest({\n                    method: \"GET\",\n                    url: reqUrl,\n                    headers: { 'User-Agent': getCurrentUserAgent(), 'Accept': '*/*' },\n                    timeout: Config.settings.timeout,\n                    onload: res => (res.status >= 200 && res.status < 300) ? resolve(res) : reject(new Error(`HTTP ${res.status}`)),\n                    onerror: () => reject(new Error('Request failed')),\n                    ontimeout: () => reject(new Error('Request timed out'))\n                });\n            });\n        }\n\n        function getCurrentUserAgent() {\n            const index = Config.settings.userAgentIndex % Config.settings.userAgents.length;\n            return Config.settings.userAgents[index];\n        }\n\n        function getContentType(res) {\n            return res.responseHeaders.split('\\n').find(h => h.toLowerCase().startsWith('content-type'))?.split(':')[1].trim().split(';')[0] || '';\n        }\n\n        function isContentTypeAllowed(ct, contentTypes) {\n            if (contentTypes.include.length && !contentTypes.include.includes(ct)) return false;\n            if (contentTypes.exclude.includes(ct)) return false;\n            return true;\n        }\n\n        async function fetchPageContent(url) {\n            try {\n                const response = await fetchWithRetry(url, Config.settings.retryAttempts, Config.settings.retryDelay);\n                return response.responseText;\n            } catch (error) {\n                Logger.log(`\ud398\uc774\uc9c0 \ub0b4\uc6a9 \uac00\uc838\uc624\uae30 \uc2e4\ud328: ${url} - ${error.message}`, 'error');\n                return null;\n            }\n        }\n\n        return {\n            fetchWithRetry,\n            getCurrentUserAgent,\n            getContentType,\n            isContentTypeAllowed,\n            fetchPageContent\n        };\n    })();\n\n    /* ============================\n       Module: Robots\n    ============================ */\n    const Robots = (function() {\n        async function checkRobotsTxt(url) {\n            try {\n                const parsed = new URL(url);\n                const robotsUrl = `${parsed.origin}/robots.txt`;\n                const res = await Fetch.fetchWithRetry(robotsUrl, 2, 2000);\n                return new RobotsParser(res.responseText).isAllowed(url, Fetch.getCurrentUserAgent());\n            } catch {\n                Logger.log('robots.txt \ud655\uc778 \uc2e4\ud328, \uae30\ubcf8 \ud5c8\uc6a9.', 'warning');\n                return true;\n            }\n        }\n\n        function detectCaptcha(doc) {\n            return doc.querySelector('iframe[src*=\"captcha\"], form[action*=\"captcha\"]') !== null;\n        }\n\n        async function handleCaptcha(url) {\n            Logger.log(`CAPTCHA \uac10\uc9c0\ub428: ${url}`, 'warning');\n            await Utils.delay(5000);\n            Logger.log(`CAPTCHA \ucc98\ub9ac \uc644\ub8cc: ${url}`, 'info');\n        }\n\n        class RobotsParser {\n            constructor(text) { this.rules = []; this.parse(text); }\n            parse(text) {\n                let agents = [];\n                text.split('\\n').forEach(line => {\n                    const [key, ...val] = line.split(':').map(s => s.trim());\n                    if (!key) return;\n                    if (key.toLowerCase() === 'user-agent') { agents = [val.join(':').toLowerCase()]; }\n                    else if (['disallow','allow'].includes(key.toLowerCase())) this.rules.push({ agents, type: key.toLowerCase(), path: val.join(':') });\n                });\n            }\n            isAllowed(url, ua) {\n                const parsedUrl = new URL(url);\n                const path = parsedUrl.pathname;\n                let allowed = true;\n                this.rules.forEach(rule => {\n                    if (rule.agents.includes('*') || rule.agents.includes(ua.toLowerCase())) {\n                        if (rule.type === 'disallow' && path.startsWith(rule.path)) allowed = false;\n                        if (rule.type === 'allow' && path.startsWith(rule.path)) allowed = true;\n                    }\n                });\n                return allowed;\n            }\n        }\n\n        return {\n            checkRobotsTxt,\n            detectCaptcha,\n            handleCaptcha\n        };\n    })();\n\n    /* ============================\n       Module: Extractor\n    ============================ */\n    const Extractor = (function() {\n        function extractMetaTags(doc) {\n            const metas = {};\n            doc.querySelectorAll('meta[name], meta[property]').forEach(meta => {\n                const name = meta.getAttribute('name') || meta.getAttribute('property');\n                const content = meta.getAttribute('content');\n                if (name && content) metas[name.toLowerCase()] = content;\n            });\n            return metas;\n        }\n\n        function extractHeadings(doc) {\n            const hs = [];\n            for (let i = 1; i <= 6; i++) doc.querySelectorAll(`h${i}`).forEach(h => hs.push(h.innerText.trim()));\n            return hs;\n        }\n\n        function extractName(doc) { return doc.querySelector('title')?.innerText || 'No Title'; }\n        function extractDescription(doc) { return doc.querySelector('meta[name=\"description\"]')?.getAttribute('content') || 'No Description'; }\n        function extractLinks(doc) { return Array.from(doc.querySelectorAll('a[href]')).map(a => a.href).filter(Utils.isValidURL); }\n\n        return {\n            extractMetaTags,\n            extractHeadings,\n            extractName,\n            extractDescription,\n            extractLinks\n        };\n    })();\n\n    /* ============================\n       Module: Classifier\n    ============================ */\n    const Classifier = (function() {\n        function classifyKeywords(text) {\n            const kws = combineKeywords();\n            for (const [cat, keywords] of Object.entries(kws)) {\n                if (keywords.some(kw => text.includes(kw))) return cat;\n            }\n            return '\uae30\ud0c0';\n        }\n\n        function combineKeywords() {\n            const standard = {\n                '\uad50\uc721': ['\ud559\uad50', '\uad50\uc721', '\ud559\uc0dd', '\uad50\uc218', '\uac15\uc758'],\n                '\uae30\uc220': ['\uae30\uc220', '\uc18c\ud504\ud2b8\uc6e8\uc5b4', '\ud558\ub4dc\uc6e8\uc5b4', '\ud504\ub85c\uadf8\ub798\ubc0d', '\uac1c\ubc1c'],\n                '\ub274\uc2a4': ['\ub274\uc2a4', '\uc18d\ubcf4', '\uae30\uc0ac', '\ubcf4\ub3c4'],\n                '\uc5d4\ud130\ud14c\uc778\uba3c\ud2b8': ['\uc601\ud654', '\uc74c\uc545', '\uc5f0\uc608', '\uac8c\uc784', '\ub4dc\ub77c\ub9c8'],\n                '\uc2a4\ud3ec\uce20': ['\ucd95\uad6c', '\uc57c\uad6c', '\ub18d\uad6c', '\uacbd\uae30', '\ud1a0\ub108\uba3c\ud2b8'],\n                '\ube44\uc988\ub2c8\uc2a4': ['\ube44\uc988\ub2c8\uc2a4', '\uacbd\uc81c', '\uc8fc\uc2dd', '\ud22c\uc790', '\uc2dc\uc7a5'],\n                '\uac74\uac15': ['\uac74\uac15', '\uc758\ub8cc', '\ud5ec\uc2a4', '\uc6b4\ub3d9', '\uc601\uc591'],\n                '\uacfc\ud559': ['\uacfc\ud559', '\uc5f0\uad6c', '\uc2e4\ud5d8', '\ubc1c\uacac', '\uc774\ub860'],\n                '\ubb38\ud559': ['\uc18c\uc124', '\uc2dc', '\ubb38\ud559', '\uc791\uac00', '\ucc45'],\n                '\uc5ec\ud589': ['\uc5ec\ud589', '\uad00\uad11', '\uc219\ubc15', '\ube44\ud589\uae30', '\uc5ec\uad8c']\n            };\n            const custom = Config.settings.customKeywords || {};\n            for (const [cat, kws] of Object.entries(custom)) {\n                standard[cat] = standard[cat] ? [...new Set([...standard[cat], ...kws])] : kws;\n            }\n            return standard;\n        }\n\n        return {\n            classifyKeywords\n        };\n    })();\n\n    /* ============================\n       Module: Evaluate\n    ============================ */\n    const Evaluate = (function() {\n        function evaluateContentQuality(text) { return text.split(/\\s+/).length; }\n\n        return {\n            evaluateContentQuality\n        };\n    })();\n\n    /* ============================\n       Module: Scheduler\n    ============================ */\n    const Scheduler = (function() {\n        let scheduleInterval = null;\n\n        function scheduleCrawling() {\n            clearInterval(scheduleInterval);\n            const now = new Date();\n            const [startHour, startMinute] = Config.settings.schedule.startTime.split(':').map(Number);\n            const startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), startHour, startMinute, 0);\n            if (startTime < now) startTime.setDate(startTime.getDate() + 1);\n            const initialDelay = startTime - now;\n            setTimeout(() => {\n                Crawling.startCrawling();\n                scheduleInterval = setInterval(Crawling.startCrawling, Config.settings.schedule.interval);\n            }, initialDelay);\n            Logger.log(`\ud06c\ub864\ub9c1 \uc2a4\ucf00\uc904\uc774 \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc2dc\uc791 \uc2dc\uac04: ${startTime.toLocaleString()}, \uac04\uaca9: ${Config.settings.schedule.interval / 60000}\ubd84`, 'info');\n        }\n\n        return {\n            scheduleCrawling\n        };\n    })();\n\n    /* ============================\n       Module: Download\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Module: DataVisualization\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Module: UI\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Module: Logger\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Module: Config\n    ============================ */\n    // Already defined above\n\n    /* ============================\n       Initialization\n    ============================ */\n    function init() {\n        UI.createUI();\n        UI.updateUI();\n        if (Config.settings.dataVisualization) DataVisualization.renderChart();\n        if (Config.settings.schedule.enabled) {\n            Scheduler.scheduleCrawling();\n        }\n        if (Config.settings.isCrawling) {\n            Logger.log('\ud06c\ub864\ub9c1 \uc7ac\uac1c\ub428.', 'info');\n            Crawling.startCrawling();\n        } else {\n            Logger.log('\ud06c\ub864\ub7ec \ucd08\uae30\ud654\ub428.', 'info');\n        }\n    }\n\n    window.addEventListener('load', init);\n\n})();             \uc77c\ub2e8 \ucd5c\ub300\ud55c \ub2c8\uac00 \uc4f8 \uc218 \uc788\ub294\ub370 \uae4c\uc9c0\ub294 \uc544\ubb34\ub9d0\uc5c6\uc774 \uc368\ubd10. \uadf8\ub0e5 \ucf54\ub4dc\ub9cc \uc368. \u3147\u3147 \uadf8\ub9ac\uace0 \ub2c8\uac00 \uc368\ub3c4 \ub418\ub294 \uc6a9\ub7c9\uc774 \ucd5c\ub300\uac00 \ub420\ub54c \ud30c\ud2b8 1 \uc73c\ub85c \ub05d\uc5b4 \uc54c\uaca0\uc9c0? \ucd5c\ub300\ud55c \uae38\uac8c \uc368. \ucd5c\ub300\ud55c \uae38\uac8c!! \uadf8\ub9ac\uace0 \u3148\u3134 \uc624\ub798\uae38\uac8c \uc0dd\uac01\ud574\ubd10 \uadf8\uac8c \ub9de\ub294\uc9c0 \uc544\ub2cc\uc9c0 \u3148\u3134 \uc624\ub798 \uc0dd\uac01\ud574\ubd10 \u3148\u3134 \uc9eb\uac8c \uc4f0\uba74 \uc0ac\uc6a9\uc791\uac00 \uc9c0\ub784\ud560 \uc9c0 \uc548\ud560\uc9c0"},{"assistant_initiates":false,"asst_text":"\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435. \u0412\u0437\u0440\u043e\u0441\u043b\u044b\u0435, \u0443 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u044b\u043b\u043e \u0436\u0435\u0441\u0442\u043e\u043a\u043e\u0435 \u0434\u043e\u043c\u0430\u0448\u043d\u0435\u0435 \u043f\u0440\u043e\u0448\u043b\u043e\u0435 (\u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u043e\u0435, \u044d\u043c\u043e\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0438\u043b\u0438 \u0434\u0430\u0436\u0435 \u043f\u0441\u0438\u0445\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0435), \u043c\u043e\u0433\u0443\u0442 \u0434\u043e\u043b\u0433\u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u0441\u0438\u043b\u0438\u044f. \u041c\u044b \u043d\u043e\u0441\u0438\u043c \u0441\u0432\u043e\u0451 \u043f\u0440\u043e\u0448\u043b\u043e\u0435 \u0441 \u0441\u043e\u0431\u043e\u0439 \u043f\u043e\u0432\u0441\u044e\u0434\u0443, \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 \u043d\u0430\u0441 \u0437\u0430\u0432\u0438\u0441\u0438\u0442, \u0441\u043c\u043e\u0436\u0435\u043c \u043b\u0438 \u043c\u044b \u043d\u0430\u0439\u0442\u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u0435\u0436\u043d\u0438\u0445 \u043e\u0431\u0438\u0434. \u042d\u0442\u043e \u0447\u0430\u0441\u0442\u043e \u043b\u0435\u0433\u0447\u0435 \u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u0447\u0435\u043c \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u043e \u044d\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e. \u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u0442\u0435\u0445, \u0441 \u043a\u0435\u043c \u0432\u044b \u0434\u0435\u043b\u0438\u043b\u0438\u0441\u044c \u0441\u0432\u043e\u0438\u043c\u0438 \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f\u043c\u0438, \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f, \u0447\u0442\u043e, \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u0432\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0436\u0438\u0437\u043d\u0438 \u0438 \u0442\u043e\u0433\u043e, \u043a\u0435\u043c \u0432\u044b \u0441\u0435\u0439\u0447\u0430\u0441 \u044f\u0432\u043b\u044f\u0435\u0442\u0435\u0441\u044c, \u0432\u044b \u0443\u0436\u0435 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438\u0441\u044c \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438 \u0441\u0432\u043e\u0435\u0433\u043e \u0434\u0435\u0442\u0441\u0442\u0432\u0430. \u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437 \u0441\u0430\u043c\u044b\u0445 \u0433\u043b\u0443\u0431\u043e\u043a\u0438\u0445 \u0448\u0440\u0430\u043c\u043e\u0432 \u0432 \u0436\u0438\u0437\u043d\u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435\u0432\u0438\u0434\u0438\u043c\u044b \u0438 \u043f\u043e\u0434\u0430\u0432\u043b\u0435\u043d\u044b. \u041c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u0440\u0438\u0439\u0442\u0438 \u043a \u0442\u043e\u043c\u0443 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e, \u043a\u043e\u0433\u0434\u0430 \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u043e\u0437\u0430\u0431\u043e\u0442\u0438\u0442\u044c\u0441\u044f \u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u043c \u0440\u0435\u0431\u0451\u043d\u043a\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0441\u0451 \u0435\u0449\u0451 \u0441\u0442\u0440\u0430\u0434\u0430\u0435\u0442, \u043e\u0441\u043e\u0437\u043d\u0430\u0432\u0430\u044f, \u0447\u0442\u043e \u0442\u0435, \u043a\u0442\u043e \u043f\u0440\u0438\u0447\u0438\u043d\u0438\u043b \u043d\u0430\u043c \u0431\u043e\u043b\u044c, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0438\u043c\u0435\u043b\u0438 \u0441\u0432\u043e\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0442\u0440\u0443\u0434\u043d\u043e\u0441\u0442\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u0435\u0448\u0430\u043b\u0438 \u0438\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0438 \u0432\u0435\u0441\u0442\u0438 \u0441\u0435\u0431\u044f \u0442\u0430\u043a, \u043a\u0430\u043a \u043d\u0430\u043c \u0431\u044b\u043b\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e. \u0422\u0435, \u043a\u0442\u043e \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u0440\u0438\u0447\u0438\u043d\u0438\u043b \u0432\u0430\u043c \u0431\u043e\u043b\u044c, \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u044b \u043f\u0440\u0438\u0437\u043d\u0430\u0442\u044c \u0438 \u043f\u043e\u043d\u044f\u0442\u044c, \u043a\u0430\u043a \u0438\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u043e\u0432\u043b\u0438\u044f\u043b\u0438 \u043d\u0430 \u0432\u0430\u0441, \u0434\u0430\u0436\u0435 \u0432 \u0441\u0430\u043c\u044b\u0445 \u0445\u0443\u0434\u0448\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445. \u041e\u043d\u0438 \u0441\u0430\u043c\u0438 \u043c\u043e\u0433\u0443\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u0432\u0441\u0451 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0435, \u043f\u044b\u0442\u0430\u044f\u0441\u044c \u0437\u0430\u0431\u044b\u0442\u044c \u043e \u0441\u0432\u043e\u0451\u043c \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0438 \u043f\u043e \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044e \u043a \u0432\u0430\u043c \u0432 \u0442\u0435 \u0433\u043e\u0434\u044b, \u0438\u0437 \u0447\u0443\u0432\u0441\u0442\u0432\u0430 \u0441\u0442\u044b\u0434\u0430 \u0438\u043b\u0438 \u0440\u0430\u0437\u0431\u0438\u0442\u043e\u0433\u043e \u0441\u0435\u0440\u0434\u0446\u0430 \u0437\u0430 \u0441\u0432\u043e\u0438 \u043f\u043e\u0441\u0442\u0443\u043f\u043a\u0438. \u041a\u0430\u043a \u043e\u043d\u0438 \u0432\u0435\u0434\u0443\u0442 \u0441\u0435\u0431\u044f \u043f\u043e \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044e \u043a \u0432\u0430\u043c \u0441\u0435\u0439\u0447\u0430\u0441? \u0421\u043c\u043e\u0436\u0435\u0442\u0435 \u043b\u0438 \u0432\u044b \u0432 \u0441\u0432\u043e\u0451\u043c \u0441\u0435\u0440\u0434\u0446\u0435 \u043d\u0430\u0439\u0442\u0438 \u043f\u0440\u043e\u0449\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u043f\u0440\u0438\u0447\u0438\u043d\u0438\u043b \u0432\u0430\u043c \u0431\u043e\u043b\u044c? \u041f\u0440\u043e\u0449\u0435\u043d\u0438\u0435 \u2014 \u044d\u0442\u043e \u043f\u043e\u0434\u0430\u0440\u043e\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0434\u0430\u0440\u0438\u043c \u0441\u0435\u0431\u0435, \u0438 \u043e\u043d \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0438\u0437\u0432\u0438\u043d\u0435\u043d\u0438\u0439 \u043e\u0431\u0438\u0434\u0447\u0438\u043a\u0430. \u041f\u0440\u043e\u0449\u0430\u044f (\u0434\u0430\u0436\u0435 \u043f\u0440\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0438\u0437\u0432\u0438\u043d\u0435\u043d\u0438\u0439), \u043c\u044b \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0430\u0435\u043c\u0441\u044f \u043e\u0442 \u0442\u044f\u0436\u0435\u0441\u0442\u0438 \u0431\u0440\u0435\u043c\u0435\u043d\u0438, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0442\u0440\u0430\u0436\u0430\u0435\u0442 \u043e\u0431\u0438\u0434\u0443, \u043d\u0430\u043d\u0435\u0441\u0451\u043d\u043d\u0443\u044e \u043d\u0430\u043c. \u0418\u043d\u043e\u0433\u0434\u0430 \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u0442\u0435\u0440\u0430\u043f\u0438\u044f. \u0420\u0430\u0437\u0433\u043e\u0432\u043e\u0440 \u0441 \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u043f\u0440\u0435\u0434\u0432\u0437\u044f\u0442\u044b\u043c \u0438 \u0432\u044b\u0440\u0430\u0436\u0430\u0435\u0442 \u043a \u0432\u0430\u043c \u044d\u043c\u043f\u0430\u0442\u0438\u0447\u043d\u043e\u0435 \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435, \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0435\u043c, \u0447\u0442\u043e \u0432\u0430\u043c \u0441\u0435\u0439\u0447\u0430\u0441 \u043d\u0443\u0436\u043d\u043e. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a \u0442\u0435\u0440\u0430\u043f\u0435\u0432\u0442\u0443, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043c\u043e\u0447\u044c \u0432\u0430\u043c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u044d\u0442\u0438\u043c\u0438 \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0447\u0443\u0432\u0441\u0442\u0432\u0430\u043c\u0438 \u0438 \u043c\u044b\u0441\u043b\u044f\u043c\u0438, \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u043b\u0435\u0433\u0447\u0435, \u0447\u0435\u043c \u043f\u044b\u0442\u0430\u0442\u044c\u0441\u044f \u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c\u0441\u044f \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e. \u042f \u043f\u0440\u0438\u0437\u044b\u0432\u0430\u044e \u0432\u0430\u0441 \u043d\u0430\u0439\u0442\u0438 \u043a\u043e\u0433\u043e-\u0442\u043e, \u043a\u0442\u043e \u043e\u0442\u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0441\u044f \u0432\u0430\u043c \u043d\u0430 \u044d\u0442\u043e\u043c \u043f\u0443\u0442\u0438, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u043d\u0430\u0447\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u043a \u0438\u0441\u0446\u0435\u043b\u0435\u043d\u0438\u044e. \u0421 \u0443\u0432\u0430\u0436\u0435\u043d\u0438\u0435\u043c, \u0428\u043e\u043d \u0411\u0435\u0440\u0442\u0435\u043b\u044c, M.S., LMHC","behavior":"degenerate_repetition","conversation_hash":"9f43ca6a42ce889f1f4b1f9993eda886","conversation_redacted":true,"conversation_toxic":false,"country":"United States","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":4188,"n_turns_in_conversation":1,"n_words":693,"source_row_offset":36431,"source_shard":"turns-00038.parquet","timestamp":"Thu, 24 Oct 2024 16:17:06 GMT","turn_id":"65a1aafcac8000ac469a8cdd","turn_index":0,"user_text":"System: \u0422\u044b - \u0443\u043c\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0441 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u0430 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439.\nUser: \u0417\u0430\u0434\u0430\u0447\u0430: \u041f\u0435\u0440\u0435\u0432\u0435\u0434\u0438 \u0442\u0435\u043a\u0441\u0442 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a\nUser: \u0422\u0435\u043a\u0441\u0442\u044b: Hello. Adults who come from abusive home histories (physical, emotional, or even psychological), may take a long time to heal from that abuse. We carry our past with us everywhere we go, and it is up to us to find resolutions to past hurts. This is often easier said than done, but it is possible. From the perspective of those with whom you have shared your concerns, it might be assumed that based on where you are in life and who you are now, that you have already worked through any issues from your younger years. Some of our deepest scars in life are indeed invisible and suppressed. We have to get to the place where we can take care of the inner child that is still hurting, by recognizing that those who hurt us - however they did, might have had their own challenges which prevented them from functioning and behaving in ways we might have deeply needed.\u00a0Those who have directly hurt you, may not be capable of acknowledging and understanding how you were affected by their actions - even in the worst of cases. They themselves may be doing what they can, either out of shame or heartbreak for their own actions, to work on forgetting how they behaved toward you all those years before. How are they behaving toward you now? Can you find it in your heart to forgive those who have hurt you? Forgiveness is the gift we give ourselves, and isn't dependent on an apology from an offender. By forgiving (even in the absence of any apology), we are releasing the weight of the burden we are carrying that reflects the offense we feel was committed against us. Sometimes therapy can be helpful in this process. Talking with someone who can be non-judgmental and reflects an empathetic position toward you might be what you need right now. Seeking out a therapist to help you work through these negatives feelings and thoughts is much easier, than trying to go it alone. I would encourage you to reach out and find someone who resonates with you on that journey, so that you can begin a path toward healing.Warm regards,<PRESIDIO_ANONYMIZED_PERSON>, M.S., LMHC\nAssistant:"}],"shard":{"index":38,"name":"turns-00038.parquet","rows":43233}}
