-
Notifications
You must be signed in to change notification settings - Fork 684
implement openai chat completions api -- enables local model support #2600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cca274b
0e4a872
2df1efc
56e19fd
bf7324e
c81e0c6
27e1ec5
c6f2f72
12344e3
091cb0c
3399a6e
5b786c7
a65a132
09fd9bb
2644d9b
597a67b
f11cc8d
b2862e3
a3bb27c
2d1567c
2d7ce41
13c84b6
a8cf3a9
6d2b340
447d714
d7194e1
df40388
2c85b50
f07c7aa
c94e407
872eee5
83feb92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| // Copyright 2025, Command Line Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import { atoms } from "@/app/store/global"; | ||
| import { cn, makeIconClass } from "@/util/util"; | ||
| import { useAtomValue } from "jotai"; | ||
| import { memo, useRef, useState } from "react"; | ||
| import { WaveAIModel } from "./waveai-model"; | ||
|
|
||
| export const AIModeDropdown = memo(() => { | ||
| const model = WaveAIModel.getInstance(); | ||
| const aiMode = useAtomValue(model.currentAIMode); | ||
| const aiModeConfigs = useAtomValue(model.aiModeConfigs); | ||
| const rateLimitInfo = useAtomValue(atoms.waveAIRateLimitInfoAtom); | ||
| const [isOpen, setIsOpen] = useState(false); | ||
| const dropdownRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| const hasPremium = !rateLimitInfo || rateLimitInfo.unknown || rateLimitInfo.preq > 0; | ||
| const hideQuick = model.inBuilder && hasPremium; | ||
|
|
||
| const sortedConfigs = Object.entries(aiModeConfigs) | ||
| .map(([mode, config]) => ({ mode, ...config })) | ||
| .sort((a, b) => { | ||
| const orderDiff = (a["display:order"] || 0) - (b["display:order"] || 0); | ||
| if (orderDiff !== 0) return orderDiff; | ||
| return (a["display:name"] || "").localeCompare(b["display:name"] || ""); | ||
| }) | ||
| .filter((config) => !(hideQuick && config.mode === "waveai@quick")); | ||
|
|
||
| const handleSelect = (mode: string) => { | ||
| const config = aiModeConfigs[mode]; | ||
| if (!config) return; | ||
| if (!hasPremium && config["waveai:premium"]) { | ||
| return; | ||
| } | ||
| model.setAIMode(mode); | ||
| setIsOpen(false); | ||
| }; | ||
|
|
||
| let currentMode = aiMode || "waveai@balanced"; | ||
| const currentConfig = aiModeConfigs[currentMode]; | ||
| if (currentConfig) { | ||
| if (!hasPremium && currentConfig["waveai:premium"]) { | ||
| currentMode = "waveai@quick"; | ||
| } | ||
| if (hideQuick && currentMode === "waveai@quick") { | ||
| currentMode = "waveai@balanced"; | ||
| } | ||
| } | ||
|
Comment on lines
+40
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Premium fallback logic may cause UI inconsistency. The Consider calling 🤖 Prompt for AI Agents |
||
|
|
||
| const displayConfig = aiModeConfigs[currentMode] || { | ||
| "display:name": "? Unknown", | ||
| "display:icon": "question", | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="relative" ref={dropdownRef}> | ||
| <button | ||
| onClick={() => setIsOpen(!isOpen)} | ||
| className={cn( | ||
| "group flex items-center gap-1.5 px-2 py-1 text-xs text-gray-300 hover:text-white rounded transition-colors cursor-pointer border border-gray-600/50", | ||
| isOpen ? "bg-gray-700" : "bg-gray-800/50 hover:bg-gray-700" | ||
| )} | ||
| title={`AI Mode: ${displayConfig["display:name"]}`} | ||
| > | ||
| <i className={cn(makeIconClass(displayConfig["display:icon"], false), "text-[10px]")}></i> | ||
| <span className={`text-[11px] ${isOpen ? "inline" : "hidden group-hover:inline @w450:inline"}`}> | ||
| {displayConfig["display:name"]} | ||
| </span> | ||
| <i className="fa fa-chevron-down text-[8px]"></i> | ||
| </button> | ||
|
|
||
| {isOpen && ( | ||
| <> | ||
| <div className="fixed inset-0 z-40" onClick={() => setIsOpen(false)} /> | ||
| <div className="absolute top-full left-0 mt-1 bg-gray-800 border border-gray-600 rounded shadow-lg z-50 min-w-[280px]"> | ||
| {sortedConfigs.map((config, index) => { | ||
| const isFirst = index === 0; | ||
| const isLast = index === sortedConfigs.length - 1; | ||
| const isDisabled = !hasPremium && config["waveai:premium"]; | ||
| const isSelected = currentMode === config.mode; | ||
| return ( | ||
| <button | ||
| key={config.mode} | ||
| onClick={() => handleSelect(config.mode)} | ||
| disabled={isDisabled} | ||
| className={`w-full flex flex-col gap-0.5 px-3 ${ | ||
| isFirst ? "pt-1 pb-0.5" : isLast ? "pt-0.5 pb-1" : "pt-0.5 pb-0.5" | ||
| } ${ | ||
| isDisabled | ||
| ? "text-gray-500 cursor-not-allowed" | ||
| : "text-gray-300 hover:bg-gray-700 cursor-pointer" | ||
| } transition-colors text-left`} | ||
| > | ||
| <div className="flex items-center gap-2 w-full"> | ||
| <i className={makeIconClass(config["display:icon"], false)}></i> | ||
| <span className={`text-sm ${isSelected ? "font-bold" : ""}`}> | ||
| {config["display:name"]} | ||
| {isDisabled && " (premium)"} | ||
| </span> | ||
| {isSelected && <i className="fa fa-check ml-auto"></i>} | ||
| </div> | ||
| <div className="text-xs text-muted pl-5" style={{ whiteSpace: "pre-line" }}> | ||
| {config["display:description"]} | ||
| </div> | ||
| </button> | ||
| ); | ||
| })} | ||
| </div> | ||
| </> | ||
| )} | ||
| </div> | ||
| ); | ||
| }); | ||
|
|
||
| AIModeDropdown.displayName = "AIModeDropdown"; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding user feedback when premium mode selection is blocked.
When a user without premium access attempts to select a premium mode, the function silently returns (lines 33-35) without any notification or feedback. Consider adding a toast message or alert to inform users why the mode cannot be selected (e.g., "Premium subscription required for this mode").
Example implementation:
const handleSelect = (mode: string) => { const config = aiModeConfigs[mode]; if (!config) return; if (!hasPremium && config["waveai:premium"]) { + // Show toast or notification: "Premium subscription required" return; } model.setAIMode(mode); setIsOpen(false); };🤖 Prompt for AI Agents