vectorplasticity commited on
Commit
38dc899
·
verified ·
1 Parent(s): b0e54f7

Add Flask app with OpenRouter models display

Browse files
Files changed (1) hide show
  1. app.py +155 -0
app.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, jsonify, Response, render_template_string
2
+ import requests
3
+ from cachetools import TTLCache
4
+ import json
5
+
6
+ app = Flask(__name__)
7
+
8
+ # Cache for 3 hours (10800 seconds)
9
+ cache = TTLCache(maxsize=1, ttl=10800)
10
+
11
+ def fetch_models():
12
+ """Fetch models from OpenRouter API."""
13
+ if 'models' in cache:
14
+ return cache['models']
15
+
16
+ resp = requests.get('https://openrouter.ai/api/v1/models', timeout=30)
17
+ resp.raise_for_status()
18
+ data = resp.json()
19
+ models = data.get('data', [])
20
+ cache['models'] = models
21
+ return models
22
+
23
+ HTML_TEMPLATE = '''
24
+ <!DOCTYPE html>
25
+ <html>
26
+ <head>
27
+ <title>OpenRouter Models</title>
28
+ <meta charset="utf-8">
29
+ <style>
30
+ body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin: 20px; background: #f5f5f5; }
31
+ .header { background: #1a1a2e; color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
32
+ .header h1 { margin: 0; }
33
+ .header p { margin: 10px 0 0; opacity: 0.8; }
34
+ .actions { margin: 20px 0; }
35
+ .btn { display: inline-block; padding: 10px 20px; margin-right: 10px; background: #4a9eff; color: white; text-decoration: none; border-radius: 5px; }
36
+ .btn:hover { background: #3a8eef; }
37
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 15px; }
38
+ .card { background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
39
+ .card h3 { margin: 0 0 10px; font-size: 14px; word-break: break-all; color: #333; }
40
+ .card .pricing { color: #28a745; font-weight: bold; margin: 5px 0; }
41
+ .card .context { color: #666; font-size: 12px; }
42
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-right: 5px; }
43
+ .badge-free { background: #28a745; color: white; }
44
+ .badge-paid { background: #dc3545; color: white; }
45
+ .status { margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee; }
46
+ .status-indicator { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; }
47
+ .status-up { background: #28a745; }
48
+ .status-down { background: #dc3545; }
49
+ .filters { margin-bottom: 15px; }
50
+ .filters input { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; width: 300px; }
51
+ table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
52
+ th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
53
+ th { background: #1a1a2e; color: white; }
54
+ tr:hover { background: #f8f9fa; }
55
+ .mode-tag { font-size: 11px; padding: 2px 6px; border-radius: 3px; background: #e9ecef; }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="header">
60
+ <h1>🤖 OpenRouter Models Directory</h1>
61
+ <p>Auto-updates every 3 hours • {{ models|length }} models available</p>
62
+ </div>
63
+
64
+ <div class="actions">
65
+ <a href="/api/raw" class="btn">📄 View Raw JSON</a>
66
+ <a href="/api/download" class="btn">⬇️ Download JSON</a>
67
+ </div>
68
+
69
+ <div class="filters">
70
+ <input type="text" id="search" placeholder="Search models..." onkeyup="filterTable()">
71
+ </div>
72
+
73
+ <table id="modelsTable">
74
+ <thead>
75
+ <tr>
76
+ <th>Model</th>
77
+ <th>Provider</th>
78
+ <th>Pricing (Prompt/Completion)</th>
79
+ <th>Context</th>
80
+ <th>Status</th>
81
+ </tr>
82
+ </thead>
83
+ <tbody>
84
+ {% for m in models %}
85
+ <tr>
86
+ <td><strong>{{ m.name or m.id }}</strong><br><small style="color:#666">{{ m.id }}</small></td>
87
+ <td>{{ m.id.split('/')[0] if '/' in m.id else 'Unknown' }}</td>
88
+ <td class="pricing">
89
+ ${{ m.pricing.prompt }}/1K → ${{ m.pricing.completion }}/1K
90
+ </td>
91
+ <td>{{ "{:,}".format(m.context_length) }} tokens</td>
92
+ <td>
93
+ {% if m.id.startswith('openai/gpt-4') or m.id.startswith('anthropic/claude-3') %}
94
+ <span class="badge badge-paid">Paid</span>
95
+ {% else %}
96
+ <span class="badge badge-free">Available</span>
97
+ {% endif %}
98
+ <div style="margin-top:5px;font-size:11px;color:#666">
99
+ Supports: {{ ", ".join(m.architecture.modality or ['text']) }}
100
+ </div>
101
+ </td>
102
+ </tr>
103
+ {% endfor %}
104
+ </tbody>
105
+ </table>
106
+
107
+ <script>
108
+ function filterTable() {
109
+ var input = document.getElementById("search");
110
+ var filter = input.value.toLowerCase();
111
+ var table = document.getElementById("modelsTable");
112
+ var tr = table.getElementsByTagName("tr");
113
+ for (var i = 1; i < tr.length; i++) {
114
+ var td = tr[i].getElementsByTagName("td");
115
+ var found = false;
116
+ for (var j = 0; j < td.length; j++) {
117
+ if (td[j] && td[j].textContent.toLowerCase().indexOf(filter) > -1) {
118
+ found = true;
119
+ break;
120
+ }
121
+ }
122
+ tr[i].style.display = found ? "" : "none";
123
+ }
124
+ }
125
+ </script>
126
+ </body>
127
+ </html>'''
128
+
129
+ @app.route('/')
130
+ def index():
131
+ models = fetch_models()
132
+ return render_template_string(HTML_TEMPLATE, models=models)
133
+
134
+ @app.route('/api/raw')
135
+ def raw_json():
136
+ """Return raw JSON with indent=4."""
137
+ models = fetch_models()
138
+ return Response(
139
+ json.dumps({'data': models}, indent=4),
140
+ mimetype='application/json'
141
+ )
142
+
143
+ @app.route('/api/download')
144
+ def download_json():
145
+ """Download JSON file."""
146
+ models = fetch_models()
147
+ json_str = json.dumps({'data': models}, indent=4)
148
+ return Response(
149
+ json_str,
150
+ mimetype='application/json',
151
+ headers={'Content-Disposition': 'attachment; filename=openrouter_models.json'}
152
+ )
153
+
154
+ if __name__ == '__main__':
155
+ app.run(host='0.0.0.0', port=7860)