onChange(newValue)}
/>
)
diff --git a/web-app/src/mock/data.ts b/web-app/src/mock/data.ts
index ff956a633..f09cb9ce3 100644
--- a/web-app/src/mock/data.ts
+++ b/web-app/src/mock/data.ts
@@ -433,116 +433,3 @@ export const mockModelProvider = [
// ],
// },
]
-
-export const mockTheads = [
- {
- id: '1',
- title: 'Ultimate Markdown Demonstration',
- isFavorite: false,
- content: [
- {
- role: 'user',
- type: 'text',
- text: {
- value: 'Dow u know Ultimate Markdown Demonstration',
- annotations: [],
- },
- },
- {
- type: 'text',
- role: 'system',
- text: {
- value:
- '# :books: Ultimate Markdown Demonstration\n\nWelcome to the **Ultimate Markdown Demo**! This document covers a wide range of Markdown features.\n\n---\n\n## 1. Headings\n\n# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6\n\n---\n\n## 2. Text Formatting\n\n- **Bold**\n- *Italic*\n- ***Bold & Italic***\n- ~~Strikethrough~~\n\n> "Markdown is _awesome_!" — *Someone Famous*\n\n---\n\n## 3. Lists\n\n### 3.1. Unordered List\n\n- Item One\n - Subitem A\n - Subitem B\n - Sub-Subitem i\n\n### 3.2. Ordered List\n\n1. First\n2. Second\n 1. Second-First\n 2. Second-Second\n3. Third\n\n---\n\n## 4. Links and Images\n\n- [Visit OpenAI](https://openai.com)\n- Inline Image:\n\n \n\n- Linked Image:\n\n [](https://commonmark.org)\n\n---\n\n## 5. Code\n\n### 5.1. Inline Code\n\nUse the `print()` function in Python.\n\n### 5.2. Code Block\n\n```python\ndef greet(name):\n return f"Hello, {name}!"\n\nprint(greet("Markdown"))\n```\n\n### 5.3. Syntax Highlighting (JavaScript)\n\n```javascript\nconst add = (a, b) => a + b;\nconsole.log(add(5, 3));\n```\n\n---\n\n## 6. Tables\n\n| Syntax | Description | Example |\n|--------|-------------|--------|\n| Header | Title | Here\'s this |\n| Paragraph | Text | And more text |\n\n---\n\n## 7. Blockquotes\n\n> "A blockquote can be used to highlight information or quotes."\n\nNested Blockquote:\n\nLevel 1\n>Level 2\nLevel 3\n\n---\n\n## 8. Task Lists\n\n- [x] Write Markdown\n- [x] Check the output\n- [ ] Celebrate\n\n---\n\n## 9. Footnotes\n\nHere is a simple footnote[^1].\n\n[^1]: This is the footnote explanation.\n\n---\n\n## 10. Horizontal Rules\n\n---\n\n## 11. Emojis\n\n:tada: :sunglasses: :potable_water: :books:\n\n---\n\n## 12. Math (Using LaTeX)\n\nInline math: \\( E = mc^2 \\)\n\nBlock math:\n\n$$\n\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}\n$$\n\n---\n\n## 13. HTML in Markdown\n\nSometimes you need raw HTML:\n\n
This is blue bold text using HTML inside Markdown!
\n\n---\n\n# :dart: That\'s a Wrap!\n\nCongratulations, you\'ve seen nearly every feature Markdown supports!',
- annotations: [],
- },
- },
- ],
- model: {
- id: 'gpt-4o',
- provider: 'openai',
- },
- },
- {
- id: '2',
- title: 'Modern JavaScript: A Comprehensive Guide',
- isFavorite: false,
- content: [
- {
- role: 'user',
- type: 'text',
- text: {
- value: 'Explain modern JavaScript',
- annotations: [],
- },
- },
- {
- type: 'text',
- role: 'system',
- text: {
- value:
- "# Modern JavaScript: A Comprehensive Guide\n\nThis guide covers essential concepts and features of modern JavaScript that every developer should know.\n\n## ES6+ Features\n\n### Arrow Functions\n\nArrow functions provide a concise syntax for writing functions and lexically bind the `this` value.\n\n```javascript\n// Traditional function\nfunction add(a, b) {\n return a + b;\n}\n\n// Arrow function\nconst add = (a, b) => a + b;\n\n// With implicit return\nconst numbers = [1, 2, 3, 4];\nconst doubled = numbers.map(n => n * 2); // [2, 4, 6, 8]\n```\n\n### Destructuring\n\nDestructuring allows you to extract values from arrays or properties from objects into distinct variables.\n\n```javascript\n// Array destructuring\nconst [first, second, ...rest] = [1, 2, 3, 4, 5];\nconsole.log(first); // 1\nconsole.log(second); // 2\nconsole.log(rest); // [3, 4, 5]\n\n// Object destructuring\nconst person = { name: 'John', age: 30, city: 'New York' };\nconst { name, age, city: location } = person;\nconsole.log(name); // 'John'\nconsole.log(age); // 30\nconsole.log(location); // 'New York'\n```\n\n### Spread and Rest Operators\n\nThe spread operator (`...`) allows an iterable to be expanded in places where zero or more arguments or elements are expected.\n\n```javascript\n// Spread with arrays\nconst arr1 = [1, 2, 3];\nconst arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]\n\n// Spread with objects\nconst obj1 = { a: 1, b: 2 };\nconst obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }\n\n// Rest parameter\nfunction sum(...numbers) {\n return numbers.reduce((total, num) => total + num, 0);\n}\nconsole.log(sum(1, 2, 3, 4)); // 10\n```\n\n## Asynchronous JavaScript\n\n### Promises\n\nPromises represent the eventual completion (or failure) of an asynchronous operation and its resulting value.\n\n```javascript\nconst fetchData = () => {\n return new Promise((resolve, reject) => {\n // Simulating an API call\n setTimeout(() => {\n const data = { id: 1, name: 'User' };\n if (data) {\n resolve(data);\n } else {\n reject('Error fetching data');\n }\n }, 1000);\n });\n};\n\nfetchData()\n .then(data => console.log(data))\n .catch(error => console.error(error));\n```\n\n### Async/Await\n\nAsync/await is syntactic sugar built on top of promises, making asynchronous code look and behave more like synchronous code.\n\n```javascript\nconst fetchUser = async (id) => {\n try {\n const response = await fetch(`https://api.example.com/users/${id}`);\n if (!response.ok) throw new Error('Network response was not ok');\n const user = await response.json();\n return user;\n } catch (error) {\n console.error('Error fetching user:', error);\n throw error;\n }\n};\n\n// Using the async function\n(async () => {\n try {\n const user = await fetchUser(1);\n console.log(user);\n } catch (error) {\n console.error(error);\n }\n})();\n```\n\n## Modern JavaScript Patterns\n\n### Module Pattern\n\nES modules provide a way to organize and structure code in separate files.\n\n```javascript\n// math.js\nexport const add = (a, b) => a + b;\nexport const subtract = (a, b) => a - b;\n\n// main.js\nimport { add, subtract } from './math.js';\nconsole.log(add(5, 3)); // 8\n```\n\n### Optional Chaining\n\nOptional chaining (`?.`) allows reading the value of a property located deep within a chain of connected objects without having to check if each reference in the chain is valid.\n\n```javascript\nconst user = {\n name: 'John',\n address: {\n street: '123 Main St',\n city: 'New York'\n }\n};\n\n// Without optional chaining\nconst city = user && user.address && user.address.city;\n\n// With optional chaining\nconst city = user?.address?.city;\n```\n\n## Conclusion\n\nModern JavaScript has evolved significantly with ES6+ features, making code more concise, readable, and maintainable. Understanding these concepts is essential for any JavaScript developer working on modern web applications.",
- annotations: [],
- },
- },
- ],
- model: {
- id: 'llama3.2:3b',
- provider: 'llama.cpp',
- },
- },
- {
- id: '3',
- title: 'Reasoning and Tools',
- isFavorite: false,
- content: [
- {
- completed_at: 1746419535.019,
- role: 'user',
- text: {
- annotations: [],
- value: 'Ask question from user',
- },
- type: 'text',
- created_at: 1746419535.019,
- id: '01JTFBEK5BBZ9Y63275WDKRF6D',
- metadata: {},
- },
- {
- completed_at: 1746419535.019,
- role: 'assistant',
- text: {
- annotations: [],
- value: "I'll read the README.md file using the `read_file` function.",
- },
- type: 'text',
- created_at: 1746419535.019,
- id: '01JTFBEK5BBZ9Y63275WDKRF6D',
- metadata: {
- token_speed: 3.5555555555555554,
- tool_calls: [
- {
- response: {
- content: [{ text: '# Jan - Local AI Assistant', type: 'text' }],
- },
- state: 'ready',
- tool: {
- function: {
- arguments:
- '{"path": "/Users/louis/Repositories/jan/README.md"}',
- name: 'read_file',
- },
- id: '01JTFBEN8ZXNM9KB2CM9AY9ZBM',
- type: 'function',
- },
- },
- ],
- },
- },
- ],
- model: {
- id: 'llama3.2:3b',
- provider: 'llama.cpp',
- },
- },
-]
diff --git a/web-app/src/routes/index.tsx b/web-app/src/routes/index.tsx
index 332a45b18..afe2f7c84 100644
--- a/web-app/src/routes/index.tsx
+++ b/web-app/src/routes/index.tsx
@@ -4,6 +4,8 @@ import ChatInput from '@/containers/ChatInput'
import HeaderPage from '@/containers/HeaderPage'
import { useTranslation } from 'react-i18next'
import DropdownModelProvider from '@/containers/DropdownModelProvider'
+import { useModelProvider } from '@/hooks/useModelProvider'
+import SetupScreen from '@/containers/SetupScreen'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const Route = createFileRoute(route.home as any)({
@@ -12,6 +14,19 @@ export const Route = createFileRoute(route.home as any)({
function Index() {
const { t } = useTranslation()
+ const { providers } = useModelProvider()
+
+ // Conditional to check if there are any valid providers
+ // required min 1 api_key or 1 model in llama.cpp
+ const hasValidProviders = providers.some(
+ (provider) =>
+ provider.api_key?.length ||
+ (provider.provider === 'llama.cpp' && provider.models.length)
+ )
+
+ if (!hasValidProviders) {
+ return
+ }
return (
@@ -28,7 +43,6 @@ function Index() {
{t('chat.description', { ns: 'chat' })}
-
diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx
index 30178bd6b..6e3062e21 100644
--- a/web-app/src/routes/settings/providers/$providerName.tsx
+++ b/web-app/src/routes/settings/providers/$providerName.tsx
@@ -1,186 +1,267 @@
import { Card, CardItem } from '@/containers/Card'
import HeaderPage from '@/containers/HeaderPage'
import ProvidersMenu from '@/containers/ProvidersMenu'
-
import { useModelProvider } from '@/hooks/useModelProvider'
-import { getProviderTitle } from '@/lib/utils'
+import { cn, getProviderTitle } from '@/lib/utils'
import { Switch } from '@/components/ui/switch'
-import { createFileRoute, useParams } from '@tanstack/react-router'
+import {
+ createFileRoute,
+ useNavigate,
+ useParams,
+ useSearch,
+} from '@tanstack/react-router'
import { t } from 'i18next'
import Capabilities from '@/containers/Capabilities'
import { DynamicControllerSetting } from '@/containers/dynamicControllerSetting'
import { RenderMarkdown } from '@/containers/RenderMarkdown'
import { DialogEditModel } from '@/containers/dialogs/EditModel'
import { DialogAddModel } from '@/containers/dialogs/AddModel'
-
import { ModelSetting } from '@/containers/ModelSetting'
import { DialoDeleteModel } from '@/containers/dialogs/DeleteModel'
+import Joyride, { CallBackProps, STATUS } from 'react-joyride'
+import { CustomTooltipJoyRide } from '@/containers/CustomeTooltipJoyRide'
+import { route } from '@/constants/routes'
// as route.threadsDetail
export const Route = createFileRoute('/settings/providers/$providerName')({
component: ProviderDetail,
+ validateSearch: (search: Record
): { step?: string } => {
+ // validate and parse the search params into a typed state
+ return {
+ step: String(search?.step),
+ }
+ },
})
+const steps = [
+ {
+ target: '.first-step-setup-remote-provider',
+ title: 'Choose a Provider',
+ disableBeacon: true,
+ content:
+ 'Pick the provider you want to use, make sure you have access to an API key for it.',
+ },
+ {
+ target: '.second-step-setup-remote-provider',
+ title: 'Get Your API Key',
+ disableBeacon: true,
+ content:
+ 'Log into the provider’s dashboard to find or generate your API key.',
+ },
+ {
+ target: '.third-step-setup-remote-provider',
+ title: 'Insert Your API Key',
+ disableBeacon: true,
+ content: 'Paste your API key here to connect and activate the provider.',
+ },
+]
+
function ProviderDetail() {
+ const { step } = useSearch({ from: Route.id })
const { providerName } = useParams({ from: Route.id })
const { getProviderByName, updateProvider } = useModelProvider()
const provider = getProviderByName(providerName)
+ const isSetup = step === 'setup_remote_provider'
+ const navigate = useNavigate()
+
+ const handleJoyrideCallback = (data: CallBackProps) => {
+ const { status } = data
+
+ if (status === STATUS.FINISHED) {
+ navigate({
+ to: route.home,
+ })
+ }
+ }
return (
-
-
- {t('common.settings')}
-
-
-
-
-
-
-
- {getProviderTitle(providerName)}
-
- {
- if (provider) {
- updateProvider(providerName, { ...provider, active: e })
- }
- }}
- />
-
-
- {/* Settings */}
-
- {provider?.settings.map((setting, settingIndex) => {
- // Use the DynamicController component
- const actionComponent = (
-
- {
- if (provider) {
- const newSettings = [...provider.settings]
- // Handle different value types by forcing the type
- // Use type assertion to bypass type checking
- // Disable eslint for this line as we need to use type assertion
-
- ;(
- newSettings[settingIndex].controller_props as {
- value: string | boolean | number
- }
- ).value = newValue
-
- // Create update object with updated settings
- const updateObj: Partial = {
- settings: newSettings,
- }
- // Check if this is an API key or base URL setting and update the corresponding top-level field
- const settingKey = setting.key
- if (
- settingKey === 'api-key' &&
- typeof newValue === 'string'
- ) {
- updateObj.api_key = newValue
- } else if (
- settingKey === 'base-url' &&
- typeof newValue === 'string'
- ) {
- updateObj.base_url = newValue
- }
- updateProvider(providerName, {
- ...provider,
- ...updateObj,
- })
- }
- }}
- />
-
- )
-
- return (
-
+
+
+
+ {t('common.settings')}
+
+
+
+
+
+
+
+ {getProviderTitle(providerName)}
+
+
{
+ if (provider) {
+ updateProvider(providerName, { ...provider, active: e })
}
- description={
- (
-
- ),
- p: ({ ...props }) => (
-
- ),
+ }}
+ />
+
+
+ {/* Settings */}
+
+ {provider?.settings.map((setting, settingIndex) => {
+ // Use the DynamicController component
+ const actionComponent = (
+
+ {
+ if (provider) {
+ const newSettings = [...provider.settings]
+ // Handle different value types by forcing the type
+ // Use type assertion to bypass type checking
+
+ ;(
+ newSettings[settingIndex].controller_props as {
+ value: string | boolean | number
+ }
+ ).value = newValue
+
+ // Create update object with updated settings
+ const updateObj: Partial = {
+ settings: newSettings,
+ }
+ // Check if this is an API key or base URL setting and update the corresponding top-level field
+ const settingKey = setting.key
+ if (
+ settingKey === 'api-key' &&
+ typeof newValue === 'string'
+ ) {
+ updateObj.api_key = newValue
+ } else if (
+ settingKey === 'base-url' &&
+ typeof newValue === 'string'
+ ) {
+ updateObj.base_url = newValue
+ }
+ updateProvider(providerName, {
+ ...provider,
+ ...updateObj,
+ })
+ }
}}
/>
- }
- actions={actionComponent}
- />
- )
- })}
-
+
+ )
- {/* Models */}
-
-
- Models
-
-
- {provider &&
}
+ return (
+
{
+ return (
+
+ )
+ },
+ p: ({ ...props }) => (
+
+ ),
+ }}
+ />
+ }
+ actions={actionComponent}
+ />
+ )
+ })}
+
+
+ {/* Models */}
+
+
+ Models
+
+
+ {provider && }
+
-
- }
- >
- {provider?.models.map((model, modelIndex) => {
- const capabilities = model.capabilities || []
- return (
-
- {model.id}
-
-
- }
- actions={
-
-
- {model.settings && (
-
- )}
-
-
- }
- />
- )
- })}
-
+ }
+ >
+ {provider?.models.map((model, modelIndex) => {
+ const capabilities = model.capabilities || []
+ return (
+
+ {model.id}
+
+
+ }
+ actions={
+
+
+ {model.settings && (
+
+ )}
+
+
+ }
+ />
+ )
+ })}
+
+
-
+ >
)
}