daviddogukan commited on
Commit
250e752
·
1 Parent(s): 847d844

Add Kimi model support and enhance LLM comparison tool functionality

Browse files

- Introduced new Kimi model configurations with pricing and supported parameters in the MODELS_CONFIG.
- Implemented the call_kimi function to handle API requests for Kimi models, including cost calculation based on token usage.
- Updated the LLMClient class to route calls to Kimi models.
- Enhanced the UI to include Kimi API key input and updated model addition logic to accommodate the new provider.
- Improved results formatting and added functionality for toggling fullscreen mode and filtering results to hide errors.

Files changed (1) hide show
  1. app.py +209 -34
app.py CHANGED
@@ -68,6 +68,19 @@ MODELS_CONFIG = {
68
  "qwen3-235b-a22b-thinking-2507": {"input_price": 0.11, "output_price": 0.6, "supports": ["temperature", "top_p", "max_tokens", "top_k"]},
69
  "qwq-32b": {"input_price": 0.15, "output_price": 0.4, "supports": ["temperature", "top_p", "max_tokens", "top_k"]},
70
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
 
73
  @dataclass
@@ -357,6 +370,43 @@ class LLMClient:
357
  except Exception as e:
358
  return None, 0, 0, str(e)
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  async def call_model(self, provider: str, model: str, prompt: str, config: ModelConfig) -> tuple:
361
  """Route to appropriate API call"""
362
  # Check if API key is provided
@@ -378,6 +428,8 @@ class LLMClient:
378
  return await self.call_deepseek(model, prompt, config)
379
  elif provider == "Qwen":
380
  return await self.call_qwen(model, prompt, config)
 
 
381
  else:
382
  return None, 0, 0, f"Unknown provider: {provider}"
383
 
@@ -385,7 +437,7 @@ class LLMClient:
385
  selected_models = []
386
 
387
  def add_model(provider, model, temperature, top_p, max_tokens, top_k, freq_penalty, pres_penalty, in_price, out_price,
388
- openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key):
389
  """Add a model to the comparison list"""
390
  if not provider or not model:
391
  return create_model_list_display(), "⚠️ Please select both provider and model"
@@ -399,6 +451,7 @@ def add_model(provider, model, temperature, top_p, max_tokens, top_k, freq_penal
399
  "Mistral": mistral_key,
400
  "Deepseek": deepseek_key,
401
  "Qwen": qwen_key,
 
402
  }
403
 
404
  added_count = 0
@@ -592,6 +645,7 @@ def update_on_provider_change(provider):
592
  gr.Textbox(visible=True, info="Get key: https://console.mistral.ai/"),
593
  gr.Textbox(visible=True, info="Get key: https://platform.deepseek.com/"),
594
  gr.Textbox(visible=True, info="Get key: https://dashscope.console.aliyun.com/"),
 
595
  ]
596
  in_price = 0.0
597
  out_price = 0.0
@@ -610,6 +664,7 @@ def update_on_provider_change(provider):
610
  gr.Textbox(visible=provider == "Mistral", info="Get key: https://console.mistral.ai/"),
611
  gr.Textbox(visible=provider == "Deepseek", info="Get key: https://platform.deepseek.com/"),
612
  gr.Textbox(visible=provider == "Qwen", info="Get key: https://dashscope.console.aliyun.com/"),
 
613
  ]
614
 
615
  # Get pricing from first model as default
@@ -619,7 +674,7 @@ def update_on_provider_change(provider):
619
  out_price = model_info["output_price"]
620
  else:
621
  model_dropdown_update = gr.Dropdown(choices=[], value=None)
622
- api_keys = [gr.Textbox(visible=False)] * 7
623
  in_price = 0.0
624
  out_price = 0.0
625
 
@@ -670,7 +725,7 @@ def update_parameter_visibility(provider, model):
670
  gr.Number(visible="presence_penalty" in supported),
671
  ]
672
 
673
- async def run_comparison(prompt, openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key):
674
  """Run comparison across all selected models"""
675
  if not selected_models:
676
  return None, "⚠️ Please add at least one model first", gr.update(visible=False)
@@ -686,6 +741,7 @@ async def run_comparison(prompt, openai_key, anthropic_key, google_key, cohere_k
686
  "Mistral": mistral_key,
687
  "Deepseek": deepseek_key,
688
  "Qwen": qwen_key,
 
689
  }
690
 
691
  client = LLMClient(api_keys)
@@ -720,21 +776,30 @@ async def run_comparison(prompt, openai_key, anthropic_key, google_key, cohere_k
720
  cost = (input_tokens * input_price + output_tokens * output_price) / 1_000_000
721
  cost_per_1000 = cost * 1000
722
 
723
- # Format model parameters
724
- params_str = f"temp={model_config['temperature']}, top_p={model_config['top_p']}, max_tokens={model_config['max_tokens']}"
 
 
 
 
 
 
 
 
725
  if model_config.get('top_k'):
726
- params_str += f", top_k={model_config['top_k']}"
727
  if model_config.get('frequency_penalty'):
728
- params_str += f", freq_penalty={model_config['frequency_penalty']}"
729
  if model_config.get('presence_penalty'):
730
- params_str += f", pres_penalty={model_config['presence_penalty']}"
 
731
 
732
- # Add pricing info to model name
733
  input_price = model_config.get('input_price', 0)
734
  output_price = model_config.get('output_price', 0)
735
- params_str += f"\n💰 ${input_price:.2f}/${output_price:.2f} per 1M tokens"
736
 
737
- model_name = f"{model_config['provider']} - {model_config['model']}\n({params_str})"
738
 
739
  if error:
740
  results.append({
@@ -744,13 +809,22 @@ async def run_comparison(prompt, openai_key, anthropic_key, google_key, cohere_k
744
  "Output": f"❌ Error: {error}"
745
  })
746
  else:
747
- # Format output: preserve line breaks for markdown, remove color codes
748
- # Remove ANSI color codes
749
- output_clean = re.sub(r'\x1b\[[0-9;]*m', '', output) if output else ""
750
- # Remove HTML color tags
751
- output_clean = re.sub(r'<span[^>]*style=["\'][^"\']*color[^"\']*["\'][^>]*>|</span>', '', output_clean)
752
- # Preserve line breaks with markdown format (two spaces + newline)
753
- formatted_output = output_clean.replace('\n', ' \n') if output_clean else ""
 
 
 
 
 
 
 
 
 
754
  results.append({
755
  "Model": model_name,
756
  "Time (s)": f"{elapsed_time:.2f}",
@@ -776,7 +850,7 @@ def clear_models():
776
  selected_models.clear()
777
  return create_model_list_display(), "🗑️ All models cleared"
778
 
779
- def export_config(openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key):
780
  """Export current model configuration with API keys as JSON"""
781
  if not selected_models:
782
  return "", gr.File(visible=False, value=None), "⚠️ No models to export"
@@ -791,6 +865,7 @@ def export_config(openai_key, anthropic_key, google_key, cohere_key, mistral_key
791
  "Mistral": mistral_key,
792
  "Deepseek": deepseek_key,
793
  "Qwen": qwen_key,
 
794
  },
795
  "models": selected_models
796
  }
@@ -808,13 +883,13 @@ def export_config(openai_key, anthropic_key, google_key, cohere_key, mistral_key
808
  def import_config(config_text):
809
  """Import model configuration with API keys from JSON"""
810
  if not config_text or config_text.strip() == "":
811
- return create_model_list_display(), "", "", "", "", "", "", "", "⚠️ Please paste a configuration to import"
812
 
813
  try:
814
  config = json.loads(config_text)
815
 
816
  if "models" not in config:
817
- return create_model_list_display(), "", "", "", "", "", "", "", "⚠️ Invalid configuration format"
818
 
819
  # Clear existing models
820
  selected_models.clear()
@@ -836,15 +911,16 @@ def import_config(config_text):
836
  mistral_key = api_keys.get("Mistral", "")
837
  deepseek_key = api_keys.get("Deepseek", "")
838
  qwen_key = api_keys.get("Qwen", "")
 
839
 
840
  return (create_model_list_display(),
841
- openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key,
842
  f"✅ Imported {len(selected_models)} model(s) with API keys")
843
 
844
  except json.JSONDecodeError as e:
845
- return create_model_list_display(), "", "", "", "", "", "", "", f"⚠️ Invalid JSON format: {str(e)}"
846
  except Exception as e:
847
- return create_model_list_display(), "", "", "", "", "", "", "", f"⚠️ Import failed: {str(e)}"
848
 
849
  # Global state for panel visibility
850
  panel_visible = True
@@ -858,6 +934,33 @@ def toggle_config_panel():
858
  else:
859
  return gr.Column(visible=False), gr.Button("▶ SHOW CONFIG")
860
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
861
  # Create clean Matrix-style theme
862
  matrix_theme = gr.themes.Base(
863
  primary_hue="green",
@@ -890,6 +993,53 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
890
  .gr-box {border: none !important;}
891
  .gr-form {border: none !important; box-shadow: none !important;}
892
  .gr-group {border: none !important;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
893
  """) as demo:
894
 
895
  gr.Markdown("# ⚡ MULTI-LLM COMPARISON MATRIX ⚡")
@@ -973,6 +1123,14 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
973
  info="Get key: https://dashscope.console.aliyun.com/"
974
  )
975
 
 
 
 
 
 
 
 
 
976
  with gr.Row():
977
  input_price = gr.Number(value=0.15, label="💰 Input $/1M", precision=2, minimum=0, scale=1)
978
  output_price = gr.Number(value=0.60, label="Output $/1M", precision=2, minimum=0, scale=1)
@@ -982,7 +1140,7 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
982
  top_p_slider = gr.Number(value=1.0, label="Top-P", minimum=0, maximum=1, step=0.05, scale=1)
983
 
984
  with gr.Row():
985
- max_tokens_slider = gr.Number(value=1000, label="Max Tokens", minimum=1, maximum=4096, step=1, scale=2)
986
  top_k_number = gr.Number(value=0, label="Top-K", minimum=0, step=1, visible=True, scale=1)
987
 
988
  with gr.Row():
@@ -1014,7 +1172,8 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
1014
 
1015
  import_status = gr.Markdown("")
1016
 
1017
- with gr.Column(scale=2):
 
1018
  gr.Markdown("### 💬 INPUT PROMPT")
1019
  prompt_input = gr.Textbox(
1020
  label="",
@@ -1027,13 +1186,18 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
1027
 
1028
  comparison_status = gr.Markdown("")
1029
 
1030
- gr.Markdown("### 📊 RESULTS MATRIX")
 
 
 
 
 
1031
  results_table = gr.Dataframe(
1032
  headers=["Model", "Time (s)", "Est. Cost per 1000 calls ($)", "Output"],
1033
  wrap=True,
1034
  interactive=False,
1035
- datatype=["str", "str", "str", "markdown"],
1036
- column_widths=["20%", "10%", "15%", "55%"]
1037
  )
1038
 
1039
  export_btn = gr.Button("📥 DOWNLOAD AS CSV", variant="secondary", visible=False)
@@ -1043,7 +1207,7 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
1043
  provider_dropdown.change(
1044
  fn=update_on_provider_change,
1045
  inputs=[provider_dropdown],
1046
- outputs=[model_dropdown, openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, input_price, output_price]
1047
  )
1048
 
1049
  model_dropdown.change(
@@ -1063,7 +1227,7 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
1063
  inputs=[provider_dropdown, model_dropdown, temperature_slider, top_p_slider,
1064
  max_tokens_slider, top_k_number, freq_penalty_slider, pres_penalty_slider,
1065
  input_price, output_price, openai_key, anthropic_key, google_key,
1066
- cohere_key, mistral_key, deepseek_key, qwen_key],
1067
  outputs=[models_display, status_text]
1068
  )
1069
 
@@ -1074,7 +1238,7 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
1074
 
1075
  run_btn.click(
1076
  fn=run_comparison,
1077
- inputs=[prompt_input, openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key],
1078
  outputs=[results_table, comparison_status, export_btn]
1079
  )
1080
 
@@ -1089,20 +1253,31 @@ with gr.Blocks(title="Multi-LLM Comparison Tool", theme=matrix_theme, css="""
1089
 
1090
  export_config_btn.click(
1091
  fn=export_config,
1092
- inputs=[openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key],
1093
  outputs=[import_textbox, config_download_file, import_status]
1094
  )
1095
 
1096
  import_btn.click(
1097
  fn=import_config,
1098
  inputs=[import_textbox],
1099
- outputs=[models_display, openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, import_status]
1100
  )
1101
 
1102
  toggle_panel_btn.click(
1103
  fn=toggle_config_panel,
1104
  outputs=[config_column, toggle_panel_btn]
1105
  )
 
 
 
 
 
 
 
 
 
 
 
1106
 
1107
  if __name__ == "__main__":
1108
  demo.launch(share=True)
 
68
  "qwen3-235b-a22b-thinking-2507": {"input_price": 0.11, "output_price": 0.6, "supports": ["temperature", "top_p", "max_tokens", "top_k"]},
69
  "qwq-32b": {"input_price": 0.15, "output_price": 0.4, "supports": ["temperature", "top_p", "max_tokens", "top_k"]},
70
  },
71
+ "Kimi": {
72
+ "kimi-k2-0905-preview": {"input_price": 0.50, "output_price": 2.00, "supports": ["temperature", "top_p", "max_tokens"]},
73
+ "kimi-k2-0711-preview": {"input_price": 0.50, "output_price": 2.00, "supports": ["temperature", "top_p", "max_tokens"]},
74
+ "kimi-k2-turbo-preview": {"input_price": 0.30, "output_price": 1.20, "supports": ["temperature", "top_p", "max_tokens"]},
75
+ "moonshot-v1-8k": {"input_price": 0.12, "output_price": 0.12, "supports": ["temperature", "top_p", "max_tokens"]},
76
+ "moonshot-v1-32k": {"input_price": 0.24, "output_price": 0.24, "supports": ["temperature", "top_p", "max_tokens"]},
77
+ "moonshot-v1-128k": {"input_price": 0.60, "output_price": 0.60, "supports": ["temperature", "top_p", "max_tokens"]},
78
+ "moonshot-v1-auto": {"input_price": 0.24, "output_price": 0.24, "supports": ["temperature", "top_p", "max_tokens"]},
79
+ "kimi-latest": {"input_price": 0.50, "output_price": 2.00, "supports": ["temperature", "top_p", "max_tokens"]},
80
+ "moonshot-v1-8k-vision-preview": {"input_price": 0.15, "output_price": 0.15, "supports": ["temperature", "top_p", "max_tokens"]},
81
+ "moonshot-v1-32k-vision-preview": {"input_price": 0.30, "output_price": 0.30, "supports": ["temperature", "top_p", "max_tokens"]},
82
+ "moonshot-v1-128k-vision-preview": {"input_price": 0.70, "output_price": 0.70, "supports": ["temperature", "top_p", "max_tokens"]},
83
+ },
84
  }
85
 
86
  @dataclass
 
370
  except Exception as e:
371
  return None, 0, 0, str(e)
372
 
373
+ async def call_kimi(self, model: str, prompt: str, config: ModelConfig) -> tuple:
374
+ """Call Kimi/Moonshot API (OpenAI-compatible)"""
375
+ try:
376
+ import openai
377
+ client = openai.AsyncOpenAI(
378
+ api_key=self.api_keys.get("Kimi", ""),
379
+ base_url="https://api.moonshot.cn/v1"
380
+ )
381
+
382
+ start_time = time.time()
383
+
384
+ params = {
385
+ "model": model,
386
+ "messages": [{"role": "user", "content": prompt}],
387
+ "temperature": config.temperature,
388
+ "top_p": config.top_p,
389
+ "max_tokens": config.max_tokens,
390
+ }
391
+
392
+ response = await client.chat.completions.create(**params)
393
+
394
+ elapsed_time = time.time() - start_time
395
+ output = response.choices[0].message.content
396
+
397
+ # Calculate tokens and cost
398
+ input_tokens = response.usage.prompt_tokens if hasattr(response, 'usage') else len(prompt.split()) * 1.3
399
+ output_tokens = response.usage.completion_tokens if hasattr(response, 'usage') else len(output.split()) * 1.3
400
+
401
+ model_info = MODELS_CONFIG["Kimi"][model]
402
+ cost = (input_tokens * model_info["input_price"] + output_tokens * model_info["output_price"]) / 1_000_000
403
+ cost_per_1000 = cost * 1000
404
+
405
+ return output, elapsed_time, cost_per_1000, None
406
+
407
+ except Exception as e:
408
+ return None, 0, 0, str(e)
409
+
410
  async def call_model(self, provider: str, model: str, prompt: str, config: ModelConfig) -> tuple:
411
  """Route to appropriate API call"""
412
  # Check if API key is provided
 
428
  return await self.call_deepseek(model, prompt, config)
429
  elif provider == "Qwen":
430
  return await self.call_qwen(model, prompt, config)
431
+ elif provider == "Kimi":
432
+ return await self.call_kimi(model, prompt, config)
433
  else:
434
  return None, 0, 0, f"Unknown provider: {provider}"
435
 
 
437
  selected_models = []
438
 
439
  def add_model(provider, model, temperature, top_p, max_tokens, top_k, freq_penalty, pres_penalty, in_price, out_price,
440
+ openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key):
441
  """Add a model to the comparison list"""
442
  if not provider or not model:
443
  return create_model_list_display(), "⚠️ Please select both provider and model"
 
451
  "Mistral": mistral_key,
452
  "Deepseek": deepseek_key,
453
  "Qwen": qwen_key,
454
+ "Kimi": kimi_key,
455
  }
456
 
457
  added_count = 0
 
645
  gr.Textbox(visible=True, info="Get key: https://console.mistral.ai/"),
646
  gr.Textbox(visible=True, info="Get key: https://platform.deepseek.com/"),
647
  gr.Textbox(visible=True, info="Get key: https://dashscope.console.aliyun.com/"),
648
+ gr.Textbox(visible=True, info="Get key: https://platform.moonshot.cn/"),
649
  ]
650
  in_price = 0.0
651
  out_price = 0.0
 
664
  gr.Textbox(visible=provider == "Mistral", info="Get key: https://console.mistral.ai/"),
665
  gr.Textbox(visible=provider == "Deepseek", info="Get key: https://platform.deepseek.com/"),
666
  gr.Textbox(visible=provider == "Qwen", info="Get key: https://dashscope.console.aliyun.com/"),
667
+ gr.Textbox(visible=provider == "Kimi", info="Get key: https://platform.moonshot.cn/"),
668
  ]
669
 
670
  # Get pricing from first model as default
 
674
  out_price = model_info["output_price"]
675
  else:
676
  model_dropdown_update = gr.Dropdown(choices=[], value=None)
677
+ api_keys = [gr.Textbox(visible=False)] * 8
678
  in_price = 0.0
679
  out_price = 0.0
680
 
 
725
  gr.Number(visible="presence_penalty" in supported),
726
  ]
727
 
728
+ async def run_comparison(prompt, openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key):
729
  """Run comparison across all selected models"""
730
  if not selected_models:
731
  return None, "⚠️ Please add at least one model first", gr.update(visible=False)
 
741
  "Mistral": mistral_key,
742
  "Deepseek": deepseek_key,
743
  "Qwen": qwen_key,
744
+ "Kimi": kimi_key,
745
  }
746
 
747
  client = LLMClient(api_keys)
 
776
  cost = (input_tokens * input_price + output_tokens * output_price) / 1_000_000
777
  cost_per_1000 = cost * 1000
778
 
779
+ # Format model name in 3 lines: Provider-Model, Parameters, Costs
780
+ # Line 1: Provider - Model
781
+ line1 = f"**{model_config['provider']} - {model_config['model']}**"
782
+
783
+ # Line 2: Parameters
784
+ params_list = [
785
+ f"temp={model_config['temperature']}",
786
+ f"top_p={model_config['top_p']}",
787
+ f"max_tokens={model_config['max_tokens']}"
788
+ ]
789
  if model_config.get('top_k'):
790
+ params_list.append(f"top_k={model_config['top_k']}")
791
  if model_config.get('frequency_penalty'):
792
+ params_list.append(f"freq_pen={model_config['frequency_penalty']}")
793
  if model_config.get('presence_penalty'):
794
+ params_list.append(f"pres_pen={model_config['presence_penalty']}")
795
+ line2 = ", ".join(params_list)
796
 
797
+ # Line 3: Costs
798
  input_price = model_config.get('input_price', 0)
799
  output_price = model_config.get('output_price', 0)
800
+ line3 = f"💰 ${input_price:.2f}/${output_price:.2f} per 1M tokens"
801
 
802
+ model_name = f"{line1} \n{line2} \n{line3}"
803
 
804
  if error:
805
  results.append({
 
809
  "Output": f"❌ Error: {error}"
810
  })
811
  else:
812
+ # Format output: preserve line breaks for markdown, remove color codes and HTML
813
+ if output:
814
+ # Remove ANSI color codes
815
+ output_clean = re.sub(r'\x1b\[[0-9;]*m', '', output)
816
+ # Remove HTML tags with style attributes
817
+ output_clean = re.sub(r'<[^>]*style=[^>]*>', '', output_clean)
818
+ # Remove closing tags
819
+ output_clean = re.sub(r'</[^>]+>', '', output_clean)
820
+ # Remove HTML color/font tags
821
+ output_clean = re.sub(r'<(font|span)[^>]*>', '', output_clean)
822
+ # Remove any remaining HTML comments
823
+ output_clean = re.sub(r'<!--.*?-->', '', output_clean, flags=re.DOTALL)
824
+ # Preserve line breaks with markdown format (two spaces + newline)
825
+ formatted_output = output_clean.replace('\n', ' \n')
826
+ else:
827
+ formatted_output = ""
828
  results.append({
829
  "Model": model_name,
830
  "Time (s)": f"{elapsed_time:.2f}",
 
850
  selected_models.clear()
851
  return create_model_list_display(), "🗑️ All models cleared"
852
 
853
+ def export_config(openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key):
854
  """Export current model configuration with API keys as JSON"""
855
  if not selected_models:
856
  return "", gr.File(visible=False, value=None), "⚠️ No models to export"
 
865
  "Mistral": mistral_key,
866
  "Deepseek": deepseek_key,
867
  "Qwen": qwen_key,
868
+ "Kimi": kimi_key,
869
  },
870
  "models": selected_models
871
  }
 
883
  def import_config(config_text):
884
  """Import model configuration with API keys from JSON"""
885
  if not config_text or config_text.strip() == "":
886
+ return create_model_list_display(), "", "", "", "", "", "", "", "", "⚠️ Please paste a configuration to import"
887
 
888
  try:
889
  config = json.loads(config_text)
890
 
891
  if "models" not in config:
892
+ return create_model_list_display(), "", "", "", "", "", "", "", "", "⚠️ Invalid configuration format"
893
 
894
  # Clear existing models
895
  selected_models.clear()
 
911
  mistral_key = api_keys.get("Mistral", "")
912
  deepseek_key = api_keys.get("Deepseek", "")
913
  qwen_key = api_keys.get("Qwen", "")
914
+ kimi_key = api_keys.get("Kimi", "")
915
 
916
  return (create_model_list_display(),
917
+ openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key,
918
  f"✅ Imported {len(selected_models)} model(s) with API keys")
919
 
920
  except json.JSONDecodeError as e:
921
+ return create_model_list_display(), "", "", "", "", "", "", "", "", f"⚠️ Invalid JSON format: {str(e)}"
922
  except Exception as e:
923
+ return create_model_list_display(), "", "", "", "", "", "", "", "", f"⚠️ Import failed: {str(e)}"
924
 
925
  # Global state for panel visibility
926
  panel_visible = True
 
934
  else:
935
  return gr.Column(visible=False), gr.Button("▶ SHOW CONFIG")
936
 
937
+ # Global state for fullscreen
938
+ fullscreen_mode = False
939
+ results_data = None
940
+
941
+ def toggle_fullscreen_results():
942
+ """Toggle fullscreen mode for results - only hide config"""
943
+ global fullscreen_mode
944
+ fullscreen_mode = not fullscreen_mode
945
+ if fullscreen_mode:
946
+ return gr.Column(visible=False), gr.Button("⛶ EXIT FULLSCREEN", variant="secondary", size="sm")
947
+ else:
948
+ return gr.Column(visible=True), gr.Button("⛶ FULLSCREEN", variant="secondary", size="sm")
949
+
950
+ def filter_results(dataframe, hide_errors):
951
+ """Filter results to hide error rows if checkbox is checked"""
952
+ if dataframe is None or dataframe.empty:
953
+ return dataframe
954
+
955
+ if hide_errors:
956
+ # Filter out rows where Time or Cost column contains "ERROR"
957
+ filtered_df = dataframe[
958
+ (dataframe.iloc[:, 1] != "ERROR") &
959
+ (dataframe.iloc[:, 2] != "ERROR")
960
+ ]
961
+ return filtered_df
962
+ return dataframe
963
+
964
  # Create clean Matrix-style theme
965
  matrix_theme = gr.themes.Base(
966
  primary_hue="green",
 
993
  .gr-box {border: none !important;}
994
  .gr-form {border: none !important; box-shadow: none !important;}
995
  .gr-group {border: none !important;}
996
+
997
+ /* Clean markdown rendering in dataframe */
998
+ .dataframe .markdown {
999
+ color: #c9d1d9 !important;
1000
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
1001
+ line-height: 1.6;
1002
+ }
1003
+ .dataframe .markdown h1,
1004
+ .dataframe .markdown h2,
1005
+ .dataframe .markdown h3,
1006
+ .dataframe .markdown h4,
1007
+ .dataframe .markdown h5,
1008
+ .dataframe .markdown h6 {
1009
+ color: #c9d1d9 !important;
1010
+ font-weight: 600;
1011
+ margin-top: 0.5em;
1012
+ margin-bottom: 0.5em;
1013
+ }
1014
+ .dataframe .markdown strong {
1015
+ font-weight: 700;
1016
+ color: #c9d1d9 !important;
1017
+ }
1018
+ .dataframe .markdown em {
1019
+ font-style: italic;
1020
+ color: #c9d1d9 !important;
1021
+ }
1022
+ .dataframe .markdown code {
1023
+ background: #161b22;
1024
+ padding: 0.2em 0.4em;
1025
+ border-radius: 3px;
1026
+ font-family: monospace;
1027
+ color: #c9d1d9 !important;
1028
+ }
1029
+ .dataframe .markdown pre {
1030
+ background: #161b22;
1031
+ padding: 10px;
1032
+ border-radius: 5px;
1033
+ overflow-x: auto;
1034
+ }
1035
+ .dataframe .markdown ul, .dataframe .markdown ol {
1036
+ padding-left: 2em;
1037
+ color: #c9d1d9 !important;
1038
+ }
1039
+ .dataframe .markdown a {
1040
+ color: #58a6ff !important;
1041
+ text-decoration: none;
1042
+ }
1043
  """) as demo:
1044
 
1045
  gr.Markdown("# ⚡ MULTI-LLM COMPARISON MATRIX ⚡")
 
1123
  info="Get key: https://dashscope.console.aliyun.com/"
1124
  )
1125
 
1126
+ kimi_key = gr.Textbox(
1127
+ label="🔑 Kimi API Key",
1128
+ type="password",
1129
+ placeholder="sk-...",
1130
+ visible=True,
1131
+ info="Get key: https://platform.moonshot.cn/"
1132
+ )
1133
+
1134
  with gr.Row():
1135
  input_price = gr.Number(value=0.15, label="💰 Input $/1M", precision=2, minimum=0, scale=1)
1136
  output_price = gr.Number(value=0.60, label="Output $/1M", precision=2, minimum=0, scale=1)
 
1140
  top_p_slider = gr.Number(value=1.0, label="Top-P", minimum=0, maximum=1, step=0.05, scale=1)
1141
 
1142
  with gr.Row():
1143
+ max_tokens_slider = gr.Number(value=2000, label="Max Tokens", minimum=1, maximum=8192, step=1, scale=2)
1144
  top_k_number = gr.Number(value=0, label="Top-K", minimum=0, step=1, visible=True, scale=1)
1145
 
1146
  with gr.Row():
 
1172
 
1173
  import_status = gr.Markdown("")
1174
 
1175
+ prompt_column = gr.Column(scale=2, visible=True)
1176
+ with prompt_column:
1177
  gr.Markdown("### 💬 INPUT PROMPT")
1178
  prompt_input = gr.Textbox(
1179
  label="",
 
1186
 
1187
  comparison_status = gr.Markdown("")
1188
 
1189
+ with gr.Row():
1190
+ gr.Markdown("### 📊 RESULTS MATRIX")
1191
+ with gr.Row():
1192
+ hide_errors_checkbox = gr.Checkbox(label="Hide Errors", value=False, scale=1)
1193
+ fullscreen_results_btn = gr.Button("⛶ FULLSCREEN", variant="secondary", size="sm", scale=1)
1194
+
1195
  results_table = gr.Dataframe(
1196
  headers=["Model", "Time (s)", "Est. Cost per 1000 calls ($)", "Output"],
1197
  wrap=True,
1198
  interactive=False,
1199
+ datatype=["markdown", "str", "str", "markdown"],
1200
+ column_widths=["25%", "10%", "15%", "50%"]
1201
  )
1202
 
1203
  export_btn = gr.Button("📥 DOWNLOAD AS CSV", variant="secondary", visible=False)
 
1207
  provider_dropdown.change(
1208
  fn=update_on_provider_change,
1209
  inputs=[provider_dropdown],
1210
+ outputs=[model_dropdown, openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key, input_price, output_price]
1211
  )
1212
 
1213
  model_dropdown.change(
 
1227
  inputs=[provider_dropdown, model_dropdown, temperature_slider, top_p_slider,
1228
  max_tokens_slider, top_k_number, freq_penalty_slider, pres_penalty_slider,
1229
  input_price, output_price, openai_key, anthropic_key, google_key,
1230
+ cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key],
1231
  outputs=[models_display, status_text]
1232
  )
1233
 
 
1238
 
1239
  run_btn.click(
1240
  fn=run_comparison,
1241
+ inputs=[prompt_input, openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key],
1242
  outputs=[results_table, comparison_status, export_btn]
1243
  )
1244
 
 
1253
 
1254
  export_config_btn.click(
1255
  fn=export_config,
1256
+ inputs=[openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key],
1257
  outputs=[import_textbox, config_download_file, import_status]
1258
  )
1259
 
1260
  import_btn.click(
1261
  fn=import_config,
1262
  inputs=[import_textbox],
1263
+ outputs=[models_display, openai_key, anthropic_key, google_key, cohere_key, mistral_key, deepseek_key, qwen_key, kimi_key, import_status]
1264
  )
1265
 
1266
  toggle_panel_btn.click(
1267
  fn=toggle_config_panel,
1268
  outputs=[config_column, toggle_panel_btn]
1269
  )
1270
+
1271
+ fullscreen_results_btn.click(
1272
+ fn=toggle_fullscreen_results,
1273
+ outputs=[config_column, fullscreen_results_btn]
1274
+ )
1275
+
1276
+ hide_errors_checkbox.change(
1277
+ fn=filter_results,
1278
+ inputs=[results_table, hide_errors_checkbox],
1279
+ outputs=[results_table]
1280
+ )
1281
 
1282
  if __name__ == "__main__":
1283
  demo.launch(share=True)