misc: provider icons, tooltip in chat toolbar, conditionally display the "Open Skills Folder" button and update "Documentation" to "Website" in settings (#60)
41
src/App.tsx
@@ -8,6 +8,7 @@ import type { ErrorInfo, ReactNode } from 'react';
|
|||||||
import { Toaster } from 'sonner';
|
import { Toaster } from 'sonner';
|
||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
import { MainLayout } from './components/layout/MainLayout';
|
import { MainLayout } from './components/layout/MainLayout';
|
||||||
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||||
import { Dashboard } from './pages/Dashboard';
|
import { Dashboard } from './pages/Dashboard';
|
||||||
import { Chat } from './pages/Chat';
|
import { Chat } from './pages/Chat';
|
||||||
import { Channels } from './pages/Channels';
|
import { Channels } from './pages/Channels';
|
||||||
@@ -145,27 +146,29 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Routes>
|
<TooltipProvider delayDuration={300}>
|
||||||
{/* Setup wizard (shown on first launch) */}
|
<Routes>
|
||||||
<Route path="/setup/*" element={<Setup />} />
|
{/* Setup wizard (shown on first launch) */}
|
||||||
|
<Route path="/setup/*" element={<Setup />} />
|
||||||
|
|
||||||
{/* Main application routes */}
|
{/* Main application routes */}
|
||||||
<Route element={<MainLayout />}>
|
<Route element={<MainLayout />}>
|
||||||
<Route path="/" element={<Chat />} />
|
<Route path="/" element={<Chat />} />
|
||||||
<Route path="/dashboard" element={<Dashboard />} />
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
<Route path="/channels" element={<Channels />} />
|
<Route path="/channels" element={<Channels />} />
|
||||||
<Route path="/skills" element={<Skills />} />
|
<Route path="/skills" element={<Skills />} />
|
||||||
<Route path="/cron" element={<Cron />} />
|
<Route path="/cron" element={<Cron />} />
|
||||||
<Route path="/settings/*" element={<Settings />} />
|
<Route path="/settings/*" element={<Settings />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
||||||
{/* Global toast notifications */}
|
{/* Global toast notifications */}
|
||||||
<Toaster
|
<Toaster
|
||||||
position="bottom-right"
|
position="bottom-right"
|
||||||
richColors
|
richColors
|
||||||
closeButton
|
closeButton
|
||||||
/>
|
/>
|
||||||
|
</TooltipProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/assets/providers/anthropic.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#191919" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Anthropic</title><path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 296 B |
1
src/assets/providers/custom.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#191919" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Custom</title><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.49.49 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 0 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1 1 12 8.4a3.6 3.6 0 0 1 0 7.2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 779 B |
1
src/assets/providers/google.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M16 8.016A8.522 8.522 0 008.016 16h-.032A8.521 8.521 0 000 8.016v-.032A8.521 8.521 0 007.984 0h.032A8.522 8.522 0 0016 7.984v.032z" fill="#191919"/></svg>
|
||||||
|
After Width: | Height: | Size: 235 B |
19
src/assets/providers/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import anthropic from './anthropic.svg';
|
||||||
|
import openai from './openai.svg';
|
||||||
|
import google from './google.svg';
|
||||||
|
import openrouter from './openrouter.svg';
|
||||||
|
import moonshot from './moonshot.svg';
|
||||||
|
import siliconflow from './siliconflow.svg';
|
||||||
|
import ollama from './ollama.svg';
|
||||||
|
import custom from './custom.svg';
|
||||||
|
|
||||||
|
export const providerIcons: Record<string, string> = {
|
||||||
|
anthropic,
|
||||||
|
openai,
|
||||||
|
google,
|
||||||
|
openrouter,
|
||||||
|
moonshot,
|
||||||
|
siliconflow,
|
||||||
|
ollama,
|
||||||
|
custom,
|
||||||
|
};
|
||||||
1
src/assets/providers/moonshot.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#191919" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>MoonshotAI</title><path d="M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.9 KiB |
1
src/assets/providers/ollama.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#000000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Ollama</title><path d="M16.361 10.26a.894.894 0 0 0-.558.47l-.072.148.001.207c0 .193.004.217.059.353.076.193.152.312.291.448.24.238.51.3.872.205a.86.86 0 0 0 .517-.436.752.752 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.06 1.06 0 0 0-.466 0zm-9.203.005c-.305.096-.533.32-.65.639a1.187 1.187 0 0 0-.06.52c.057.309.31.59.598.667.362.095.632.033.872-.205.14-.136.215-.255.291-.448.055-.136.059-.16.059-.353l.001-.207-.072-.148a.894.894 0 0 0-.565-.472 1.02 1.02 0 0 0-.474.007Zm4.184 2c-.131.071-.223.25-.195.383.031.143.157.288.353.407.105.063.112.072.117.136.004.038-.01.146-.029.243-.02.094-.036.194-.036.222.002.074.07.195.143.253.064.052.076.054.255.059.164.005.198.001.264-.03.169-.082.212-.234.15-.525-.052-.243-.042-.28.087-.355.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48.394.394 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053-.053-.032c-.219-.13-.259-.145-.391-.143a.396.396 0 0 0-.193.032zm.39-2.195c-.373.036-.475.05-.654.086-.291.06-.68.195-.951.328-.94.46-1.589 1.226-1.787 2.114-.04.176-.045.234-.045.53 0 .294.005.357.043.524.264 1.16 1.332 2.017 2.714 2.173.3.033 1.596.033 1.896 0 1.11-.125 2.064-.727 2.493-1.571.114-.226.169-.372.22-.602.039-.167.044-.23.044-.523 0-.297-.005-.355-.045-.531-.288-1.29-1.539-2.304-3.072-2.497a6.873 6.873 0 0 0-.855-.031zm.645.937a3.283 3.283 0 0 1 1.44.514c.223.148.537.458.671.662.166.251.26.508.303.82.02.143.01.251-.043.482-.08.345-.332.705-.672.957a3.115 3.115 0 0 1-.689.348c-.382.122-.632.144-1.525.138-.582-.006-.686-.01-.853-.042-.57-.107-1.022-.334-1.35-.68-.264-.28-.385-.535-.45-.946-.03-.192.025-.509.137-.776.136-.326.488-.73.836-.963.403-.269.934-.46 1.422-.512.187-.02.586-.02.773-.002zm-5.503-11a1.653 1.653 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503 0 .544.064 1.24.155 1.721.02.107.031.202.023.208a8.12 8.12 0 0 1-.187.152 5.324 5.324 0 0 0-.949 1.02 5.49 5.49 0 0 0-.94 2.339 6.625 6.625 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195-.037.064c-.269.452-.498 1.105-.605 1.732-.084.496-.095.629-.095 1.294 0 .67.009.803.088 1.266.095.555.288 1.143.503 1.534.071.128.243.393.264.407.007.003-.014.067-.046.141a7.405 7.405 0 0 0-.548 1.873c-.062.417-.071.552-.071.991 0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.915.915 0 0 0-.194-.25 1.74 1.74 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451.124-.486.329-.918.544-1.154a.787.787 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.136 3.136 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834.563-.814 1.353-1.336 2.237-1.475.199-.033.57-.028.776.01.226.04.367.028.512-.041.179-.085.268-.19.374-.431.093-.215.165-.333.36-.576.234-.29.46-.489.822-.729.413-.27.884-.467 1.352-.561.17-.035.25-.04.569-.04.319 0 .398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602.034.057.095.177.132.267.105.241.195.346.374.43.14.068.286.082.503.045.343-.058.607-.053.943.016 1.144.23 2.14 1.173 2.581 2.437.385 1.108.276 2.267-.296 3.153-.097.15-.193.27-.333.419-.301.322-.301.722-.001 1.053.493.539.801 1.866.708 3.036-.062.772-.26 1.463-.533 1.854a2.096 2.096 0 0 1-.224.258.916.916 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295.253 1.008.231 2.01-.059 2.581a.845.845 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074.036-.134c.019-.076.057-.3.088-.516.029-.217.029-1.016 0-1.258-.11-.875-.295-1.57-.597-2.226-.032-.074-.053-.138-.046-.141.008-.005.057-.074.108-.152.376-.569.607-1.284.724-2.228.031-.26.031-1.378 0-1.628-.083-.645-.182-1.082-.348-1.525a6.083 6.083 0 0 0-.329-.7l-.038-.064.131-.194c.402-.604.636-1.262.727-2.04a6.625 6.625 0 0 0-.024-1.358 5.512 5.512 0 0 0-.939-2.339 5.325 5.325 0 0 0-.95-1.02 8.097 8.097 0 0 1-.186-.152.692.692 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503-.19-.924-.535-1.658-.98-2.082-.354-.338-.716-.482-1.15-.455-.996.059-1.8 1.205-2.116 3.01a6.805 6.805 0 0 0-.097.726c0 .036-.007.066-.015.066a.96.96 0 0 1-.149-.078A4.857 4.857 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a.958.958 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a6.71 6.71 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2.096 2.096 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388.03.113.06.244.069.292.007.047.026.152.041.233.067.365.098.76.102 1.24l.002.475-.12.175-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.438 8.438 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711.067-.05.079-.049.157.013zm9.825-.012c.17.126.358.46.498.888.28.854.36 2.028.212 3.145-.019.14-.024.151-.057.144l-.238-.06a3.693 3.693 0 0 0-.954-.124h-.278l-.119-.178-.119-.175.002-.474c.004-.669.066-1.19.214-1.772.157-.623.434-1.185.68-1.382.078-.062.09-.063.159-.012z"/></svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
1
src/assets/providers/openai.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#000000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>OpenAI</title><path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.666zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
src/assets/providers/openrouter.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="#191919" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>OpenRouter</title><path d="M16.778 1.844v1.919q-.569-.026-1.138-.032-.708-.008-1.415.037c-1.93.126-4.023.728-6.149 2.237-2.911 2.066-2.731 1.95-4.14 2.75-.396.223-1.342.574-2.185.798-.841.225-1.753.333-1.751.333v4.229s.768.108 1.61.333c.842.224 1.789.575 2.185.799 1.41.798 1.228.683 4.14 2.75 2.126 1.509 4.22 2.11 6.148 2.236.88.058 1.716.041 2.555.005v1.918l7.222-4.168-7.222-4.17v2.176c-.86.038-1.611.065-2.278.021-1.364-.09-2.417-.357-3.979-1.465-2.244-1.593-2.866-2.027-3.68-2.508.889-.518 1.449-.906 3.822-2.59 1.56-1.109 2.614-1.377 3.978-1.466.667-.044 1.418-.017 2.278.02v2.176L24 6.014Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 699 B |
1
src/assets/providers/siliconflow.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>SiliconCloud</title><path clip-rule="evenodd" d="M22.956 6.521H12.522c-.577 0-1.044.468-1.044 1.044v3.13c0 .577-.466 1.044-1.043 1.044H1.044c-.577 0-1.044.467-1.044 1.044v4.174C0 17.533.467 18 1.044 18h10.434c.577 0 1.044-.467 1.044-1.043v-3.13c0-.578.466-1.044 1.043-1.044h9.391c.577 0 1.044-.467 1.044-1.044V7.565c0-.576-.467-1.044-1.044-1.044z" fill="#191919" fill-rule="evenodd"></path></svg>
|
||||||
|
After Width: | Height: | Size: 520 B |
@@ -26,6 +26,8 @@ import { useProviderStore, type ProviderConfig, type ProviderWithKeyInfo } from
|
|||||||
import {
|
import {
|
||||||
PROVIDER_TYPE_INFO,
|
PROVIDER_TYPE_INFO,
|
||||||
type ProviderType,
|
type ProviderType,
|
||||||
|
getProviderIconUrl,
|
||||||
|
shouldInvertInDark,
|
||||||
} from '@/lib/providers';
|
} from '@/lib/providers';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -292,7 +294,11 @@ function ProviderCard({
|
|||||||
{/* Top row: icon + name + toggle */}
|
{/* Top row: icon + name + toggle */}
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-xl">{typeInfo?.icon || '⚙️'}</span>
|
{getProviderIconUrl(provider.type) ? (
|
||||||
|
<img src={getProviderIconUrl(provider.type)} alt={typeInfo?.name || provider.type} className={cn('h-5 w-5', shouldInvertInDark(provider.type) && 'dark:invert')} />
|
||||||
|
) : (
|
||||||
|
<span className="text-xl">{typeInfo?.icon || '⚙️'}</span>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-semibold">{provider.name}</span>
|
<span className="font-semibold">{provider.name}</span>
|
||||||
@@ -521,7 +527,11 @@ function AddProviderDialog({ existingTypes, onClose, onAdd, onValidateKey }: Add
|
|||||||
}}
|
}}
|
||||||
className="p-4 rounded-lg border hover:bg-accent transition-colors text-center"
|
className="p-4 rounded-lg border hover:bg-accent transition-colors text-center"
|
||||||
>
|
>
|
||||||
<span className="text-2xl">{type.icon}</span>
|
{getProviderIconUrl(type.id) ? (
|
||||||
|
<img src={getProviderIconUrl(type.id)} alt={type.name} className={cn('h-7 w-7 mx-auto', shouldInvertInDark(type.id) && 'dark:invert')} />
|
||||||
|
) : (
|
||||||
|
<span className="text-2xl">{type.icon}</span>
|
||||||
|
)}
|
||||||
<p className="font-medium mt-2">{type.name}</p>
|
<p className="font-medium mt-2">{type.name}</p>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -529,7 +539,11 @@ function AddProviderDialog({ existingTypes, onClose, onAdd, onValidateKey }: Add
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted">
|
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted">
|
||||||
<span className="text-2xl">{typeInfo?.icon}</span>
|
{getProviderIconUrl(selectedType!) ? (
|
||||||
|
<img src={getProviderIconUrl(selectedType!)} alt={typeInfo?.name} className={cn('h-7 w-7', shouldInvertInDark(selectedType!) && 'dark:invert')} />
|
||||||
|
) : (
|
||||||
|
<span className="text-2xl">{typeInfo?.icon}</span>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">{typeInfo?.name}</p>
|
<p className="font-medium">{typeInfo?.name}</p>
|
||||||
<button
|
<button
|
||||||
|
|||||||
28
src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||||
@@ -9,5 +9,11 @@
|
|||||||
"creativeTasks": "Creative Tasks",
|
"creativeTasks": "Creative Tasks",
|
||||||
"creativeTasksDesc": "Writing, brainstorming, ideas"
|
"creativeTasksDesc": "Writing, brainstorming, ideas"
|
||||||
},
|
},
|
||||||
"noLogs": "(No logs available yet)"
|
"noLogs": "(No logs available yet)",
|
||||||
|
"toolbar": {
|
||||||
|
"newSession": "New session",
|
||||||
|
"refresh": "Refresh chat",
|
||||||
|
"showThinking": "Show thinking",
|
||||||
|
"hideThinking": "Hide thinking"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"tagline": "Graphical AI Assistant",
|
"tagline": "Graphical AI Assistant",
|
||||||
"basedOn": "Based on OpenClaw",
|
"basedOn": "Based on OpenClaw",
|
||||||
"version": "Version {{version}}",
|
"version": "Version {{version}}",
|
||||||
"docs": "Documentation",
|
"docs": "Website",
|
||||||
"github": "GitHub"
|
"github": "GitHub"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,8 @@
|
|||||||
"failedSave": "Failed to save configuration",
|
"failedSave": "Failed to save configuration",
|
||||||
"failedOpenFolder": "Failed to open skills folder",
|
"failedOpenFolder": "Failed to open skills folder",
|
||||||
"failedInstall": "Failed to install",
|
"failedInstall": "Failed to install",
|
||||||
"failedUninstall": "Failed to uninstall"
|
"failedUninstall": "Failed to uninstall",
|
||||||
|
"failedFolderNotFound": "Skills folder does not exist yet. Install a skill first."
|
||||||
},
|
},
|
||||||
"marketplace": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -9,5 +9,11 @@
|
|||||||
"creativeTasks": "クリエイティブタスク",
|
"creativeTasks": "クリエイティブタスク",
|
||||||
"creativeTasksDesc": "ライティング、ブレスト、アイデア"
|
"creativeTasksDesc": "ライティング、ブレスト、アイデア"
|
||||||
},
|
},
|
||||||
"noLogs": "(ログはまだありません)"
|
"noLogs": "(ログはまだありません)",
|
||||||
|
"toolbar": {
|
||||||
|
"newSession": "新しいセッション",
|
||||||
|
"refresh": "チャットを更新",
|
||||||
|
"showThinking": "思考を表示",
|
||||||
|
"hideThinking": "思考を非表示"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
"tagline": "グラフィカル AI アシスタント",
|
"tagline": "グラフィカル AI アシスタント",
|
||||||
"basedOn": "OpenClaw ベース",
|
"basedOn": "OpenClaw ベース",
|
||||||
"version": "バージョン {{version}}",
|
"version": "バージョン {{version}}",
|
||||||
"docs": "ドキュメント",
|
"docs": "公式サイト",
|
||||||
"github": "GitHub"
|
"github": "GitHub"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,8 @@
|
|||||||
"failedSave": "設定の保存に失敗しました",
|
"failedSave": "設定の保存に失敗しました",
|
||||||
"failedOpenFolder": "スキルフォルダを開けませんでした",
|
"failedOpenFolder": "スキルフォルダを開けませんでした",
|
||||||
"failedInstall": "インストールに失敗しました",
|
"failedInstall": "インストールに失敗しました",
|
||||||
"failedUninstall": "アンインストールに失敗しました"
|
"failedUninstall": "アンインストールに失敗しました",
|
||||||
|
"failedFolderNotFound": "スキルフォルダがまだ存在しません。先にスキルをインストールしてください。"
|
||||||
},
|
},
|
||||||
"marketplace": {
|
"marketplace": {
|
||||||
"title": "マーケットプレイス",
|
"title": "マーケットプレイス",
|
||||||
|
|||||||
@@ -9,5 +9,11 @@
|
|||||||
"creativeTasks": "创意任务",
|
"creativeTasks": "创意任务",
|
||||||
"creativeTasksDesc": "写作、头脑风暴、创意"
|
"creativeTasksDesc": "写作、头脑风暴、创意"
|
||||||
},
|
},
|
||||||
"noLogs": "(暂无日志)"
|
"noLogs": "(暂无日志)",
|
||||||
|
"toolbar": {
|
||||||
|
"newSession": "新会话",
|
||||||
|
"refresh": "刷新聊天",
|
||||||
|
"showThinking": "显示思考过程",
|
||||||
|
"hideThinking": "隐藏思考过程"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"tagline": "图形化 AI 助手",
|
"tagline": "图形化 AI 助手",
|
||||||
"basedOn": "基于 OpenClaw",
|
"basedOn": "基于 OpenClaw",
|
||||||
"version": "版本 {{version}}",
|
"version": "版本 {{version}}",
|
||||||
"docs": "文档",
|
"docs": "官网",
|
||||||
"github": "GitHub"
|
"github": "GitHub"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,8 @@
|
|||||||
"failedSave": "保存配置失败",
|
"failedSave": "保存配置失败",
|
||||||
"failedOpenFolder": "无法打开技能文件夹",
|
"failedOpenFolder": "无法打开技能文件夹",
|
||||||
"failedInstall": "安装失败",
|
"failedInstall": "安装失败",
|
||||||
"failedUninstall": "卸载失败"
|
"failedUninstall": "卸载失败",
|
||||||
|
"failedFolderNotFound": "技能文件夹尚不存在,请先安装一个技能。"
|
||||||
},
|
},
|
||||||
"marketplace": {
|
"marketplace": {
|
||||||
"title": "市场",
|
"title": "市场",
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ export interface ProviderTypeInfo {
|
|||||||
defaultModelId?: string;
|
defaultModelId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { providerIcons } from '@/assets/providers';
|
||||||
|
|
||||||
/** All supported provider types with UI metadata */
|
/** All supported provider types with UI metadata */
|
||||||
export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [
|
export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [
|
||||||
{ id: 'anthropic', name: 'Anthropic', icon: '🤖', placeholder: 'sk-ant-api03-...', model: 'Claude', requiresApiKey: true },
|
{ id: 'anthropic', name: 'Anthropic', icon: '🤖', placeholder: 'sk-ant-api03-...', model: 'Claude', requiresApiKey: true },
|
||||||
@@ -65,6 +67,16 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [
|
|||||||
{ id: 'custom', name: 'Custom', icon: '⚙️', placeholder: 'API key...', requiresApiKey: true, showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'your-provider/model-id' },
|
{ id: 'custom', name: 'Custom', icon: '⚙️', placeholder: 'API key...', requiresApiKey: true, showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'your-provider/model-id' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** Get the SVG logo URL for a provider type, falls back to undefined */
|
||||||
|
export function getProviderIconUrl(type: ProviderType | string): string | undefined {
|
||||||
|
return providerIcons[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether a provider's logo needs CSS invert in dark mode (all logos are monochrome) */
|
||||||
|
export function shouldInvertInDark(_type: ProviderType | string): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Provider list shown in the Setup wizard */
|
/** Provider list shown in the Setup wizard */
|
||||||
export const SETUP_PROVIDERS = PROVIDER_TYPE_INFO;
|
export const SETUP_PROVIDERS = PROVIDER_TYPE_INFO;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Supports: file picker, clipboard paste, drag & drop.
|
* Supports: file picker, clipboard paste, drag & drop.
|
||||||
*/
|
*/
|
||||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||||
import { Send, Square, ImagePlus, X } from 'lucide-react';
|
import { Send, Square, X } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
|
||||||
@@ -200,18 +200,7 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false }:
|
|||||||
|
|
||||||
{/* Input Row */}
|
{/* Input Row */}
|
||||||
<div className={`flex items-end gap-2 ${dragOver ? 'ring-2 ring-primary rounded-lg' : ''}`}>
|
<div className={`flex items-end gap-2 ${dragOver ? 'ring-2 ring-primary rounded-lg' : ''}`}>
|
||||||
{/* Image Upload Button */}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="shrink-0 h-[44px] w-[44px] text-muted-foreground hover:text-foreground"
|
|
||||||
onClick={() => fileInputRef.current?.click()}
|
|
||||||
disabled={disabled}
|
|
||||||
title="Attach image"
|
|
||||||
>
|
|
||||||
<ImagePlus className="h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
|
|||||||
@@ -5,8 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
import { RefreshCw, Brain, ChevronDown, Plus } from 'lucide-react';
|
import { RefreshCw, Brain, ChevronDown, Plus } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { useChatStore } from '@/stores/chat';
|
import { useChatStore } from '@/stores/chat';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export function ChatToolbar() {
|
export function ChatToolbar() {
|
||||||
const sessions = useChatStore((s) => s.sessions);
|
const sessions = useChatStore((s) => s.sessions);
|
||||||
@@ -17,6 +19,7 @@ export function ChatToolbar() {
|
|||||||
const loading = useChatStore((s) => s.loading);
|
const loading = useChatStore((s) => s.loading);
|
||||||
const showThinking = useChatStore((s) => s.showThinking);
|
const showThinking = useChatStore((s) => s.showThinking);
|
||||||
const toggleThinking = useChatStore((s) => s.toggleThinking);
|
const toggleThinking = useChatStore((s) => s.toggleThinking);
|
||||||
|
const { t } = useTranslation('chat');
|
||||||
|
|
||||||
const handleSessionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleSessionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
switchSession(e.target.value);
|
switchSession(e.target.value);
|
||||||
@@ -51,41 +54,59 @@ export function ChatToolbar() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* New Session */}
|
{/* New Session */}
|
||||||
<Button
|
<Tooltip>
|
||||||
variant="ghost"
|
<TooltipTrigger asChild>
|
||||||
size="icon"
|
<Button
|
||||||
className="h-8 w-8"
|
variant="ghost"
|
||||||
onClick={newSession}
|
size="icon"
|
||||||
title="New session"
|
className="h-8 w-8"
|
||||||
>
|
onClick={newSession}
|
||||||
<Plus className="h-4 w-4" />
|
>
|
||||||
</Button>
|
<Plus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t('toolbar.newSession')}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
{/* Refresh */}
|
{/* Refresh */}
|
||||||
<Button
|
<Tooltip>
|
||||||
variant="ghost"
|
<TooltipTrigger asChild>
|
||||||
size="icon"
|
<Button
|
||||||
className="h-8 w-8"
|
variant="ghost"
|
||||||
onClick={() => refresh()}
|
size="icon"
|
||||||
disabled={loading}
|
className="h-8 w-8"
|
||||||
title="Refresh chat"
|
onClick={() => refresh()}
|
||||||
>
|
disabled={loading}
|
||||||
<RefreshCw className={cn('h-4 w-4', loading && 'animate-spin')} />
|
>
|
||||||
</Button>
|
<RefreshCw className={cn('h-4 w-4', loading && 'animate-spin')} />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t('toolbar.refresh')}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
{/* Thinking Toggle */}
|
{/* Thinking Toggle */}
|
||||||
<Button
|
<Tooltip>
|
||||||
variant="ghost"
|
<TooltipTrigger asChild>
|
||||||
size="icon"
|
<Button
|
||||||
className={cn(
|
variant="ghost"
|
||||||
'h-8 w-8',
|
size="icon"
|
||||||
showThinking && 'bg-primary/10 text-primary',
|
className={cn(
|
||||||
)}
|
'h-8 w-8',
|
||||||
onClick={toggleThinking}
|
showThinking && 'bg-primary/10 text-primary',
|
||||||
title={showThinking ? 'Hide thinking' : 'Show thinking'}
|
)}
|
||||||
>
|
onClick={toggleThinking}
|
||||||
<Brain className="h-4 w-4" />
|
>
|
||||||
</Button>
|
<Brain className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{showThinking ? t('toolbar.hideThinking') : t('toolbar.showThinking')}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ const defaultSkills: DefaultSkill[] = [
|
|||||||
{ id: 'terminal', name: 'Terminal', description: 'Shell command execution' },
|
{ id: 'terminal', name: 'Terminal', description: 'Shell command execution' },
|
||||||
];
|
];
|
||||||
|
|
||||||
import { SETUP_PROVIDERS, type ProviderTypeInfo } from '@/lib/providers';
|
import { SETUP_PROVIDERS, type ProviderTypeInfo, getProviderIconUrl, shouldInvertInDark } from '@/lib/providers';
|
||||||
|
|
||||||
// Use the shared provider registry for setup providers
|
// Use the shared provider registry for setup providers
|
||||||
const providers = SETUP_PROVIDERS;
|
const providers = SETUP_PROVIDERS;
|
||||||
@@ -1454,7 +1454,7 @@ function CompleteContent({ selectedProvider, installedSkills }: CompleteContentP
|
|||||||
<div className="flex items-center justify-between p-3 rounded-lg bg-muted/50">
|
<div className="flex items-center justify-between p-3 rounded-lg bg-muted/50">
|
||||||
<span>{t('complete.provider')}</span>
|
<span>{t('complete.provider')}</span>
|
||||||
<span className="text-green-400">
|
<span className="text-green-400">
|
||||||
{providerData ? `${providerData.icon} ${providerData.name}` : '—'}
|
{providerData ? <span className="flex items-center gap-1.5">{getProviderIconUrl(providerData.id) ? <img src={getProviderIconUrl(providerData.id)} alt={providerData.name} className={`h-4 w-4 inline-block ${shouldInvertInDark(providerData.id) ? 'dark:invert' : ''}`} /> : providerData.icon} {providerData.name}</span> : '—'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between p-3 rounded-lg bg-muted/50">
|
<div className="flex items-center justify-between p-3 rounded-lg bg-muted/50">
|
||||||
|
|||||||
@@ -615,6 +615,8 @@ export function Skills() {
|
|||||||
}
|
}
|
||||||
}, [enableSkill, disableSkill, t]);
|
}, [enableSkill, disableSkill, t]);
|
||||||
|
|
||||||
|
const hasInstalledSkills = skills.some(s => !s.isBundled);
|
||||||
|
|
||||||
const handleOpenSkillsFolder = useCallback(async () => {
|
const handleOpenSkillsFolder = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const skillsDir = await window.electron.ipcRenderer.invoke('openclaw:getSkillsDir') as string;
|
const skillsDir = await window.electron.ipcRenderer.invoke('openclaw:getSkillsDir') as string;
|
||||||
@@ -623,7 +625,12 @@ export function Skills() {
|
|||||||
}
|
}
|
||||||
const result = await window.electron.ipcRenderer.invoke('shell:openPath', skillsDir) as string;
|
const result = await window.electron.ipcRenderer.invoke('shell:openPath', skillsDir) as string;
|
||||||
if (result) {
|
if (result) {
|
||||||
throw new Error(result);
|
// shell.openPath returns an error string if the path doesn't exist
|
||||||
|
if (result.toLowerCase().includes('no such file') || result.toLowerCase().includes('not found') || result.toLowerCase().includes('failed to open')) {
|
||||||
|
toast.error(t('toast.failedFolderNotFound'));
|
||||||
|
} else {
|
||||||
|
throw new Error(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(t('toast.failedOpenFolder') + ': ' + String(err));
|
toast.error(t('toast.failedOpenFolder') + ': ' + String(err));
|
||||||
@@ -702,10 +709,12 @@ export function Skills() {
|
|||||||
<RefreshCw className="h-4 w-4 mr-2" />
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
{t('refresh')}
|
{t('refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" onClick={handleOpenSkillsFolder}>
|
{hasInstalledSkills && (
|
||||||
<FolderOpen className="h-4 w-4 mr-2" />
|
<Button variant="outline" onClick={handleOpenSkillsFolder}>
|
||||||
{t('openFolder')}
|
<FolderOpen className="h-4 w-4 mr-2" />
|
||||||
</Button>
|
{t('openFolder')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||