AiCoderv2 commited on
Commit
0fe09d3
·
verified ·
1 Parent(s): 78505c8

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +223 -323
index.html CHANGED
@@ -1,11 +1,11 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
-
4
  <head>
5
  <meta charset="UTF-8" />
6
- <title>DuckDuckGo AI Chatbot - Enhanced Multi-Source</title>
7
  <meta name="viewport" content="width=device-width, initial-scale=1" />
8
- <meta name="description" content="An enhanced chatbot that answers using multiple data sources: DuckDuckGo, Wikipedia, and various APIs." />
 
9
  <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
@@ -28,12 +28,9 @@
28
  --radius: 14px;
29
  }
30
 
31
- * {
32
- box-sizing: border-box
33
- }
34
 
35
- html,
36
- body {
37
  margin: 0;
38
  padding: 0;
39
  height: 100%;
@@ -47,9 +44,7 @@
47
  background-attachment: fixed;
48
  }
49
 
50
- a {
51
- color: var(--accent)
52
- }
53
 
54
  .app {
55
  display: flex;
@@ -274,12 +269,9 @@
274
  color: #e9efff;
275
  }
276
 
277
- .answer p {
278
- margin: 0 0 10px 0;
279
- }
280
 
281
- .answer ul,
282
- .answer ol {
283
  margin: 6px 0 10px 18px;
284
  }
285
 
@@ -422,34 +414,15 @@
422
  animation: blink 1.4s infinite ease-in-out;
423
  }
424
 
425
- .dot:nth-child(2) {
426
- animation-delay: .2s
427
- }
428
-
429
- .dot:nth-child(3) {
430
- animation-delay: .4s
431
- }
432
 
433
  @keyframes blink {
434
-
435
- 0%,
436
- 80%,
437
- 100% {
438
- transform: translateY(0);
439
- opacity: .5
440
- }
441
-
442
- 40% {
443
- transform: translateY(-3px);
444
- opacity: 1
445
- }
446
  }
447
 
448
- .hint {
449
- font-size: 12px;
450
- color: var(--muted);
451
- padding: 4px 2px 0 2px;
452
- }
453
 
454
  .footer-note {
455
  text-align: center;
@@ -484,6 +457,11 @@
484
  border-color: rgba(168, 85, 247, 0.4);
485
  }
486
 
 
 
 
 
 
487
  .source-badge {
488
  font-size: 10px;
489
  padding: 1px 4px;
@@ -492,62 +470,6 @@
492
  margin-left: 4px;
493
  }
494
 
495
- /* Responsive */
496
- @media (max-width: 720px) {
497
- .topbar {
498
- padding: 12px
499
- }
500
-
501
- .titles .title {
502
- font-size: 17px
503
- }
504
-
505
- .links {
506
- display: none
507
- }
508
-
509
- .bubble {
510
- max-width: min(760px, 92vw)
511
- }
512
-
513
- .suggestions {
514
- bottom: 60px
515
- }
516
- }
517
-
518
- /* Scrollbar */
519
- .chat::-webkit-scrollbar {
520
- width: 10px
521
- }
522
-
523
- .chat::-webkit-scrollbar-thumb {
524
- background: #16214a;
525
- border-radius: 10px;
526
- border: 2px solid transparent;
527
- background-clip: padding-box;
528
- }
529
-
530
- .chat::-webkit-scrollbar-track {
531
- background: transparent
532
- }
533
-
534
- /* Simple fade-in for messages */
535
- .fade-in {
536
- animation: fadeIn .22s ease-out;
537
- }
538
-
539
- @keyframes fadeIn {
540
- from {
541
- opacity: 0;
542
- transform: translateY(6px)
543
- }
544
-
545
- to {
546
- opacity: 1;
547
- transform: translateY(0)
548
- }
549
- }
550
-
551
  .mono {
552
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
553
  font-size: 12px;
@@ -593,6 +515,62 @@
593
  color: #c7d5ff;
594
  margin-top: 2px;
595
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
  </style>
597
  </head>
598
 
@@ -604,21 +582,20 @@
604
  <div class="logo" aria-hidden="true"></div>
605
  <div class="titles">
606
  <div class="title">Enhanced Multi-Source AI Chatbot</div>
607
- <div class="subtitle">Answers from DuckDuckGo, Wikipedia, Weather APIs, and more.</div>
608
  </div>
609
  </div>
610
  <div class="links">
611
  <a class="btn ghost" href="https://duckduckgo.com/" target="_blank" rel="noopener noreferrer">
612
  <svg class="icon" viewBox="0 0 24 24" fill="none">
613
- <path
614
- d="M12 2a10 10 0 1 0 .001 20.001A10 10 0 0 0 12 2Zm0 0c2.5 0 4.5 5 4.5 5s-2 5-4.5 5-4.5-5-4.5-5 2-5 4.5-5Zm0 10c4.5 0 6 5 6 5"
615
- stroke="currentColor" stroke-width="2" stroke-linecap="round" />
616
  </svg>
617
  DuckDuckGo
618
  </a>
619
  <a class="btn ghost" href="https://en.wikipedia.org/" target="_blank" rel="noopener noreferrer">
620
  <svg class="icon" viewBox="0 0 24 24" fill="none">
621
- <path d="M4 4h16v16H4z" stroke="currentColor" stroke-width="2"/><path d="M7 8h10M7 12h10M7 16h6" stroke="currentColor" stroke-width="2"/>
 
622
  </svg>
623
  Wikipedia
624
  </a>
@@ -630,8 +607,7 @@
630
  <svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M3 6h18M8 6v12m8-12v12M5 6l1 14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2L19 6M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
631
  Clear
632
  </button>
633
- <a class="btn" href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener noreferrer"
634
- title="Built with anycoder">
635
  Built with anycoder
636
  </a>
637
  </div>
@@ -649,8 +625,8 @@
649
  <span>Multi-Source AI Assistant</span>
650
  </div>
651
  <div class="answer">
652
- <p>Hi! I'm your enhanced AI assistant with access to multiple data sources including DuckDuckGo Instant Answers, Wikipedia, Weather APIs, and general web search.</p>
653
- <p class="hint">Try asking about: facts, definitions, weather, time zones, calculations, historical events, scientific information, or general knowledge questions.</p>
654
  </div>
655
  </div>
656
  </div>
@@ -675,10 +651,15 @@
675
  </main>
676
 
677
  <div class="footer-note">
678
- Enhanced with multiple data sources: DuckDuckGo Instant Answers, Wikipedia API, Open-Meteo Weather API, and web search capabilities.
679
  </div>
680
  </div>
681
 
 
 
 
 
 
682
  <script>
683
  // Utilities
684
  const $ = (sel, el=document) => el.querySelector(sel);
@@ -686,6 +667,13 @@
686
  const escapeHTML = (s) => s.replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
687
  const stripTags = (s) => s.replace(/<[^>]*>/g, '');
688
  const truncate = (s, n=220) => s.length > n ? s.slice(0, n-1) + '…' : s;
 
 
 
 
 
 
 
689
 
690
  // DOM elements
691
  const chatEl = $('#chat');
@@ -696,14 +684,34 @@
696
  const suggestionsEl = $('#suggestions');
697
  const clearBtn = $('#clearBtn');
698
  const exportBtn = $('#exportBtn');
 
 
699
 
700
  // State
701
  let history = [];
702
  let abortController = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
 
704
- // Enhanced suggestions
705
  const defaultChips = [
706
- 'weather in Tokyo', 'time in London', 'define artificial intelligence', 'who was Einstein',
707
  'sqrt(144)', 'USD to EUR', 'prime factors of 91', 'Python programming history',
708
  'what is machine learning', 'how tall is Mount Everest', 'capital of France',
709
  'latest news about AI', 'JavaScript vs Python', 'quantum computing explained'
@@ -745,7 +753,6 @@
745
  if (q && !hasHistory) chips.push(q + ' history');
746
  if (q && !hasScience) chips.push(q + ' science');
747
 
748
- // de-dup
749
  const unique = [...new Set(chips)].filter(Boolean).slice(0,6);
750
  if (unique.length === 0) renderSuggestions(defaultChips);
751
  else renderSuggestions(unique);
@@ -769,14 +776,14 @@
769
  badge.className = 'badge';
770
  badge.textContent = role === 'bot' ? (meta.badge || 'AI Assistant') : 'You';
771
  metaEl.appendChild(badge);
772
-
773
  if (meta.source) {
774
  const sourceEl = document.createElement('span');
775
  sourceEl.className = `data-source-indicator ${meta.source.className || ''}`;
776
  sourceEl.innerHTML = `${meta.source.icon || '📡'} ${meta.source.name}`;
777
  metaEl.appendChild(sourceEl);
778
  }
779
-
780
  if (meta.note){
781
  const note = document.createElement('span');
782
  note.textContent = meta.note;
@@ -809,7 +816,7 @@
809
  row.appendChild(avatar);
810
  row.appendChild(bubble);
811
  messagesEl.appendChild(row);
812
- chatEl.scrollTop = chatEl.scrollHeight; // Auto scroll down
813
  return bubble;
814
  }
815
 
@@ -836,7 +843,7 @@
836
  row.appendChild(avatar);
837
  row.appendChild(bubble);
838
  messagesEl.appendChild(row);
839
- chatEl.scrollTop = chatEl.scrollHeight; // Auto scroll down
840
  return row;
841
  }
842
  function removeTyping(){
@@ -856,14 +863,14 @@
856
  for (const buildProxy of CORS_PROXIES){
857
  const proxyUrl = buildProxy(targetUrl);
858
  try{
859
- const res = await fetch(proxyUrl, { signal, headers: { 'Accept': 'application/json' } });
860
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
861
  const contentType = res.headers.get('content-type') || '';
862
- if (!contentType.includes('application/json') && !contentType.includes('text/plain')) {
863
- throw new Error('Unexpected content-type: ' + contentType);
864
- }
865
  const text = await res.text();
866
- const data = JSON.parse(text);
 
 
 
867
  return data;
868
  }catch(err){
869
  lastErr = err;
@@ -880,6 +887,40 @@
880
  this.className = className;
881
  }
882
  async search(query, signal) { throw new Error('Not implemented'); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
883
  }
884
 
885
  class DuckDuckGoSource extends DataSource {
@@ -897,59 +938,66 @@
897
  return url.toString();
898
  }
899
 
 
 
 
 
 
 
900
  async search(query, signal) {
901
  const targetUrl = this.buildDDGUrl(query);
902
- const data = await fetchWithCorsProxies(targetUrl, signal);
903
-
904
- const sources = [];
905
- if (Array.isArray(data.Results)) {
906
- data.Results.forEach(r => {
907
- if (r && r.FirstURL && r.Text) sources.push({ href: r.FirstURL, label: new URL(r.FirstURL).hostname.replace('www.','') });
908
- });
909
- }
910
- if (data.AbstractURL) sources.push({ href: data.AbstractURL, label: new URL(data.AbstractURL).hostname.replace('www.','') });
911
- if (data.Answer && data.AnswerURL) sources.push({ href: data.AnswerURL, label: new URL(data.AnswerURL).hostname.replace('www.','') });
912
-
913
- let primary = '';
914
- let note = '';
915
-
916
- if (data.Answer && data.Answer.trim()){
917
- primary = data.Answer;
918
- note = 'Instant Answer';
919
- } else if (data.Definition && data.Definition.trim()){
920
- primary = data.Definition;
921
- note = data.DefinitionSource ? ('Definition • ' + data.DefinitionSource) : 'Definition';
922
- if (data.DefinitionURL) sources.push({ href: data.DefinitionURL, label: new URL(data.DefinitionURL).hostname.replace('www.','') });
923
- } else if (data.AbstractText && data.AbstractText.trim()){
924
- primary = data.AbstractText;
925
- note = data.Heading ? ('About: ' + data.Heading) : (data.AbstractSource ? data.AbstractSource : 'Abstract');
926
- } else if (data.RelatedTopics && data.RelatedTopics.length){
927
- const items = [];
928
- for (const t of data.RelatedTopics){
929
- if (t && t.Text && t.FirstURL) {
930
- items.push(`<li><a href="${t.FirstURL}" target="_blank" rel="noopener noreferrer">${escapeHTML(t.Text)}</a></li>`);
931
- } else if (Array.isArray(t.Topics)) {
932
- for (const tt of t.Topics){
933
- if (tt && tt.Text && tt.FirstURL) {
934
- items.push(`<li><a href="${tt.FirstURL}" target="_blank" rel="noopener noreferrer">${escapeHTML(tt.Text)}</a></li>`);
 
935
  }
936
  }
 
 
 
 
 
937
  }
938
- if (items.length >= 6) break;
939
  }
940
- if (items.length){
941
- primary = `<p>Here are some related topics I found:</p><ul>${items.join('')}</ul>`;
942
- note = 'Related topics';
943
  }
944
- }
945
 
946
- return {
947
- found: !!primary,
948
- html: primary || `<p>No direct answer from DuckDuckGo.</p>`,
949
- sources,
950
- note,
951
- raw: data
952
- };
953
  }
954
  }
955
 
@@ -958,33 +1006,33 @@
958
  super('Wikipedia', '📚', 'wiki');
959
  }
960
 
 
 
 
 
 
 
 
961
  async search(query, signal) {
962
  try {
963
- // Search for pages
964
- const searchUrl = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`;
 
965
  const data = await fetchWithCorsProxies(searchUrl, signal);
966
 
967
  if (data.extract) {
968
  const sources = [];
969
  if (data.content_urls?.desktop?.page) {
970
- sources.push({
971
- href: data.content_urls.desktop.page,
972
- label: 'Wikipedia Article',
973
- multi: true
974
- });
975
  }
976
 
977
  let html = `<p>${escapeHTML(data.extract)}</p>`;
978
-
979
  if (data.extract_html) {
980
  html = data.extract_html;
981
  }
982
-
983
- // Add thumbnail if available
984
  if (data.thumbnail?.source) {
985
  html = `<img src="${data.thumbnail.source}" alt="${escapeHTML(data.title || 'Image')}" style="max-width: 200px; border-radius: 8px; margin-bottom: 10px;" /><br/>${html}`;
986
  }
987
-
988
  return {
989
  found: true,
990
  html,
@@ -993,154 +1041,6 @@
993
  raw: data
994
  };
995
  }
996
-
997
  return { found: false, html: '<p>No Wikipedia article found.</p>', sources: [], note: 'Wikipedia', raw: data };
998
  } catch (err) {
999
- return { found: false, html: '<p>Error fetching Wikipedia data.</p>', sources: [], note: 'Wikipedia', raw: null };
1000
- }
1001
- }
1002
- }
1003
-
1004
- class WeatherSource extends DataSource {
1005
- constructor() {
1006
- super('Weather', '🌤️', 'weather');
1007
- }
1008
-
1009
- async search(query, signal) {
1010
- const cityMatch = query.match(/\b(?:weather\s+in|weather|temperature\s+in|forecast\s+in)\s+([a-zA-Z\s,]+)/i);
1011
- if (!cityMatch) {
1012
- return { found: false, html: '<p>No city specified for weather.</p>', sources: [], note: 'Weather API' };
1013
- }
1014
-
1015
- const city = cityMatch[1].trim();
1016
- try {
1017
- // Using Open-Meteo API (free, no API key needed)
1018
- const geocodeUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=en&format=json`;
1019
- const geoData = await fetchWithCorsProxies(geocodeUrl, signal);
1020
-
1021
- if (geoData.results && geoData.results.length > 0) {
1022
- const location = geoData.results[0];
1023
- const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&current=temperature_2m,weather_code,wind_speed_10m&temperature_unit=celsius&wind_speed_unit=kmh`;
1024
- const weatherData = await fetchWithCorsProxies(weatherUrl, signal);
1025
-
1026
- if (weatherData.current) {
1027
- const temp = weatherData.current.temperature_2m;
1028
- const weatherCode = weatherData.current.weather_code;
1029
- const windSpeed = weatherData.current.wind_speed_10m;
1030
-
1031
- const weatherDescription = this.getWeatherDescription(weatherCode);
1032
- const sources = [
1033
- { href: `https://open-meteo.com/`, label: 'Open-Meteo API', multi: true }
1034
- ];
1035
-
1036
- const html = `
1037
- <div class="search-results">
1038
- <h4>Current Weather in ${escapeHTML(location.name)}, ${escapeHTML(location.country_code)}</h4>
1039
- <p><strong>Temperature:</strong> ${temp}°C (${Math.round(temp * 9/5 + 32)}°F)</p>
1040
- <p><strong>Conditions:</strong> ${weatherDescription}</p>
1041
- <p><strong>Wind Speed:</strong> ${windSpeed} km/h</p>
1042
- </div>
1043
- `;
1044
-
1045
- return {
1046
- found: true,
1047
- html,
1048
- sources,
1049
- note: `Weather in ${location.name}`,
1050
- raw: { location, weather: weatherData }
1051
- };
1052
- }
1053
- }
1054
-
1055
- return { found: false, html: '<p>Could not find weather data for that location.</p>', sources: [], note: 'Weather API' };
1056
- } catch (err) {
1057
- return { found: false, html: '<p>Error fetching weather data.</p>', sources: [], note: 'Weather API' };
1058
- }
1059
- }
1060
-
1061
- getWeatherDescription(code) {
1062
- const weatherCodes = {
1063
- 0: "Clear sky",
1064
- 1: "Mainly clear",
1065
- 2: "Partly cloudy",
1066
- 3: "Overcast",
1067
- 45: "Fog",
1068
- 48: "Depositing rime fog",
1069
- 51: "Light drizzle",
1070
- 53: "Moderate drizzle",
1071
- 55: "Dense drizzle",
1072
- 56: "Light freezing drizzle",
1073
- 57: "Dense freezing drizzle",
1074
- 61: "Slight rain",
1075
- 63: "Moderate rain",
1076
- 65: "Heavy rain",
1077
- 66: "Light freezing rain",
1078
- 67: "Heavy freezing rain",
1079
- 71: "Slight snow fall",
1080
- 73: "Moderate snow fall",
1081
- 75: "Heavy snow fall",
1082
- 77: "Snow grains",
1083
- 80: "Slight rain showers",
1084
- 81: "Moderate rain showers",
1085
- 82: "Violent rain showers",
1086
- 85: "Slight snow showers",
1087
- 86: "Heavy snow showers",
1088
- 95: "Slight thunderstorm",
1089
- 96: "Thunderstorm with slight hail",
1090
- 99: "Thunderstorm with heavy hail"
1091
- };
1092
- return weatherCodes[code] || "Unknown";
1093
- }
1094
- }
1095
-
1096
- class CalculatorSource extends DataSource {
1097
- constructor() {
1098
- super('Calculator', '🧮', 'calc');
1099
- }
1100
-
1101
- async search(query, signal) {
1102
- // Simple math expression evaluation
1103
- const mathMatch = query.match(/([\d\+\-\*\/\^\(\)\.\s]+)/);
1104
- if (!mathMatch) {
1105
- return { found: false, html: '<p>No mathematical expression found.</p>', sources: [], note: 'Calculator' };
1106
- }
1107
-
1108
- try {
1109
- // Simple and safe evaluation for basic math
1110
- const expression = mathMatch[1].replace(/\s/g, '');
1111
-
1112
- // Basic validation to prevent code injection
1113
- if (!/^[\d\+\-\*\/\^\(\)\.]+$/.test(expression)) {
1114
- return { found: false, html: '<p>Invalid mathematical expression.</p>', sources: [], note: 'Calculator' };
1115
- }
1116
-
1117
- // Very basic math evaluation (you might want to use a proper math library)
1118
- const result = this.safeEval(expression);
1119
-
1120
- if (result !== null && result !== undefined) {
1121
- const html = `
1122
- <div class="search-results">
1123
- <h4>Calculation Result</h4>
1124
- <p><strong>Expression:</strong> <code>${escapeHTML(expression)}</code></p>
1125
- <p><strong>Result:</strong> <span style="font-size: 18px; color: var(--success);">${result}</span></p>
1126
- </div>
1127
- `;
1128
-
1129
- return {
1130
- found: true,
1131
- html,
1132
- sources: [],
1133
- note: `Math Calculation`,
1134
- raw: { expression, result }
1135
- };
1136
- }
1137
-
1138
- return { found: false, html: '<p>Could not evaluate the expression.</p>', sources: [], note: 'Calculator' };
1139
- } catch (err) {
1140
- return { found: false, html: '<p>Error evaluating mathematical expression.</p>', sources: [], note: 'Calculator' };
1141
- }
1142
- }
1143
-
1144
- safeEval(expression) {
1145
- try {
1146
- // This is a very basic evaluator. For production,
 
1
  <!DOCTYPE html>
2
  <html lang="en">
 
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <title>Enhanced Multi-Source AI Chatbot</title>
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta name="description"
8
+ content="A responsive multi-source chatbot that answers using DuckDuckGo, Wikipedia, Weather APIs, and built-in offline knowledge/fallbacks." />
9
  <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
 
28
  --radius: 14px;
29
  }
30
 
31
+ * { box-sizing: border-box }
 
 
32
 
33
+ html, body {
 
34
  margin: 0;
35
  padding: 0;
36
  height: 100%;
 
44
  background-attachment: fixed;
45
  }
46
 
47
+ a { color: var(--accent) }
 
 
48
 
49
  .app {
50
  display: flex;
 
269
  color: #e9efff;
270
  }
271
 
272
+ .answer p { margin: 0 0 10px 0; }
 
 
273
 
274
+ .answer ul, .answer ol {
 
275
  margin: 6px 0 10px 18px;
276
  }
277
 
 
414
  animation: blink 1.4s infinite ease-in-out;
415
  }
416
 
417
+ .dot:nth-child(2) { animation-delay: .2s }
418
+ .dot:nth-child(3) { animation-delay: .4s }
 
 
 
 
 
419
 
420
  @keyframes blink {
421
+ 0%, 80%, 100% { transform: translateY(0); opacity: .5 }
422
+ 40% { transform: translateY(-3px); opacity: 1 }
 
 
 
 
 
 
 
 
 
 
423
  }
424
 
425
+ .hint { font-size: 12px; color: var(--muted); padding: 4px 2px 0 2px; }
 
 
 
 
426
 
427
  .footer-note {
428
  text-align: center;
 
457
  border-color: rgba(168, 85, 247, 0.4);
458
  }
459
 
460
+ .data-source-indicator.offline {
461
+ background: rgba(248, 113, 113, 0.2);
462
+ border-color: rgba(248, 113, 113, 0.4);
463
+ }
464
+
465
  .source-badge {
466
  font-size: 10px;
467
  padding: 1px 4px;
 
470
  margin-left: 4px;
471
  }
472
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  .mono {
474
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
475
  font-size: 12px;
 
515
  color: #c7d5ff;
516
  margin-top: 2px;
517
  }
518
+
519
+ .status {
520
+ position: fixed;
521
+ bottom: 10px;
522
+ right: 12px;
523
+ background: rgba(12, 19, 48, 0.8);
524
+ border: 1px solid rgba(255, 255, 255, .1);
525
+ padding: 6px 10px;
526
+ border-radius: 999px;
527
+ font-size: 12px;
528
+ color: var(--muted);
529
+ display: flex;
530
+ align-items: center;
531
+ gap: 8px;
532
+ backdrop-filter: blur(6px);
533
+ }
534
+
535
+ .status-dot {
536
+ width: 8px;
537
+ height: 8px;
538
+ border-radius: 50%;
539
+ background: var(--warning);
540
+ box-shadow: 0 0 8px var(--warning);
541
+ }
542
+
543
+ .status-dot.ok { background: var(--success); box-shadow: 0 0 8px var(--success); }
544
+ .status-dot.err { background: var(--danger); box-shadow: 0 0 8px var(--danger); }
545
+
546
+ /* Responsive */
547
+ @media (max-width: 720px) {
548
+ .topbar { padding: 12px }
549
+ .titles .title { font-size: 17px }
550
+ .links { display: none }
551
+ .bubble { max-width: min(760px, 92vw) }
552
+ .suggestions { bottom: 60px }
553
+ }
554
+
555
+ /* Scrollbar */
556
+ .chat::-webkit-scrollbar { width: 10px }
557
+ .chat::-webkit-scrollbar-thumb {
558
+ background: #16214a;
559
+ border-radius: 10px;
560
+ border: 2px solid transparent;
561
+ background-clip: padding-box;
562
+ }
563
+ .chat::-webkit-scrollbar-track { background: transparent }
564
+
565
+ /* Simple fade-in for messages */
566
+ .fade-in {
567
+ animation: fadeIn .22s ease-out;
568
+ }
569
+
570
+ @keyframes fadeIn {
571
+ from { opacity: 0; transform: translateY(6px) }
572
+ to { opacity: 1; transform: translateY(0) }
573
+ }
574
  </style>
575
  </head>
576
 
 
582
  <div class="logo" aria-hidden="true"></div>
583
  <div class="titles">
584
  <div class="title">Enhanced Multi-Source AI Chatbot</div>
585
+ <div class="subtitle">DuckDuckGo, Wikipedia, Weather, Calculator + Offline Fallbacks</div>
586
  </div>
587
  </div>
588
  <div class="links">
589
  <a class="btn ghost" href="https://duckduckgo.com/" target="_blank" rel="noopener noreferrer">
590
  <svg class="icon" viewBox="0 0 24 24" fill="none">
591
+ <path d="M12 2a10 10 0 1 0 .001 20.001A10 10 0 0 0 12 2Zm0 0c2.5 0 4.5 5 4.5 5s-2 5-4.5 5-4.5-5-4.5-5 2-5 4.5-5Zm0 10c4.5 0 6 5 6 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
 
 
592
  </svg>
593
  DuckDuckGo
594
  </a>
595
  <a class="btn ghost" href="https://en.wikipedia.org/" target="_blank" rel="noopener noreferrer">
596
  <svg class="icon" viewBox="0 0 24 24" fill="none">
597
+ <path d="M4 4h16v16H4z" stroke="currentColor" stroke-width="2" />
598
+ <path d="M7 8h10M7 12h10M7 16h6" stroke="currentColor" stroke-width="2" />
599
  </svg>
600
  Wikipedia
601
  </a>
 
607
  <svg class="icon" viewBox="0 0 24 24" fill="none"><path d="M3 6h18M8 6v12m8-12v12M5 6l1 14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2L19 6M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
608
  Clear
609
  </button>
610
+ <a class="btn" href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener noreferrer" title="Built with anycoder">
 
611
  Built with anycoder
612
  </a>
613
  </div>
 
625
  <span>Multi-Source AI Assistant</span>
626
  </div>
627
  <div class="answer">
628
+ <p>Hi! I'm your enhanced AI assistant with multiple data sources including DuckDuckGo Instant Answers, Wikipedia, Weather APIs, Calculator, and robust offline fallbacks.</p>
629
+ <p class="hint">Try asking about: facts, definitions, weather, time zones, calculations, historical events, or general knowledge.</p>
630
  </div>
631
  </div>
632
  </div>
 
651
  </main>
652
 
653
  <div class="footer-note">
654
+ Enhanced with multiple data sources: DuckDuckGo, Wikipedia, Open-Meteo Weather API, Calculator, and offline knowledge.
655
  </div>
656
  </div>
657
 
658
+ <div class="status" id="status">
659
+ <span class="status-dot" id="statusDot"></span>
660
+ <span id="statusText">Checking network...</span>
661
+ </div>
662
+
663
  <script>
664
  // Utilities
665
  const $ = (sel, el=document) => el.querySelector(sel);
 
667
  const escapeHTML = (s) => s.replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
668
  const stripTags = (s) => s.replace(/<[^>]*>/g, '');
669
  const truncate = (s, n=220) => s.length > n ? s.slice(0, n-1) + '…' : s;
670
+ const nowTime = () => new Date().toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
671
+
672
+ function updateTextareaHeight() {
673
+ const el = $('#prompt');
674
+ el.style.height = 'auto';
675
+ el.style.height = Math.min(el.scrollHeight, 200) + 'px';
676
+ }
677
 
678
  // DOM elements
679
  const chatEl = $('#chat');
 
684
  const suggestionsEl = $('#suggestions');
685
  const clearBtn = $('#clearBtn');
686
  const exportBtn = $('#exportBtn');
687
+ const statusDot = $('#statusDot');
688
+ const statusText = $('#statusText');
689
 
690
  // State
691
  let history = [];
692
  let abortController = null;
693
+ let lastQuery = '';
694
+ let offlineMode = false;
695
+
696
+ // Network indicator
697
+ function updateNetworkStatus() {
698
+ if (navigator.onLine) {
699
+ statusDot.className = 'status-dot ok';
700
+ statusText.textContent = 'Online';
701
+ offlineMode = false;
702
+ } else {
703
+ statusDot.className = 'status-dot err';
704
+ statusText.textContent = 'Offline';
705
+ offlineMode = true;
706
+ }
707
+ }
708
+ window.addEventListener('online', updateNetworkStatus);
709
+ window.addEventListener('offline', updateNetworkStatus);
710
+ updateNetworkStatus();
711
 
712
+ // Suggestions
713
  const defaultChips = [
714
+ 'weather in Tokyo', 'time in London', 'define artificial intelligence', 'who was Einstein',
715
  'sqrt(144)', 'USD to EUR', 'prime factors of 91', 'Python programming history',
716
  'what is machine learning', 'how tall is Mount Everest', 'capital of France',
717
  'latest news about AI', 'JavaScript vs Python', 'quantum computing explained'
 
753
  if (q && !hasHistory) chips.push(q + ' history');
754
  if (q && !hasScience) chips.push(q + ' science');
755
 
 
756
  const unique = [...new Set(chips)].filter(Boolean).slice(0,6);
757
  if (unique.length === 0) renderSuggestions(defaultChips);
758
  else renderSuggestions(unique);
 
776
  badge.className = 'badge';
777
  badge.textContent = role === 'bot' ? (meta.badge || 'AI Assistant') : 'You';
778
  metaEl.appendChild(badge);
779
+
780
  if (meta.source) {
781
  const sourceEl = document.createElement('span');
782
  sourceEl.className = `data-source-indicator ${meta.source.className || ''}`;
783
  sourceEl.innerHTML = `${meta.source.icon || '📡'} ${meta.source.name}`;
784
  metaEl.appendChild(sourceEl);
785
  }
786
+
787
  if (meta.note){
788
  const note = document.createElement('span');
789
  note.textContent = meta.note;
 
816
  row.appendChild(avatar);
817
  row.appendChild(bubble);
818
  messagesEl.appendChild(row);
819
+ chatEl.scrollTop = chatEl.scrollHeight;
820
  return bubble;
821
  }
822
 
 
843
  row.appendChild(avatar);
844
  row.appendChild(bubble);
845
  messagesEl.appendChild(row);
846
+ chatEl.scrollTop = chatEl.scrollHeight;
847
  return row;
848
  }
849
  function removeTyping(){
 
863
  for (const buildProxy of CORS_PROXIES){
864
  const proxyUrl = buildProxy(targetUrl);
865
  try{
866
+ const res = await fetch(proxyUrl, { signal, headers: { 'Accept': 'application/json, text/plain, */*' } });
867
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
868
  const contentType = res.headers.get('content-type') || '';
 
 
 
869
  const text = await res.text();
870
+ let data = text;
871
+ if (contentType.includes('application/json')) {
872
+ try { data = JSON.parse(text); } catch { /* keep text */ }
873
+ }
874
  return data;
875
  }catch(err){
876
  lastErr = err;
 
887
  this.className = className;
888
  }
889
  async search(query, signal) { throw new Error('Not implemented'); }
890
+ getPriority() { return 50; }
891
+ canHandle() { return false; }
892
+ }
893
+
894
+ class OfflineSource extends DataSource {
895
+ constructor() {
896
+ super('Offline', '💾', 'offline');
897
+ }
898
+
899
+ canHandle() { return offlineMode; }
900
+
901
+ getPriority() { return 5; } // lowest priority
902
+
903
+ async search(query, signal) {
904
+ // Use built-in knowledge base as fallback
905
+ const kb = LocalKnowledgeBase;
906
+ const found = kb.search(query);
907
+ if (found) {
908
+ return {
909
+ found: true,
910
+ html: found.html,
911
+ sources: found.sources || [],
912
+ note: found.note || 'Offline knowledge',
913
+ raw: { query }
914
+ };
915
+ }
916
+ return {
917
+ found: false,
918
+ html: `<p>I'm currently offline and don't have information about that. Please try a rephrase or check your network connection.</p>`,
919
+ sources: [],
920
+ note: 'Offline',
921
+ raw: { query }
922
+ };
923
+ }
924
  }
925
 
926
  class DuckDuckGoSource extends DataSource {
 
938
  return url.toString();
939
  }
940
 
941
+ canHandle() {
942
+ return true; // try for general queries
943
+ }
944
+
945
+ getPriority() { return 40; }
946
+
947
  async search(query, signal) {
948
  const targetUrl = this.buildDDGUrl(query);
949
+ try {
950
+ const data = await fetchWithCorsProxies(targetUrl, signal);
951
+ const sources = [];
952
+ if (Array.isArray(data.Results)) {
953
+ data.Results.forEach(r => {
954
+ if (r && r.FirstURL && r.Text) sources.push({ href: r.FirstURL, label: new URL(r.FirstURL).hostname.replace('www.','') });
955
+ });
956
+ }
957
+ if (data.AbstractURL) sources.push({ href: data.AbstractURL, label: new URL(data.AbstractURL).hostname.replace('www.','') });
958
+ if (data.Answer && data.AnswerURL) sources.push({ href: data.AnswerURL, label: new URL(data.AnswerURL).hostname.replace('www.','') });
959
+
960
+ let primary = '';
961
+ let note = '';
962
+
963
+ if (data.Answer && data.Answer.trim()){
964
+ primary = data.Answer;
965
+ note = 'Instant Answer';
966
+ } else if (data.Definition && data.Definition.trim()){
967
+ primary = data.Definition;
968
+ note = data.DefinitionSource ? ('Definition • ' + data.DefinitionSource) : 'Definition';
969
+ if (data.DefinitionURL) sources.push({ href: data.DefinitionURL, label: new URL(data.DefinitionURL).hostname.replace('www.','') });
970
+ } else if (data.AbstractText && data.AbstractText.trim()){
971
+ primary = data.AbstractText;
972
+ note = data.Heading ? ('About: ' + data.Heading) : (data.AbstractSource ? data.AbstractSource : 'Abstract');
973
+ } else if (data.RelatedTopics && data.RelatedTopics.length){
974
+ const items = [];
975
+ for (const t of data.RelatedTopics){
976
+ if (t && t.Text && t.FirstURL) {
977
+ items.push(`<li><a href="${t.FirstURL}" target="_blank" rel="noopener noreferrer">${escapeHTML(t.Text)}</a></li>`);
978
+ } else if (Array.isArray(t.Topics)) {
979
+ for (const tt of t.Topics){
980
+ if (tt && tt.Text && tt.FirstURL) {
981
+ items.push(`<li><a href="${tt.FirstURL}" target="_blank" rel="noopener noreferrer">${escapeHTML(tt.Text)}</a></li>`);
982
+ }
983
  }
984
  }
985
+ if (items.length >= 6) break;
986
+ }
987
+ if (items.length){
988
+ primary = `<p>Here are some related topics I found:</p><ul>${items.join('')}</ul>`;
989
+ note = 'Related topics';
990
  }
 
991
  }
992
+
993
+ if (!primary) {
994
+ return { found: false, html: '<p>No direct answer from DuckDuckGo.</p>', sources, note: 'DuckDuckGo', raw: data };
995
  }
 
996
 
997
+ return { found: true, html: primary, sources, note, raw: data };
998
+ } catch (err) {
999
+ return { found: false, html: '<p>Failed to fetch from DuckDuckGo (possibly blocked by CORS). Try again or use a different query.</p>', sources: [], note: 'DuckDuckGo', raw: null };
1000
+ }
 
 
 
1001
  }
1002
  }
1003
 
 
1006
  super('Wikipedia', '📚', 'wiki');
1007
  }
1008
 
1009
+ canHandle() {
1010
+ return /\b(who is|who was|what is|what was|definition|define|history|historical|article)\b/i.test(lastQuery) ||
1011
+ /\b[a-zA-Z]{3,}\b/.test(lastQuery);
1012
+ }
1013
+
1014
+ getPriority() { return 35; }
1015
+
1016
  async search(query, signal) {
1017
  try {
1018
+ // Try primary summary API
1019
+ const title = query.replace(/^(who is|who was|what is|what was|definition|define)\s+/i, '').trim();
1020
+ const searchUrl = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(title)}`;
1021
  const data = await fetchWithCorsProxies(searchUrl, signal);
1022
 
1023
  if (data.extract) {
1024
  const sources = [];
1025
  if (data.content_urls?.desktop?.page) {
1026
+ sources.push({ href: data.content_urls.desktop.page, label: 'Wikipedia Article', multi: true });
 
 
 
 
1027
  }
1028
 
1029
  let html = `<p>${escapeHTML(data.extract)}</p>`;
 
1030
  if (data.extract_html) {
1031
  html = data.extract_html;
1032
  }
 
 
1033
  if (data.thumbnail?.source) {
1034
  html = `<img src="${data.thumbnail.source}" alt="${escapeHTML(data.title || 'Image')}" style="max-width: 200px; border-radius: 8px; margin-bottom: 10px;" /><br/>${html}`;
1035
  }
 
1036
  return {
1037
  found: true,
1038
  html,
 
1041
  raw: data
1042
  };
1043
  }
 
1044
  return { found: false, html: '<p>No Wikipedia article found.</p>', sources: [], note: 'Wikipedia', raw: data };
1045
  } catch (err) {
1046
+ return { found: false, html: '<p>Error fetching Wikipedia data.</p>', sources: [], note: 'Wikipedia', raw: null };