Merge branch 'docs-pena-team' of github.com:janhq/jan into docs-pena-team
This commit is contained in:
commit
435b947839
4
.gitignore
vendored
4
.gitignore
vendored
@ -31,3 +31,7 @@ extensions/inference-nitro-extension/bin/saved-*
|
|||||||
extensions/inference-nitro-extension/bin/*.tar.gz
|
extensions/inference-nitro-extension/bin/*.tar.gz
|
||||||
extensions/inference-nitro-extension/bin/vulkaninfoSDK.exe
|
extensions/inference-nitro-extension/bin/vulkaninfoSDK.exe
|
||||||
extensions/inference-nitro-extension/bin/vulkaninfo
|
extensions/inference-nitro-extension/bin/vulkaninfo
|
||||||
|
|
||||||
|
|
||||||
|
# Turborepo
|
||||||
|
.turbo
|
||||||
@ -1,11 +1,11 @@
|
|||||||
|
---
|
||||||
openapi: 3.0.0
|
openapi: 3.0.0
|
||||||
info:
|
info:
|
||||||
title: API Reference
|
title: API Reference
|
||||||
description: >
|
description: >
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
Jan API is compatible with the [OpenAI
|
Jan API is compatible with the [OpenAI API](https://platform.openai.com/docs/api-reference).
|
||||||
API](https://platform.openai.com/docs/api-reference).
|
|
||||||
version: 0.1.8
|
version: 0.1.8
|
||||||
contact:
|
contact:
|
||||||
name: Jan Discord
|
name: Jan Discord
|
||||||
@ -20,12 +20,12 @@ tags:
|
|||||||
description: List and describe the various models available in the API.
|
description: List and describe the various models available in the API.
|
||||||
- name: Chat
|
- name: Chat
|
||||||
description: >
|
description: >
|
||||||
Given a list of messages comprising a conversation, the model will return
|
Given a list of messages comprising a conversation, the model will
|
||||||
a response.
|
return a response.
|
||||||
- name: Messages
|
- name: Messages
|
||||||
description: >
|
description: >
|
||||||
Messages capture a conversation's content. This can include the content
|
Messages capture a conversation's content. This can include the
|
||||||
from LLM responses and other metadata from [chat
|
content from LLM responses and other metadata from [chat
|
||||||
completions](/specs/chats).
|
completions](/specs/chats).
|
||||||
- name: Threads
|
- name: Threads
|
||||||
- name: Assistants
|
- name: Assistants
|
||||||
@ -49,16 +49,16 @@ paths:
|
|||||||
summary: |
|
summary: |
|
||||||
Create chat completion
|
Create chat completion
|
||||||
description: >
|
description: >
|
||||||
Creates a model response for the given chat conversation. <a href =
|
Creates a model response for the given chat conversation. <a href
|
||||||
"https://platform.openai.com/docs/api-reference/chat/create"> Equivalent
|
= "https://platform.openai.com/docs/api-reference/chat/create">
|
||||||
to OpenAI's create chat completion. </a>
|
Equivalent to OpenAI's create chat completion. </a>
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: specs/chat.yaml#/components/schemas/ChatCompletionRequest
|
$ref: specs/chat.yaml#/components/schemas/ChatCompletionRequest
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -192,9 +192,7 @@ paths:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
response =
|
response = requests.post('http://localhost:1337/v1/chat/completions', json=data)
|
||||||
requests.post('http://localhost:1337/v1/chat/completions',
|
|
||||||
json=data)
|
|
||||||
|
|
||||||
print(response.json())
|
print(response.json())
|
||||||
/models:
|
/models:
|
||||||
@ -204,12 +202,12 @@ paths:
|
|||||||
- Models
|
- Models
|
||||||
summary: List models
|
summary: List models
|
||||||
description: >
|
description: >
|
||||||
Lists the currently available models, and provides basic information
|
Lists the currently available models, and provides basic
|
||||||
about each one such as the owner and availability. <a href =
|
information about each one such as the owner and availability. <a href
|
||||||
"https://platform.openai.com/docs/api-reference/models/list"> Equivalent
|
= "https://platform.openai.com/docs/api-reference/models/list">
|
||||||
to OpenAI's list model. </a>
|
Equivalent to OpenAI's list model. </a>
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -228,14 +226,6 @@ paths:
|
|||||||
headers: {Accept: 'application/json'}
|
headers: {Accept: 'application/json'}
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
- lang: Python
|
|
||||||
source: |-
|
|
||||||
import requests
|
|
||||||
|
|
||||||
url = 'http://localhost:1337/v1/models'
|
|
||||||
headers = {'Accept': 'application/json'}
|
|
||||||
response = requests.get(url, headers=headers)
|
|
||||||
data = response.json()
|
|
||||||
- lang: Node.js
|
- lang: Node.js
|
||||||
source: |-
|
source: |-
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
@ -249,7 +239,15 @@ paths:
|
|||||||
fetch(url, options)
|
fetch(url, options)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(json => console.log(json));
|
.then(json => console.log(json));
|
||||||
/models/download/{model_id}:
|
- lang: Python
|
||||||
|
source: |-
|
||||||
|
import requests
|
||||||
|
|
||||||
|
url = 'http://localhost:1337/v1/models'
|
||||||
|
headers = {'Accept': 'application/json'}
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
data = response.json()
|
||||||
|
"/models/download/{model_id}":
|
||||||
get:
|
get:
|
||||||
operationId: downloadModel
|
operationId: downloadModel
|
||||||
tags:
|
tags:
|
||||||
@ -267,7 +265,7 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
The ID of the model to use for this request.
|
The ID of the model to use for this request.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -304,20 +302,18 @@ paths:
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
response =
|
response = requests.get('http://localhost:1337/v1/models/download/{model_id}', headers={'accept': 'application/json'})
|
||||||
requests.get('http://localhost:1337/v1/models/download/{model_id}',
|
|
||||||
headers={'accept': 'application/json'})
|
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
/models/{model_id}:
|
"/models/{model_id}":
|
||||||
get:
|
get:
|
||||||
operationId: retrieveModel
|
operationId: retrieveModel
|
||||||
tags:
|
tags:
|
||||||
- Models
|
- Models
|
||||||
summary: Retrieve model
|
summary: Retrieve model
|
||||||
description: >
|
description: >
|
||||||
Get a model instance, providing basic information about the model such
|
Get a model instance, providing basic information about the model
|
||||||
as the owner and permissioning. <a href =
|
such as the owner and permissioning. <a href =
|
||||||
"https://platform.openai.com/docs/api-reference/models/retrieve">
|
"https://platform.openai.com/docs/api-reference/models/retrieve">
|
||||||
Equivalent to OpenAI's retrieve model. </a>
|
Equivalent to OpenAI's retrieve model. </a>
|
||||||
parameters:
|
parameters:
|
||||||
@ -330,7 +326,7 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
The ID of the model to use for this request.
|
The ID of the model to use for this request.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -374,9 +370,7 @@ paths:
|
|||||||
model_id = 'mistral-ins-7b-q4'
|
model_id = 'mistral-ins-7b-q4'
|
||||||
|
|
||||||
|
|
||||||
response =
|
response = requests.get(f'http://localhost:1337/v1/models/{model_id}', headers={'accept': 'application/json'})
|
||||||
requests.get(f'http://localhost:1337/v1/models/{model_id}',
|
|
||||||
headers={'accept': 'application/json'})
|
|
||||||
|
|
||||||
print(response.json())
|
print(response.json())
|
||||||
delete:
|
delete:
|
||||||
@ -398,7 +392,7 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
The model id to delete
|
The model id to delete
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -442,9 +436,7 @@ paths:
|
|||||||
model_id = 'mistral-ins-7b-q4'
|
model_id = 'mistral-ins-7b-q4'
|
||||||
|
|
||||||
|
|
||||||
response =
|
response = requests.delete(f'http://localhost:1337/v1/models/{model_id}', headers={'accept': 'application/json'})
|
||||||
requests.delete(f'http://localhost:1337/v1/models/{model_id}',
|
|
||||||
headers={'accept': 'application/json'})
|
|
||||||
/threads:
|
/threads:
|
||||||
post:
|
post:
|
||||||
operationId: createThread
|
operationId: createThread
|
||||||
@ -462,7 +454,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: specs/threads.yaml#/components/schemas/CreateThreadObject
|
$ref: specs/threads.yaml#/components/schemas/CreateThreadObject
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Thread created successfully
|
description: Thread created successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -483,6 +475,73 @@ paths:
|
|||||||
"content": "How does AI work? Explain it in simple terms."
|
"content": "How does AI work? Explain it in simple terms."
|
||||||
}]
|
}]
|
||||||
}'
|
}'
|
||||||
|
- lang: JavaScript
|
||||||
|
source: |-
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
fetch('http://localhost:1337/v1/threads', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello, what is AI?',
|
||||||
|
file_ids: ['file-abc123']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'How does AI work? Explain it in simple terms.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
- lang: Node.js
|
||||||
|
source: |-
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
fetch('http://localhost:1337/v1/threads', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Hello, what is AI?',
|
||||||
|
file_ids: ['file-abc123']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'How does AI work? Explain it in simple terms.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
- lang: Python
|
||||||
|
source: |-
|
||||||
|
import requests
|
||||||
|
|
||||||
|
url = 'http://localhost:1337/v1/threads'
|
||||||
|
payload = {
|
||||||
|
'messages': [
|
||||||
|
{
|
||||||
|
'role': 'user',
|
||||||
|
'content': 'Hello, what is AI?',
|
||||||
|
'file_ids': ['file-abc123']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'role': 'user',
|
||||||
|
'content': 'How does AI work? Explain it in simple terms.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url, json=payload)
|
||||||
|
print(response.text)
|
||||||
get:
|
get:
|
||||||
operationId: listThreads
|
operationId: listThreads
|
||||||
tags:
|
tags:
|
||||||
@ -491,7 +550,7 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
Retrieves a list of all threads available in the system.
|
Retrieves a list of all threads available in the system.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: List of threads retrieved successfully
|
description: List of threads retrieved successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -516,10 +575,37 @@ paths:
|
|||||||
metadata: {}
|
metadata: {}
|
||||||
x-codeSamples:
|
x-codeSamples:
|
||||||
- lang: cURL
|
- lang: cURL
|
||||||
source: |
|
source: |-
|
||||||
curl http://localhost:1337/v1/threads \
|
curl http://localhost:1337/v1/threads \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json"
|
||||||
/threads/{thread_id}:
|
- lang: JavaScript
|
||||||
|
source: |-
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
fetch('http://localhost:1337/v1/threads', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
}).then(res => res.json())
|
||||||
|
.then(json => console.log(json));
|
||||||
|
- lang: Node.js
|
||||||
|
source: |-
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
fetch('http://localhost:1337/v1/threads', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
}).then(res => res.json())
|
||||||
|
.then(json => console.log(json));
|
||||||
|
- lang: Python
|
||||||
|
source: |-
|
||||||
|
import requests
|
||||||
|
|
||||||
|
url = 'http://localhost:1337/v1/threads'
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
print(response.json())
|
||||||
|
"/threads/{thread_id}":
|
||||||
get:
|
get:
|
||||||
operationId: getThread
|
operationId: getThread
|
||||||
tags:
|
tags:
|
||||||
@ -539,7 +625,7 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
The ID of the thread to retrieve.
|
The ID of the thread to retrieve.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Thread details retrieved successfully
|
description: Thread details retrieved successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -579,7 +665,7 @@ paths:
|
|||||||
items:
|
items:
|
||||||
$ref: specs/threads.yaml#/components/schemas/ThreadMessageObject
|
$ref: specs/threads.yaml#/components/schemas/ThreadMessageObject
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Thread modified successfully
|
description: Thread modified successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -618,7 +704,7 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
The ID of the thread to be deleted.
|
The ID of the thread to be deleted.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Thread deleted successfully
|
description: Thread deleted successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -639,7 +725,7 @@ paths:
|
|||||||
"https://platform.openai.com/docs/api-reference/assistants/listAssistants">
|
"https://platform.openai.com/docs/api-reference/assistants/listAssistants">
|
||||||
Equivalent to OpenAI's list assistants. </a>
|
Equivalent to OpenAI's list assistants. </a>
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: List of assistants retrieved successfully
|
description: List of assistants retrieved successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -676,10 +762,36 @@ paths:
|
|||||||
metadata: {}
|
metadata: {}
|
||||||
x-codeSamples:
|
x-codeSamples:
|
||||||
- lang: cURL
|
- lang: cURL
|
||||||
source: |
|
source: |-
|
||||||
curl http://localhost:1337/v1/assistants \
|
curl http://localhost:1337/v1/assistants \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json"
|
||||||
/assistants/{assistant_id}:
|
- lang: JavaScript
|
||||||
|
source: |-
|
||||||
|
fetch('http://localhost:1337/v1/assistants', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
- lang: Node.js
|
||||||
|
source: |-
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
fetch('http://localhost:1337/v1/assistants', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
- lang: Python
|
||||||
|
source: |-
|
||||||
|
import requests
|
||||||
|
|
||||||
|
url = 'http://localhost:1337/v1/assistants'
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
"/assistants/{assistant_id}":
|
||||||
get:
|
get:
|
||||||
operationId: getAssistant
|
operationId: getAssistant
|
||||||
tags:
|
tags:
|
||||||
@ -699,19 +811,51 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
The ID of the assistant to retrieve.
|
The ID of the assistant to retrieve.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: null
|
description: null
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: >-
|
$ref: specs/assistants.yaml#/components/schemas/RetrieveAssistantResponse
|
||||||
specs/assistants.yaml#/components/schemas/RetrieveAssistantResponse
|
|
||||||
x-codeSamples:
|
x-codeSamples:
|
||||||
- lang: cURL
|
- lang: cURL
|
||||||
source: |
|
source: |-
|
||||||
curl http://localhost:1337/v1/assistants/{assistant_id} \
|
curl http://localhost:1337/v1/assistants/{assistant_id} \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json"
|
||||||
/threads/{thread_id}/messages:
|
- lang: JavaScript
|
||||||
|
source: |-
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
let assistantId = 'abc123';
|
||||||
|
|
||||||
|
fetch(`http://localhost:1337/v1/assistants/${assistantId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
- lang: Node.js
|
||||||
|
source: |-
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
let assistantId = 'abc123';
|
||||||
|
|
||||||
|
fetch(`http://localhost:1337/v1/assistants/${assistantId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
- lang: Python
|
||||||
|
source: >-
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
assistant_id = 'abc123'
|
||||||
|
|
||||||
|
|
||||||
|
response = requests.get(f'http://localhost:1337/v1/assistants/{assistant_id}', headers={'Content-Type': 'application/json'})
|
||||||
|
"/threads/{thread_id}/messages":
|
||||||
get:
|
get:
|
||||||
operationId: listMessages
|
operationId: listMessages
|
||||||
tags:
|
tags:
|
||||||
@ -730,7 +874,7 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
The ID of the thread from which to retrieve messages.
|
The ID of the thread from which to retrieve messages.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: List of messages retrieved successfully
|
description: List of messages retrieved successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -782,7 +926,7 @@ paths:
|
|||||||
- role
|
- role
|
||||||
- content
|
- content
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: Message created successfully
|
description: Message created successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -797,7 +941,7 @@ paths:
|
|||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "How does AI work? Explain it in simple terms."
|
"content": "How does AI work? Explain it in simple terms."
|
||||||
}'
|
}'
|
||||||
/threads/{thread_id}/messages/{message_id}:
|
"/threads/{thread_id}/messages/{message_id}":
|
||||||
get:
|
get:
|
||||||
operationId: retrieveMessage
|
operationId: retrieveMessage
|
||||||
tags:
|
tags:
|
||||||
@ -824,7 +968,7 @@ paths:
|
|||||||
description: |
|
description: |
|
||||||
The ID of the message to retrieve.
|
The ID of the message to retrieve.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
@ -833,8 +977,8 @@ paths:
|
|||||||
x-codeSamples:
|
x-codeSamples:
|
||||||
- lang: cURL
|
- lang: cURL
|
||||||
source: >
|
source: >
|
||||||
curl
|
curl http://localhost:1337/v1/threads/{thread_id}/messages/{message_id}
|
||||||
http://localhost:1337/v1/threads/{thread_id}/messages/{message_id} \
|
\
|
||||||
-H "Content-Type: application/json"
|
-H "Content-Type: application/json"
|
||||||
x-webhooks:
|
x-webhooks:
|
||||||
ModelObject:
|
ModelObject:
|
||||||
@ -856,9 +1000,10 @@ x-webhooks:
|
|||||||
post:
|
post:
|
||||||
summary: The assistant object
|
summary: The assistant object
|
||||||
description: >
|
description: >
|
||||||
Build assistants that can call models and use tools to perform tasks.
|
Build assistants that can call models and use tools to perform
|
||||||
<a href = "https://platform.openai.com/docs/api-reference/assistants">
|
tasks. <a href =
|
||||||
Equivalent to OpenAI's assistants object. </a>
|
"https://platform.openai.com/docs/api-reference/assistants"> Equivalent
|
||||||
|
to OpenAI's assistants object. </a>
|
||||||
operationId: AssistantObjects
|
operationId: AssistantObjects
|
||||||
tags:
|
tags:
|
||||||
- Assistants
|
- Assistants
|
||||||
@ -885,8 +1030,7 @@ x-webhooks:
|
|||||||
ThreadObject:
|
ThreadObject:
|
||||||
post:
|
post:
|
||||||
summary: The thread object
|
summary: The thread object
|
||||||
description: >-
|
description: Represents a thread that contains messages. <a href =
|
||||||
Represents a thread that contains messages. <a href =
|
|
||||||
"https://platform.openai.com/docs/api-reference/threads/object">
|
"https://platform.openai.com/docs/api-reference/threads/object">
|
||||||
Equivalent to OpenAI's thread object. </a>
|
Equivalent to OpenAI's thread object. </a>
|
||||||
operationId: ThreadObject
|
operationId: ThreadObject
|
||||||
|
|||||||
@ -93,12 +93,12 @@ export function handleAppIPCs() {
|
|||||||
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||||
title: 'Select model files',
|
title: 'Select model files',
|
||||||
buttonLabel: 'Select',
|
buttonLabel: 'Select',
|
||||||
properties: ['openFile', 'multiSelections'],
|
properties: ['openFile', 'openDirectory', 'multiSelections'],
|
||||||
})
|
})
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
return filePaths
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return filePaths
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,6 +61,8 @@
|
|||||||
"test:e2e": "playwright test --workers=1",
|
"test:e2e": "playwright test --workers=1",
|
||||||
"copy:assets": "rimraf --glob \"./pre-install/*.tgz\" && cpx \"../pre-install/*.tgz\" \"./pre-install\"",
|
"copy:assets": "rimraf --glob \"./pre-install/*.tgz\" && cpx \"../pre-install/*.tgz\" \"./pre-install\"",
|
||||||
"dev": "yarn copy:assets && tsc -p . && electron .",
|
"dev": "yarn copy:assets && tsc -p . && electron .",
|
||||||
|
"compile": "tsc -p .",
|
||||||
|
"start": "electron .",
|
||||||
"build": "yarn copy:assets && run-script-os",
|
"build": "yarn copy:assets && run-script-os",
|
||||||
"build:test": "yarn copy:assets && run-script-os",
|
"build:test": "yarn copy:assets && run-script-os",
|
||||||
"build:test:darwin": "tsc -p . && electron-builder -p never -m --dir",
|
"build:test:darwin": "tsc -p . && electron-builder -p never -m --dir",
|
||||||
|
|||||||
@ -41,7 +41,8 @@
|
|||||||
"build:extensions": "run-script-os",
|
"build:extensions": "run-script-os",
|
||||||
"build:test": "yarn copy:assets && yarn build:web && yarn workspace jan build:test",
|
"build:test": "yarn copy:assets && yarn build:web && yarn workspace jan build:test",
|
||||||
"build": "yarn build:web && yarn build:electron",
|
"build": "yarn build:web && yarn build:electron",
|
||||||
"build:publish": "yarn copy:assets && yarn build:web && yarn workspace jan build:publish"
|
"build:publish": "yarn copy:assets && yarn build:web && yarn workspace jan build:publish",
|
||||||
|
"turbo:electron": "turbo run dev --parallel --filter=!@janhq/server"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^8.2.1",
|
"concurrently": "^8.2.1",
|
||||||
|
|||||||
29
turbo.json
Normal file
29
turbo.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
"outputs": [".next/**", "!.next/cache/**"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false
|
||||||
|
},
|
||||||
|
"web#build": {
|
||||||
|
"dependsOn": ["@janhq/core#build"]
|
||||||
|
},
|
||||||
|
"web:dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true,
|
||||||
|
"dependsOn": ["@janhq/core#build", "@janhq/uikit#build"]
|
||||||
|
},
|
||||||
|
"electron:dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true,
|
||||||
|
"dependsOn": ["@janhq/core#build", "@janhq/server#build", "jan#compile"]
|
||||||
|
},
|
||||||
|
"electron#build": {
|
||||||
|
"dependsOn": ["web#build", "server#build", "core#build"],
|
||||||
|
"cache": false
|
||||||
|
},
|
||||||
|
"type-check": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,11 +5,11 @@
|
|||||||
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
||||||
|
|
||||||
&-primary {
|
&-primary {
|
||||||
@apply bg-blue-600 text-white hover:bg-blue-600/90;
|
@apply bg-primary hover:bg-primary/90 text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-secondary-blue {
|
&-secondary-blue {
|
||||||
@apply bg-blue-200 text-blue-600 hover:bg-blue-300/50;
|
@apply bg-blue-200 text-blue-600 hover:bg-blue-300/50 dark:hover:bg-blue-200/80;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-danger {
|
&-danger {
|
||||||
@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-secondary-danger {
|
&-secondary-danger {
|
||||||
@apply bg-red-200 text-red-600 hover:bg-red-300/50;
|
@apply bg-red-200 text-red-600 hover:bg-red-300/50 dark:hover:bg-red-200/80;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-outline {
|
&-outline {
|
||||||
@ -66,7 +66,7 @@
|
|||||||
[type='reset'],
|
[type='reset'],
|
||||||
[type='submit'] {
|
[type='submit'] {
|
||||||
&.btn-primary {
|
&.btn-primary {
|
||||||
@apply bg-blue-600 hover:bg-blue-600/90;
|
@apply bg-primary hover:bg-primary/90;
|
||||||
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
||||||
}
|
}
|
||||||
&.btn-secondary {
|
&.btn-secondary {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
.checkbox {
|
.checkbox {
|
||||||
@apply border-border h-5 w-5 flex-shrink-0 rounded-md border data-[state=checked]:bg-blue-600 data-[state=checked]:text-white;
|
@apply border-border data-[state=checked]:bg-primary h-5 w-5 flex-shrink-0 rounded-md border data-[state=checked]:text-white;
|
||||||
|
|
||||||
&--icon {
|
&--icon {
|
||||||
@apply h-4 w-4;
|
@apply h-4 w-4;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.input {
|
.input {
|
||||||
@apply border-border placeholder:text-muted-foreground flex h-9 w-full rounded-lg border bg-transparent px-3 py-1 transition-colors;
|
@apply border-border placeholder:text-muted-foreground flex h-9 w-full rounded-lg border bg-transparent px-3 py-1 transition-colors;
|
||||||
@apply disabled:text-muted-foreground disabled:cursor-not-allowed disabled:bg-zinc-100;
|
@apply disabled:text-muted-foreground disabled:cursor-not-allowed disabled:bg-zinc-100 disabled:dark:bg-zinc-800 disabled:dark:text-zinc-600;
|
||||||
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
|
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
|
||||||
@apply file:border-0 file:bg-transparent file:font-medium;
|
@apply file:border-0 file:bg-transparent file:font-medium;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,10 +42,69 @@
|
|||||||
--danger: 346.8 77.2% 49.8%;
|
--danger: 346.8 77.2% 49.8%;
|
||||||
--danger-foreground: 355.7 100% 97.3%;
|
--danger-foreground: 355.7 100% 97.3%;
|
||||||
|
|
||||||
--secondary: 60 4.8% 95.9%;
|
|
||||||
--secondary-foreground: 24 9.8% 10%;
|
|
||||||
|
|
||||||
--border: 20 5.9% 90%;
|
--border: 20 5.9% 90%;
|
||||||
--input: 20 5.9% 90%;
|
--input: 20 5.9% 90%;
|
||||||
--ring: 20 14.3% 4.1%;
|
--ring: 20 14.3% 4.1%;
|
||||||
|
|
||||||
|
.primary-blue {
|
||||||
|
--primary: 221 83% 53%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--secondary: 60 4.8% 95.9%;
|
||||||
|
--secondary-foreground: 24 9.8% 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-green {
|
||||||
|
--primary: 142.1 76.2% 36.3%;
|
||||||
|
--primary-foreground: 355.7 100% 97.3%;
|
||||||
|
|
||||||
|
--secondary: 240 4.8% 95.9%;
|
||||||
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-purple {
|
||||||
|
--primary: 262.1 83.3% 57.8%;
|
||||||
|
--primary-foreground: 210 20% 98%;
|
||||||
|
|
||||||
|
--secondary: 220 14.3% 95.9%;
|
||||||
|
--secondary-foreground: 220.9 39.3% 11%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 20 14.3% 4.1%;
|
||||||
|
--foreground: 60 9.1% 97.8%;
|
||||||
|
|
||||||
|
--muted: 12 6.5% 15.1%;
|
||||||
|
--muted-foreground: 24 5.4% 63.9%;
|
||||||
|
|
||||||
|
--danger: 346.8 77.2% 49.8%;
|
||||||
|
--danger-foreground: 355.7 100% 97.3%;
|
||||||
|
|
||||||
|
--border: 12 6.5% 15.1%;
|
||||||
|
--input: 12 6.5% 15.1%;
|
||||||
|
--ring: 35.5 91.7% 32.9%;
|
||||||
|
|
||||||
|
.primary-blue {
|
||||||
|
--primary: 221 83% 53%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--secondary: 12 6.5% 15.1%;
|
||||||
|
--secondary-foreground: 60 9.1% 97.8%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-green {
|
||||||
|
--primary: 142.1 70.6% 45.3%;
|
||||||
|
--primary-foreground: 144.9 80.4% 10%;
|
||||||
|
--secondary: 240 3.7% 15.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-purple {
|
||||||
|
--primary: 263.4 70% 50.4%;
|
||||||
|
--primary-foreground: 210 20% 98%;
|
||||||
|
|
||||||
|
--secondary: 215 27.9% 16.9%;
|
||||||
|
--secondary-foreground: 210 20% 98%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
.progress {
|
.progress {
|
||||||
@apply relative h-4 w-full overflow-hidden rounded-full bg-gray-100;
|
@apply bg-secondary relative h-4 w-full overflow-hidden rounded-full;
|
||||||
|
|
||||||
&-indicator {
|
&-indicator {
|
||||||
@apply h-full w-full flex-1 bg-blue-600 transition-all;
|
@apply bg-primary h-full w-full flex-1 transition-all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.select {
|
.select {
|
||||||
@apply placeholder:text-muted-foreground border-border flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm disabled:cursor-not-allowed [&>span]:line-clamp-1;
|
@apply placeholder:text-muted-foreground border-border flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm disabled:cursor-not-allowed [&>span]:line-clamp-1;
|
||||||
@apply disabled:text-muted-foreground disabled:cursor-not-allowed disabled:bg-zinc-100;
|
@apply disabled:text-muted-foreground disabled:cursor-not-allowed disabled:bg-zinc-100 disabled:dark:bg-zinc-800 disabled:dark:text-zinc-600;
|
||||||
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
|
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
|
||||||
|
|
||||||
&-caret {
|
&-caret {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
@apply relative flex w-full touch-none select-none items-center;
|
@apply relative flex w-full touch-none select-none items-center;
|
||||||
|
|
||||||
&-track {
|
&-track {
|
||||||
@apply relative h-1.5 w-full grow overflow-hidden rounded-full bg-gray-200;
|
@apply relative h-1.5 w-full grow overflow-hidden rounded-full bg-gray-200 dark:bg-gray-800;
|
||||||
[data-disabled] {
|
[data-disabled] {
|
||||||
@apply cursor-not-allowed opacity-50;
|
@apply cursor-not-allowed opacity-50;
|
||||||
}
|
}
|
||||||
@ -13,6 +13,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-thumb {
|
&-thumb {
|
||||||
@apply bg-background focus-visible:ring-ring block h-4 w-4 rounded-full border border-blue-600/50 shadow transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50;
|
@apply border-primary/50 bg-background focus-visible:ring-ring block h-4 w-4 rounded-full border shadow transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
.switch {
|
.switch {
|
||||||
@apply inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent;
|
@apply inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent;
|
||||||
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
|
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
|
||||||
@apply data-[state=unchecked]:bg-input data-[state=checked]:bg-blue-600;
|
@apply data-[state=checked]:bg-primary data-[state=unchecked]:bg-input;
|
||||||
@apply disabled:cursor-not-allowed disabled:opacity-50;
|
@apply disabled:cursor-not-allowed disabled:opacity-50;
|
||||||
|
|
||||||
&-toggle {
|
&-toggle {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.tooltip {
|
.tooltip {
|
||||||
@apply z-50 overflow-hidden rounded-md bg-gray-950 px-2 py-1.5 text-xs font-medium text-gray-200 shadow-md;
|
@apply dark:bg-input dark:text-foreground z-50 overflow-hidden rounded-md bg-gray-950 px-2 py-1.5 text-xs font-medium text-gray-200 shadow-md;
|
||||||
&-arrow {
|
&-arrow {
|
||||||
@apply fill-gray-950;
|
@apply dark:fill-input fill-gray-950;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export const metadata: Metadata = {
|
|||||||
export default function RootLayout({ children }: PropsWithChildren) {
|
export default function RootLayout({ children }: PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className="bg-white font-sans text-sm antialiased">
|
<body className="bg-white font-sans text-sm antialiased dark:bg-background">
|
||||||
<div className="title-bar" />
|
<div className="title-bar" />
|
||||||
<Providers>{children}</Providers>
|
<Providers>{children}</Providers>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export default function CardSidebar({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'flex w-full flex-col border-t border-border bg-zinc-100',
|
'flex w-full flex-col border-t border-border bg-zinc-100 dark:bg-zinc-900',
|
||||||
asChild ? 'rounded-lg border' : 'border-t'
|
asChild ? 'rounded-lg border' : 'border-t'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -61,7 +61,7 @@ export default function CardSidebar({
|
|||||||
if (!children) return
|
if (!children) return
|
||||||
setShow(!show)
|
setShow(!show)
|
||||||
}}
|
}}
|
||||||
className="flex w-full flex-1 items-center space-x-2 rounded-lg bg-zinc-100 py-2 pr-2"
|
className="flex w-full flex-1 items-center space-x-2 rounded-lg bg-zinc-100 py-2 pr-2 dark:bg-zinc-900"
|
||||||
>
|
>
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
@ -79,7 +79,7 @@ export default function CardSidebar({
|
|||||||
{!hideMoreVerticalAction && (
|
{!hideMoreVerticalAction && (
|
||||||
<div
|
<div
|
||||||
ref={setToggle}
|
ref={setToggle}
|
||||||
className="cursor-pointer rounded-lg bg-zinc-100 p-2 px-3"
|
className="cursor-pointer rounded-lg bg-zinc-100 p-2 px-3 dark:bg-zinc-900"
|
||||||
onClick={() => setMore(!more)}
|
onClick={() => setMore(!more)}
|
||||||
>
|
>
|
||||||
<MoreVerticalIcon className="h-5 w-5" />
|
<MoreVerticalIcon className="h-5 w-5" />
|
||||||
@ -114,7 +114,7 @@ export default function CardSidebar({
|
|||||||
<>
|
<>
|
||||||
{title === 'Model' ? (
|
{title === 'Model' ? (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-medium text-black">
|
<span className="font-medium text-black dark:text-muted-foreground">
|
||||||
{openFileTitle()}
|
{openFileTitle()}
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 text-muted-foreground">
|
<span className="mt-1 text-muted-foreground">
|
||||||
@ -122,7 +122,7 @@ export default function CardSidebar({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-bold text-black">
|
<span className="text-bold text-black dark:text-muted-foreground">
|
||||||
{openFileTitle()}
|
{openFileTitle()}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -141,7 +141,7 @@ export default function CardSidebar({
|
|||||||
/>
|
/>
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="line-clamp-1 font-medium text-black">
|
<span className="line-clamp-1 font-medium text-black dark:text-muted-foreground">
|
||||||
Edit Global Defaults for{' '}
|
Edit Global Defaults for{' '}
|
||||||
<span
|
<span
|
||||||
className="font-bold"
|
className="font-bold"
|
||||||
@ -175,7 +175,7 @@ export default function CardSidebar({
|
|||||||
{show && (
|
{show && (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'flex flex-col gap-2 bg-white px-2',
|
'flex flex-col gap-2 bg-white px-2 dark:bg-background',
|
||||||
asChild && 'rounded-b-lg'
|
asChild && 'rounded-b-lg'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -34,10 +34,12 @@ const Checkbox: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="mb-1 flex items-center gap-x-2">
|
<div className="mb-1 flex items-center gap-x-2">
|
||||||
<p className="text-sm font-semibold text-zinc-500">{title}</p>
|
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<InfoIcon size={16} className="flex-shrink-0" />
|
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
<TooltipContent side="top" className="max-w-[240px]">
|
<TooltipContent side="top" className="max-w-[240px]">
|
||||||
|
|||||||
@ -203,14 +203,15 @@ const DropdownListSidebar = ({
|
|||||||
isTabActive === 1 && '[&_.select-scroll-down-button]:hidden'
|
isTabActive === 1 && '[&_.select-scroll-down-button]:hidden'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="relative px-2 py-2">
|
<div className="relative px-2 py-2 dark:bg-secondary/50">
|
||||||
<ul className="inline-flex w-full space-x-2 rounded-lg bg-zinc-100 px-1">
|
<ul className="inline-flex w-full space-x-2 rounded-lg bg-zinc-100 px-1 dark:bg-secondary">
|
||||||
{engineOptions.map((name, i) => {
|
{engineOptions.map((name, i) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'relative my-1 flex w-full cursor-pointer items-center justify-center space-x-2 px-2 py-2',
|
'relative my-1 flex w-full cursor-pointer items-center justify-center space-x-2 px-2 py-2',
|
||||||
isTabActive === i && 'rounded-md bg-background'
|
isTabActive === i &&
|
||||||
|
'rounded-md bg-background dark:bg-white'
|
||||||
)}
|
)}
|
||||||
key={i}
|
key={i}
|
||||||
onClick={() => setIsTabActive(i)}
|
onClick={() => setIsTabActive(i)}
|
||||||
@ -229,7 +230,8 @@ const DropdownListSidebar = ({
|
|||||||
<span
|
<span
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'relative z-50 font-medium text-muted-foreground',
|
'relative z-50 font-medium text-muted-foreground',
|
||||||
isTabActive === i && 'font-bold text-foreground'
|
isTabActive === i &&
|
||||||
|
'font-bold text-foreground dark:text-black'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ const GPUDriverPrompt: React.FC = () => {
|
|||||||
id="default-checkbox"
|
id="default-checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
onChange={onDoNotShowAgainChange}
|
onChange={onDoNotShowAgainChange}
|
||||||
className="h-4 w-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500"
|
className="h-4 w-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600"
|
||||||
/>
|
/>
|
||||||
<span>Don't show again</span>
|
<span>Don't show again</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
type Props = {
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
disabled?: boolean
|
|
||||||
onChange?: (text?: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ItemCardSidebar({
|
|
||||||
description,
|
|
||||||
title,
|
|
||||||
disabled,
|
|
||||||
onChange,
|
|
||||||
}: Props) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>{title}</span>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
value={description}
|
|
||||||
disabled={disabled}
|
|
||||||
type="text"
|
|
||||||
className="block w-full rounded-md border-0 px-1 py-1.5 text-white shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
|
||||||
placeholder=""
|
|
||||||
onChange={(e) => onChange?.(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -47,7 +47,7 @@ export default function DownloadingState() {
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
<span
|
<span
|
||||||
className="absolute left-0 h-full rounded-md rounded-l-md bg-blue-500/20"
|
className="absolute left-0 h-full rounded-md rounded-l-md bg-primary/20"
|
||||||
style={{
|
style={{
|
||||||
width: `${totalPercentage}%`,
|
width: `${totalPercentage}%`,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -38,17 +38,17 @@ const ImportingModelState: React.FC = () => {
|
|||||||
className="flex cursor-pointer flex-row items-center space-x-2"
|
className="flex cursor-pointer flex-row items-center space-x-2"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<p className="text-xs font-semibold text-[#09090B]">
|
<p className="text-xs font-semibold text-muted-foreground">
|
||||||
Importing model ({finishedImportModelCount}/{importingModels.length}
|
Importing model ({finishedImportModelCount}/{importingModels.length}
|
||||||
)
|
)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-row items-center justify-center space-x-2 rounded-md bg-[#F4F4F5] px-2 py-[2px]">
|
<div className="flex flex-row items-center justify-center space-x-2 rounded-md bg-secondary px-2 py-[2px]">
|
||||||
<Progress
|
<Progress
|
||||||
className="h-2 w-24"
|
className="h-2 w-24"
|
||||||
value={transferredSize / totalSize}
|
value={transferredSize / totalSize}
|
||||||
/>
|
/>
|
||||||
<span className="text-xs font-bold text-blue-600">
|
<span className="text-xs font-bold text-muted-foreground">
|
||||||
{progress.toFixed(2)}%
|
{progress.toFixed(2)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export default function RibbonNav() {
|
|||||||
size={20}
|
size={20}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'flex-shrink-0 text-muted-foreground',
|
'flex-shrink-0 text-muted-foreground',
|
||||||
serverEnabled && 'text-gray-300'
|
serverEnabled && 'text-gray-300 dark:text-gray-700'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@ -114,7 +114,7 @@ export default function RibbonNav() {
|
|||||||
</div>
|
</div>
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<m.div
|
<m.div
|
||||||
className="absolute inset-0 left-0 h-full w-full rounded-md bg-gray-200"
|
className="absolute inset-0 left-0 h-full w-full rounded-md bg-gray-200 dark:bg-secondary"
|
||||||
layoutId="active-state-primary"
|
layoutId="active-state-primary"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -166,7 +166,7 @@ export default function RibbonNav() {
|
|||||||
</div>
|
</div>
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<m.div
|
<m.div
|
||||||
className="absolute inset-0 left-0 h-full w-full rounded-md bg-gray-200"
|
className="absolute inset-0 left-0 h-full w-full rounded-md bg-gray-200 dark:bg-secondary"
|
||||||
layoutId="active-state-secondary"
|
layoutId="active-state-secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -159,7 +159,7 @@ const TopBar = () => {
|
|||||||
size={16}
|
size={16}
|
||||||
className="text-muted-foreground"
|
className="text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
<span className="font-medium text-black ">
|
<span className="font-medium text-black dark:text-muted-foreground">
|
||||||
{openFileTitle()}
|
{openFileTitle()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -175,7 +175,7 @@ const TopBar = () => {
|
|||||||
className="mt-0.5 flex-shrink-0 text-muted-foreground"
|
className="mt-0.5 flex-shrink-0 text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-medium text-black ">
|
<span className="font-medium text-black dark:text-muted-foreground">
|
||||||
Edit Threads Settings
|
Edit Threads Settings
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 text-muted-foreground">
|
<span className="mt-1 text-muted-foreground">
|
||||||
@ -204,7 +204,7 @@ const TopBar = () => {
|
|||||||
className="text-muted-foreground"
|
className="text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-medium text-black ">
|
<span className="font-medium text-black dark:text-muted-foreground">
|
||||||
{openFileTitle()}
|
{openFileTitle()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,12 +7,12 @@ export default function Loader({ description }: Props) {
|
|||||||
<div className="space-y-16">
|
<div className="space-y-16">
|
||||||
<div className="loader">
|
<div className="loader">
|
||||||
<div className="loader-inner">
|
<div className="loader-inner">
|
||||||
<label className="h-2 w-2 rounded-full bg-blue-500" />
|
<label className="h-2 w-2 rounded-full bg-primary" />
|
||||||
<label className="h-2 w-2 rounded-full bg-blue-500" />
|
<label className="h-2 w-2 rounded-full bg-primary" />
|
||||||
<label className="h-2 w-2 rounded-full bg-blue-500" />
|
<label className="h-2 w-2 rounded-full bg-primary" />
|
||||||
<label className="h-2 w-2 rounded-full bg-blue-500" />
|
<label className="h-2 w-2 rounded-full bg-primary" />
|
||||||
<label className="h-2 w-2 rounded-full bg-blue-500" />
|
<label className="h-2 w-2 rounded-full bg-primary" />
|
||||||
<label className="h-2 w-2 rounded-full bg-blue-500" />
|
<label className="h-2 w-2 rounded-full bg-primary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="font-medium text-muted-foreground">{description}</p>
|
<p className="font-medium text-muted-foreground">{description}</p>
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const AppLogs = () => {
|
|||||||
<div className="absolute -top-11 right-2">
|
<div className="absolute -top-11 right-2">
|
||||||
<Button
|
<Button
|
||||||
themes="outline"
|
themes="outline"
|
||||||
className="bg-white"
|
className="bg-white dark:bg-secondary/50"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.copy(logs.slice(-50) ?? '')
|
clipboard.copy(logs.slice(-50) ?? '')
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const DeviceSpecs = () => {
|
|||||||
<div className="absolute -top-11 right-2">
|
<div className="absolute -top-11 right-2">
|
||||||
<Button
|
<Button
|
||||||
themes="outline"
|
themes="outline"
|
||||||
className="bg-white"
|
className="bg-white dark:bg-secondary/50"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.copy(userAgent ?? '')
|
clipboard.copy(userAgent ?? '')
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const ModalTroubleShooting: React.FC = () => {
|
|||||||
<a
|
<a
|
||||||
href="https://jan.ai/guides/troubleshooting"
|
href="https://jan.ai/guides/troubleshooting"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-blue-600 hover:underline dark:text-blue-300"
|
||||||
>
|
>
|
||||||
troubleshooting guide
|
troubleshooting guide
|
||||||
</a>
|
</a>
|
||||||
@ -65,7 +65,7 @@ const ModalTroubleShooting: React.FC = () => {
|
|||||||
<a
|
<a
|
||||||
href="https://discord.gg/AsJ8krTT3N"
|
href="https://discord.gg/AsJ8krTT3N"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-blue-600 hover:underline dark:text-blue-300"
|
||||||
>
|
>
|
||||||
Discord
|
Discord
|
||||||
</a>
|
</a>
|
||||||
@ -77,8 +77,8 @@ const ModalTroubleShooting: React.FC = () => {
|
|||||||
|
|
||||||
<div className="flex flex-col pt-4">
|
<div className="flex flex-col pt-4">
|
||||||
{/* TODO @faisal replace this once we have better tabs component UI */}
|
{/* TODO @faisal replace this once we have better tabs component UI */}
|
||||||
<div className="relative bg-zinc-100 px-4 py-2">
|
<div className="relative bg-zinc-100 px-4 py-2 dark:bg-secondary/50">
|
||||||
<ul className="inline-flex space-x-2 rounded-lg bg-zinc-200 px-1">
|
<ul className="inline-flex space-x-2 rounded-lg bg-zinc-200 px-1 dark:bg-secondary">
|
||||||
{logOption.map((name, i) => {
|
{logOption.map((name, i) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
@ -89,14 +89,15 @@ const ModalTroubleShooting: React.FC = () => {
|
|||||||
<span
|
<span
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'relative z-50 font-medium text-muted-foreground',
|
'relative z-50 font-medium text-muted-foreground',
|
||||||
isTabActive === i && 'font-bold text-foreground'
|
isTabActive === i &&
|
||||||
|
'font-bold text-foreground dark:text-black'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
{isTabActive === i && (
|
{isTabActive === i && (
|
||||||
<m.div
|
<m.div
|
||||||
className="absolute left-0 top-1 h-[calc(100%-8px)] w-full rounded-md bg-background"
|
className="absolute left-0 top-1 h-[calc(100%-8px)] w-full rounded-md bg-background dark:bg-white"
|
||||||
layoutId="log-state-active"
|
layoutId="log-state-active"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -30,10 +30,12 @@ const ModelConfigInput: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="mb-2 flex items-center gap-x-2">
|
<div className="mb-2 flex items-center gap-x-2">
|
||||||
<p className="text-sm font-semibold text-zinc-500">{title}</p>
|
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<InfoIcon size={16} className="flex-shrink-0" />
|
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
<TooltipContent side="top" className="max-w-[240px]">
|
<TooltipContent side="top" className="max-w-[240px]">
|
||||||
|
|||||||
@ -33,7 +33,7 @@ const OpenAiKeyInput: React.FC = () => {
|
|||||||
<div className="my-4">
|
<div className="my-4">
|
||||||
<label
|
<label
|
||||||
id="thread-title"
|
id="thread-title"
|
||||||
className="mb-2 inline-block font-bold text-gray-600"
|
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
API Key
|
API Key
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@ -6,9 +6,17 @@ import { ThemeProvider } from 'next-themes'
|
|||||||
|
|
||||||
import { motion as m } from 'framer-motion'
|
import { motion as m } from 'framer-motion'
|
||||||
|
|
||||||
|
import { useBodyClass } from '@/hooks/useBodyClass'
|
||||||
|
|
||||||
|
import { useUserConfigs } from '@/hooks/useUserConfigs'
|
||||||
|
|
||||||
export default function ThemeWrapper({ children }: PropsWithChildren) {
|
export default function ThemeWrapper({ children }: PropsWithChildren) {
|
||||||
|
const [config] = useUserConfigs()
|
||||||
|
|
||||||
|
useBodyClass(config.primaryColor || 'primary-yellow')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider attribute="class" forcedTheme="light">
|
<ThemeProvider attribute="class" enableSystem>
|
||||||
<m.div
|
<m.div
|
||||||
initial={{ opacity: 0, y: -10 }}
|
initial={{ opacity: 0, y: -10 }}
|
||||||
animate={{
|
animate={{
|
||||||
|
|||||||
@ -11,8 +11,6 @@ import EventListenerWrapper from '@/containers/Providers/EventListener'
|
|||||||
import JotaiWrapper from '@/containers/Providers/Jotai'
|
import JotaiWrapper from '@/containers/Providers/Jotai'
|
||||||
import ThemeWrapper from '@/containers/Providers/Theme'
|
import ThemeWrapper from '@/containers/Providers/Theme'
|
||||||
|
|
||||||
import FeatureToggleWrapper from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { setupCoreServices } from '@/services/coreService'
|
import { setupCoreServices } from '@/services/coreService'
|
||||||
import {
|
import {
|
||||||
isCoreExtensionInstalled,
|
isCoreExtensionInstalled,
|
||||||
@ -81,7 +79,6 @@ const Providers = (props: PropsWithChildren) => {
|
|||||||
{settingUp && <Loader description="Preparing Update..." />}
|
{settingUp && <Loader description="Preparing Update..." />}
|
||||||
{setupCore && activated && (
|
{setupCore && activated && (
|
||||||
<KeyListener>
|
<KeyListener>
|
||||||
<FeatureToggleWrapper>
|
|
||||||
<EventListenerWrapper>
|
<EventListenerWrapper>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<DataLoader>{children}</DataLoader>
|
<DataLoader>{children}</DataLoader>
|
||||||
@ -89,7 +86,6 @@ const Providers = (props: PropsWithChildren) => {
|
|||||||
{!isMac && <GPUDriverPrompt />}
|
{!isMac && <GPUDriverPrompt />}
|
||||||
</EventListenerWrapper>
|
</EventListenerWrapper>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</FeatureToggleWrapper>
|
|
||||||
</KeyListener>
|
</KeyListener>
|
||||||
)}
|
)}
|
||||||
</ThemeWrapper>
|
</ThemeWrapper>
|
||||||
|
|||||||
@ -57,7 +57,7 @@ const ServerLogs = (props: ServerLogsProps) => {
|
|||||||
<div className="absolute -top-11 right-2">
|
<div className="absolute -top-11 right-2">
|
||||||
<Button
|
<Button
|
||||||
themes="outline"
|
themes="outline"
|
||||||
className="bg-white"
|
className="bg-white dark:bg-secondary/50"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.copy(logs.slice(-100) ?? '')
|
clipboard.copy(logs.slice(-100) ?? '')
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -42,10 +42,12 @@ const SliderRightPanel: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="mb-3 flex items-center gap-x-2">
|
<div className="mb-3 flex items-center gap-x-2">
|
||||||
<p className="text-sm font-semibold text-zinc-500">{title}</p>
|
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<InfoIcon size={16} className="flex-shrink-0" />
|
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
<TooltipContent side="top" className="max-w-[240px]">
|
<TooltipContent side="top" className="max-w-[240px]">
|
||||||
|
|||||||
@ -108,11 +108,11 @@ export function toaster(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'unset-drag relative flex animate-enter items-center gap-x-4 rounded-lg bg-foreground px-4 py-2 text-white',
|
'unset-drag dark:bg-zinc-white relative flex animate-enter items-center gap-x-4 rounded-lg bg-foreground px-4 py-2 text-white dark:border dark:border-border',
|
||||||
t.visible ? 'animate-enter' : 'animate-leave'
|
t.visible ? 'animate-enter' : 'animate-leave'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-x-3">
|
<div className="flex items-start gap-x-3 dark:text-black">
|
||||||
<div className="mt-1">{renderIcon(type)}</div>
|
<div className="mt-1">{renderIcon(type)}</div>
|
||||||
<div className="pr-4">
|
<div className="pr-4">
|
||||||
<h1 className="font-bold">{title}</h1>
|
<h1 className="font-bold">{title}</h1>
|
||||||
@ -120,7 +120,7 @@ export function toaster(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<XIcon
|
<XIcon
|
||||||
size={24}
|
size={24}
|
||||||
className="absolute right-2 top-2 w-4 cursor-pointer"
|
className="absolute right-2 top-2 w-4 cursor-pointer dark:text-black"
|
||||||
onClick={() => toast.dismiss(t.id)}
|
onClick={() => toast.dismiss(t.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -138,16 +138,16 @@ export function snackbar(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'unset-drag relative bottom-2 flex animate-enter items-center gap-x-4 rounded-lg bg-foreground px-4 py-2 text-white',
|
'unset-drag dark:bg-zinc-white relative bottom-2 flex animate-enter items-center gap-x-4 rounded-lg bg-foreground px-4 py-2 text-white dark:border dark:border-border',
|
||||||
t.visible ? 'animate-enter' : 'animate-leave'
|
t.visible ? 'animate-enter' : 'animate-leave'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-x-3">
|
<div className="flex items-start gap-x-3 dark:text-black">
|
||||||
<div>{renderIcon(type)}</div>
|
<div>{renderIcon(type)}</div>
|
||||||
<p className="pr-4">{description}</p>
|
<p className="pr-4">{description}</p>
|
||||||
<XIcon
|
<XIcon
|
||||||
size={24}
|
size={24}
|
||||||
className="absolute right-2 top-1/2 w-4 -translate-y-1/2 cursor-pointer"
|
className="absolute right-2 top-1/2 w-4 -translate-y-1/2 cursor-pointer dark:text-black"
|
||||||
onClick={() => toast.dismiss(t.id)}
|
onClick={() => toast.dismiss(t.id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,104 +0,0 @@
|
|||||||
import { createContext, ReactNode, useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
interface FeatureToggleContextType {
|
|
||||||
experimentalFeature: boolean
|
|
||||||
ignoreSSL: boolean
|
|
||||||
proxy: string
|
|
||||||
proxyEnabled: boolean
|
|
||||||
vulkanEnabled: boolean
|
|
||||||
setExperimentalFeature: (on: boolean) => void
|
|
||||||
setVulkanEnabled: (on: boolean) => void
|
|
||||||
setIgnoreSSL: (on: boolean) => void
|
|
||||||
setProxy: (value: string) => void
|
|
||||||
setProxyEnabled: (on: boolean) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialContext: FeatureToggleContextType = {
|
|
||||||
experimentalFeature: false,
|
|
||||||
ignoreSSL: false,
|
|
||||||
proxy: '',
|
|
||||||
proxyEnabled: false,
|
|
||||||
vulkanEnabled: false,
|
|
||||||
setExperimentalFeature: () => {},
|
|
||||||
setVulkanEnabled: () => {},
|
|
||||||
setIgnoreSSL: () => {},
|
|
||||||
setProxy: () => {},
|
|
||||||
setProxyEnabled: () => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FeatureToggleContext =
|
|
||||||
createContext<FeatureToggleContextType>(initialContext)
|
|
||||||
|
|
||||||
export default function FeatureToggleWrapper({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: ReactNode
|
|
||||||
}) {
|
|
||||||
const EXPERIMENTAL_FEATURE = 'experimentalFeature'
|
|
||||||
const VULKAN_ENABLED = 'vulkanEnabled'
|
|
||||||
const IGNORE_SSL = 'ignoreSSLFeature'
|
|
||||||
const HTTPS_PROXY_FEATURE = 'httpsProxyFeature'
|
|
||||||
const PROXY_FEATURE_ENABLED = 'proxyFeatureEnabled'
|
|
||||||
|
|
||||||
const [experimentalFeature, directSetExperimentalFeature] =
|
|
||||||
useState<boolean>(false)
|
|
||||||
const [proxyEnabled, directSetProxyEnabled] = useState<boolean>(false)
|
|
||||||
const [vulkanEnabled, directEnableVulkan] = useState<boolean>(false)
|
|
||||||
const [ignoreSSL, directSetIgnoreSSL] = useState<boolean>(false)
|
|
||||||
const [proxy, directSetProxy] = useState<string>('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
directSetExperimentalFeature(
|
|
||||||
localStorage.getItem(EXPERIMENTAL_FEATURE) === 'true'
|
|
||||||
)
|
|
||||||
directSetIgnoreSSL(localStorage.getItem(IGNORE_SSL) === 'true')
|
|
||||||
directSetProxy(localStorage.getItem(HTTPS_PROXY_FEATURE) ?? '')
|
|
||||||
directSetProxyEnabled(
|
|
||||||
localStorage.getItem(PROXY_FEATURE_ENABLED) === 'true'
|
|
||||||
)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const setExperimentalFeature = (on: boolean) => {
|
|
||||||
localStorage.setItem(EXPERIMENTAL_FEATURE, on ? 'true' : 'false')
|
|
||||||
directSetExperimentalFeature(on)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setVulkanEnabled = (on: boolean) => {
|
|
||||||
localStorage.setItem(VULKAN_ENABLED, on ? 'true' : 'false')
|
|
||||||
directEnableVulkan(on)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setIgnoreSSL = (on: boolean) => {
|
|
||||||
localStorage.setItem(IGNORE_SSL, on ? 'true' : 'false')
|
|
||||||
directSetIgnoreSSL(on)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setProxy = (proxy: string) => {
|
|
||||||
localStorage.setItem(HTTPS_PROXY_FEATURE, proxy)
|
|
||||||
directSetProxy(proxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setProxyEnabled = (on: boolean) => {
|
|
||||||
localStorage.setItem(PROXY_FEATURE_ENABLED, on ? 'true' : 'false')
|
|
||||||
directSetProxyEnabled(on)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FeatureToggleContext.Provider
|
|
||||||
value={{
|
|
||||||
experimentalFeature,
|
|
||||||
ignoreSSL,
|
|
||||||
proxy,
|
|
||||||
proxyEnabled,
|
|
||||||
vulkanEnabled,
|
|
||||||
setExperimentalFeature,
|
|
||||||
setVulkanEnabled,
|
|
||||||
setIgnoreSSL,
|
|
||||||
setProxy,
|
|
||||||
setProxyEnabled,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</FeatureToggleContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
16
web/helpers/atoms/ApiServer.atom.ts
Normal file
16
web/helpers/atoms/ApiServer.atom.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { atomWithStorage } from 'jotai/utils'
|
||||||
|
|
||||||
|
export const hostOptions = ['127.0.0.1', '0.0.0.0']
|
||||||
|
|
||||||
|
export const apiServerPortAtom = atomWithStorage('apiServerPort', '1337')
|
||||||
|
export const apiServerHostAtom = atomWithStorage('apiServerHost', '127.0.0.1')
|
||||||
|
|
||||||
|
export const apiServerCorsEnabledAtom = atomWithStorage(
|
||||||
|
'apiServerCorsEnabled',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
export const apiServerVerboseLogEnabledAtom = atomWithStorage(
|
||||||
|
'apiServerVerboseLogEnabled',
|
||||||
|
true
|
||||||
|
)
|
||||||
@ -1,3 +1,21 @@
|
|||||||
import { atom } from 'jotai'
|
import { atom } from 'jotai'
|
||||||
|
import { atomWithStorage } from 'jotai/utils'
|
||||||
|
|
||||||
|
const EXPERIMENTAL_FEATURE = 'experimentalFeature'
|
||||||
|
const PROXY_FEATURE_ENABLED = 'proxyFeatureEnabled'
|
||||||
|
const VULKAN_ENABLED = 'vulkanEnabled'
|
||||||
|
const IGNORE_SSL = 'ignoreSSLFeature'
|
||||||
|
const HTTPS_PROXY_FEATURE = 'httpsProxyFeature'
|
||||||
|
|
||||||
export const janDataFolderPathAtom = atom('')
|
export const janDataFolderPathAtom = atom('')
|
||||||
|
|
||||||
|
export const experimentalFeatureEnabledAtom = atomWithStorage(
|
||||||
|
EXPERIMENTAL_FEATURE,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
export const proxyEnabledAtom = atomWithStorage(PROXY_FEATURE_ENABLED, false)
|
||||||
|
export const proxyAtom = atomWithStorage(HTTPS_PROXY_FEATURE, '')
|
||||||
|
|
||||||
|
export const ignoreSslAtom = atomWithStorage(IGNORE_SSL, false)
|
||||||
|
export const vulkanEnabledAtom = atomWithStorage(VULKAN_ENABLED, false)
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { useContext } from 'react'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExtensionTypeEnum,
|
ExtensionTypeEnum,
|
||||||
HuggingFaceExtension,
|
HuggingFaceExtension,
|
||||||
@ -7,18 +5,18 @@ import {
|
|||||||
Quantization,
|
Quantization,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
|
|
||||||
import { useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
|
import { ignoreSslAtom, proxyAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
import {
|
import {
|
||||||
conversionStatusAtom,
|
conversionStatusAtom,
|
||||||
conversionErrorAtom,
|
conversionErrorAtom,
|
||||||
} from '@/helpers/atoms/HFConverter.atom'
|
} from '@/helpers/atoms/HFConverter.atom'
|
||||||
|
|
||||||
export const useConvertHuggingFaceModel = () => {
|
export const useConvertHuggingFaceModel = () => {
|
||||||
const { ignoreSSL, proxy } = useContext(FeatureToggleContext)
|
const proxy = useAtomValue(proxyAtom)
|
||||||
|
const ignoreSSL = useAtomValue(ignoreSslAtom)
|
||||||
const setConversionStatus = useSetAtom(conversionStatusAtom)
|
const setConversionStatus = useSetAtom(conversionStatusAtom)
|
||||||
const setConversionError = useSetAtom(conversionErrorAtom)
|
const setConversionError = useSetAtom(conversionErrorAtom)
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { useContext } from 'react'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Assistant,
|
Assistant,
|
||||||
ConversationalExtension,
|
ConversationalExtension,
|
||||||
@ -17,8 +15,6 @@ import { atom, useAtomValue, useSetAtom } from 'jotai'
|
|||||||
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
||||||
import { fileUploadAtom } from '@/containers/Providers/Jotai'
|
import { fileUploadAtom } from '@/containers/Providers/Jotai'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { generateThreadId } from '@/utils/thread'
|
import { generateThreadId } from '@/utils/thread'
|
||||||
|
|
||||||
import useRecommendedModel from './useRecommendedModel'
|
import useRecommendedModel from './useRecommendedModel'
|
||||||
@ -27,6 +23,7 @@ import useSetActiveThread from './useSetActiveThread'
|
|||||||
|
|
||||||
import { extensionManager } from '@/extension'
|
import { extensionManager } from '@/extension'
|
||||||
|
|
||||||
|
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
import {
|
import {
|
||||||
threadsAtom,
|
threadsAtom,
|
||||||
threadStatesAtom,
|
threadStatesAtom,
|
||||||
@ -59,7 +56,8 @@ export const useCreateNewThread = () => {
|
|||||||
const setFileUpload = useSetAtom(fileUploadAtom)
|
const setFileUpload = useSetAtom(fileUploadAtom)
|
||||||
const setSelectedModel = useSetAtom(selectedModelAtom)
|
const setSelectedModel = useSetAtom(selectedModelAtom)
|
||||||
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
||||||
const { experimentalFeature } = useContext(FeatureToggleContext)
|
|
||||||
|
const experimentalEnabled = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
|
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
|
||||||
|
|
||||||
const { recommendedModel, downloadedModels } = useRecommendedModel()
|
const { recommendedModel, downloadedModels } = useRecommendedModel()
|
||||||
@ -94,7 +92,7 @@ export const useCreateNewThread = () => {
|
|||||||
const assistantInfo: ThreadAssistantInfo = {
|
const assistantInfo: ThreadAssistantInfo = {
|
||||||
assistant_id: assistant.id,
|
assistant_id: assistant.id,
|
||||||
assistant_name: assistant.name,
|
assistant_name: assistant.name,
|
||||||
tools: experimentalFeature ? [assistantTools] : assistant.tools,
|
tools: experimentalEnabled ? [assistantTools] : assistant.tools,
|
||||||
model: {
|
model: {
|
||||||
id: defaultModel?.id ?? '*',
|
id: defaultModel?.id ?? '*',
|
||||||
settings: defaultModel?.settings ?? {},
|
settings: defaultModel?.settings ?? {},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useContext } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Model,
|
Model,
|
||||||
@ -10,17 +10,22 @@ import {
|
|||||||
DownloadState,
|
DownloadState,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
|
|
||||||
import { useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { setDownloadStateAtom } from './useDownloadState'
|
import { setDownloadStateAtom } from './useDownloadState'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
|
import {
|
||||||
|
ignoreSslAtom,
|
||||||
|
proxyAtom,
|
||||||
|
proxyEnabledAtom,
|
||||||
|
} from '@/helpers/atoms/AppConfig.atom'
|
||||||
import { addDownloadingModelAtom } from '@/helpers/atoms/Model.atom'
|
import { addDownloadingModelAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
export default function useDownloadModel() {
|
export default function useDownloadModel() {
|
||||||
const { ignoreSSL, proxy, proxyEnabled } = useContext(FeatureToggleContext)
|
const ignoreSSL = useAtomValue(ignoreSslAtom)
|
||||||
|
const proxy = useAtomValue(proxyAtom)
|
||||||
|
const proxyEnabled = useAtomValue(proxyEnabledAtom)
|
||||||
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
||||||
const addDownloadingModel = useSetAtom(addDownloadingModelAtom)
|
const addDownloadingModel = useSetAtom(addDownloadingModelAtom)
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export default function useDropModelBinaries() {
|
|||||||
}))
|
}))
|
||||||
if (unsupportedFiles.length > 0) {
|
if (unsupportedFiles.length > 0) {
|
||||||
snackbar({
|
snackbar({
|
||||||
description: `File has to be a .gguf file`,
|
description: `Only files with .gguf extension can be imported.`,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,9 @@ export const useGetHFRepoData = () => {
|
|||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
setRepoData(data)
|
setRepoData(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setFetchError(err as Error)
|
setFetchError(
|
||||||
|
Error("The repo does not exist or you don't have access to it.")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|||||||
11
web/hooks/useUserConfigs.ts
Normal file
11
web/hooks/useUserConfigs.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import { atomWithStorage } from 'jotai/utils'
|
||||||
|
|
||||||
|
export const userConfigs = atomWithStorage<UserConfig>('config', {
|
||||||
|
gettingStartedShow: true,
|
||||||
|
primaryColor: 'primary-blue',
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useUserConfigs() {
|
||||||
|
return useAtom(userConfigs)
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { useContext, useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { InferenceEvent, MessageStatus, events } from '@janhq/core'
|
import { InferenceEvent, MessageStatus, events } from '@janhq/core'
|
||||||
|
|
||||||
@ -24,8 +24,6 @@ import { twMerge } from 'tailwind-merge'
|
|||||||
|
|
||||||
import { currentPromptAtom, fileUploadAtom } from '@/containers/Providers/Jotai'
|
import { currentPromptAtom, fileUploadAtom } from '@/containers/Providers/Jotai'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
import { useClickOutside } from '@/hooks/useClickOutside'
|
import { useClickOutside } from '@/hooks/useClickOutside'
|
||||||
|
|
||||||
@ -34,6 +32,7 @@ import useSendChatMessage from '@/hooks/useSendChatMessage'
|
|||||||
import FileUploadPreview from '../FileUploadPreview'
|
import FileUploadPreview from '../FileUploadPreview'
|
||||||
import ImageUploadPreview from '../ImageUploadPreview'
|
import ImageUploadPreview from '../ImageUploadPreview'
|
||||||
|
|
||||||
|
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
@ -58,7 +57,7 @@ const ChatInput: React.FC = () => {
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const imageInputRef = useRef<HTMLInputElement>(null)
|
const imageInputRef = useRef<HTMLInputElement>(null)
|
||||||
const [showAttacmentMenus, setShowAttacmentMenus] = useState(false)
|
const [showAttacmentMenus, setShowAttacmentMenus] = useState(false)
|
||||||
const { experimentalFeature } = useContext(FeatureToggleContext)
|
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
const isGeneratingResponse = useAtomValue(isGeneratingResponseAtom)
|
const isGeneratingResponse = useAtomValue(isGeneratingResponseAtom)
|
||||||
const threadStates = useAtomValue(threadStatesAtom)
|
const threadStates = useAtomValue(threadStatesAtom)
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,9 @@ const CleanThreadModal: React.FC<Props> = ({ threadId }) => {
|
|||||||
<ModalTrigger asChild onClick={(e) => e.stopPropagation()}>
|
<ModalTrigger asChild onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
||||||
<Paintbrush size={16} className="text-muted-foreground" />
|
<Paintbrush size={16} className="text-muted-foreground" />
|
||||||
<span className="text-bold text-black">Clean thread</span>
|
<span className="text-bold text-black dark:text-muted-foreground">
|
||||||
|
Clean thread
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ModalTrigger>
|
</ModalTrigger>
|
||||||
<ModalPortal />
|
<ModalPortal />
|
||||||
|
|||||||
@ -33,8 +33,10 @@ const DeleteThreadModal: React.FC<Props> = ({ threadId }) => {
|
|||||||
<Modal>
|
<Modal>
|
||||||
<ModalTrigger asChild onClick={(e) => e.stopPropagation()}>
|
<ModalTrigger asChild onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
||||||
<Trash2Icon size={16} className="text-red-600" />
|
<Trash2Icon size={16} className="text-red-600 dark:text-red-300" />
|
||||||
<span className="text-bold text-red-600">Delete thread</span>
|
<span className="text-bold text-red-600 dark:text-red-300">
|
||||||
|
Delete thread
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ModalTrigger>
|
</ModalTrigger>
|
||||||
<ModalPortal />
|
<ModalPortal />
|
||||||
|
|||||||
@ -54,7 +54,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
Port 3928 is currently unavailable. Check for conflicting apps,
|
Port 3928 is currently unavailable. Check for conflicting apps,
|
||||||
or access
|
or access
|
||||||
<span
|
<span
|
||||||
className="cursor-pointer text-blue-600"
|
className="cursor-pointer text-primary dark:text-blue-400"
|
||||||
onClick={() => setModalTroubleShooting(true)}
|
onClick={() => setModalTroubleShooting(true)}
|
||||||
>
|
>
|
||||||
troubleshooting assistance
|
troubleshooting assistance
|
||||||
@ -72,7 +72,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
<p>
|
<p>
|
||||||
Jan’s in beta. Access
|
Jan’s in beta. Access
|
||||||
<span
|
<span
|
||||||
className="cursor-pointer text-blue-600"
|
className="cursor-pointer text-primary dark:text-blue-400"
|
||||||
onClick={() => setModalTroubleShooting(true)}
|
onClick={() => setModalTroubleShooting(true)}
|
||||||
>
|
>
|
||||||
troubleshooting assistance
|
troubleshooting assistance
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
import React from 'react'
|
||||||
import React, { useContext } from 'react'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
@ -24,8 +23,6 @@ import DropdownListSidebar, {
|
|||||||
selectedModelAtom,
|
selectedModelAtom,
|
||||||
} from '@/containers/DropdownListSidebar'
|
} from '@/containers/DropdownListSidebar'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||||
|
|
||||||
import { getConfigurationsData } from '@/utils/componentSettings'
|
import { getConfigurationsData } from '@/utils/componentSettings'
|
||||||
@ -37,6 +34,7 @@ import ModelSetting from '../ModelSetting'
|
|||||||
|
|
||||||
import SettingComponentBuilder from '../ModelSetting/SettingComponent'
|
import SettingComponentBuilder from '../ModelSetting/SettingComponent'
|
||||||
|
|
||||||
|
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
getActiveThreadModelParamsAtom,
|
getActiveThreadModelParamsAtom,
|
||||||
@ -50,7 +48,7 @@ const Sidebar: React.FC = () => {
|
|||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
const { updateThreadMetadata } = useCreateNewThread()
|
const { updateThreadMetadata } = useCreateNewThread()
|
||||||
const { experimentalFeature } = useContext(FeatureToggleContext)
|
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
|
|
||||||
const modelEngineParams = toSettingParams(activeModelParams)
|
const modelEngineParams = toSettingParams(activeModelParams)
|
||||||
const modelRuntimeParams = toRuntimeParams(activeModelParams)
|
const modelRuntimeParams = toRuntimeParams(activeModelParams)
|
||||||
@ -71,7 +69,7 @@ const Sidebar: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'h-full flex-shrink-0 overflow-x-hidden border-l border-border bg-background pb-6 transition-all duration-100',
|
'h-full flex-shrink-0 overflow-x-hidden border-l border-border bg-background pb-6 transition-all duration-100 dark:bg-background/20',
|
||||||
showing
|
showing
|
||||||
? 'w-80 translate-x-0 opacity-100'
|
? 'w-80 translate-x-0 opacity-100'
|
||||||
: 'w-0 translate-x-full opacity-0'
|
: 'w-0 translate-x-full opacity-0'
|
||||||
@ -87,7 +85,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
id="thread-title"
|
id="thread-title"
|
||||||
className="mb-2 inline-block font-bold text-gray-600"
|
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Title
|
Title
|
||||||
</label>
|
</label>
|
||||||
@ -106,7 +104,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<label
|
<label
|
||||||
id="thread-title"
|
id="thread-title"
|
||||||
className="mb-2 inline-block font-bold text-zinc-500"
|
className="mb-2 inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Threads ID
|
Threads ID
|
||||||
</label>
|
</label>
|
||||||
@ -127,7 +125,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
id="thread-title"
|
id="thread-title"
|
||||||
className="mb-2 inline-block font-bold text-zinc-500"
|
className="mb-2 inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Instructions
|
Instructions
|
||||||
</label>
|
</label>
|
||||||
@ -174,7 +172,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<div className="px-2 py-4">
|
<div className="px-2 py-4">
|
||||||
<SettingComponentBuilder
|
<SettingComponentBuilder
|
||||||
componentData={componentDataEngineSetting}
|
componentData={componentDataEngineSetting}
|
||||||
selector={(x: any) => x.name === 'prompt_template'}
|
selector={(x) => x.name === 'prompt_template'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
@ -203,14 +201,14 @@ const Sidebar: React.FC = () => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label
|
<label
|
||||||
id="retrieval"
|
id="retrieval"
|
||||||
className="inline-flex items-center font-bold text-zinc-500"
|
className="inline-flex items-center font-bold text-zinc-500 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Retrieval
|
Retrieval
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
size={16}
|
size={16}
|
||||||
className="ml-2 flex-shrink-0 text-black"
|
className="ml-2 flex-shrink-0 text-black dark:text-gray-500"
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
@ -269,7 +267,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<div className="item-center mb-2 flex">
|
<div className="item-center mb-2 flex">
|
||||||
<label
|
<label
|
||||||
id="embedding-model"
|
id="embedding-model"
|
||||||
className="inline-flex font-bold text-zinc-500"
|
className="inline-flex font-bold text-zinc-500 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Embedding Model
|
Embedding Model
|
||||||
</label>
|
</label>
|
||||||
@ -277,7 +275,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
size={16}
|
size={16}
|
||||||
className="ml-2 flex-shrink-0"
|
className="ml-2 flex-shrink-0 dark:text-gray-500"
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
@ -309,7 +307,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<div className="mb-2 flex items-center">
|
<div className="mb-2 flex items-center">
|
||||||
<label
|
<label
|
||||||
id="vector-database"
|
id="vector-database"
|
||||||
className="inline-block font-bold text-zinc-500"
|
className="inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Vector Database
|
Vector Database
|
||||||
</label>
|
</label>
|
||||||
@ -317,7 +315,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
size={16}
|
size={16}
|
||||||
className="ml-2 flex-shrink-0"
|
className="ml-2 flex-shrink-0 dark:text-gray-500"
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
|
|||||||
@ -79,7 +79,7 @@ export default function ThreadList() {
|
|||||||
<div
|
<div
|
||||||
key={thread.id}
|
key={thread.id}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
`group/message relative mb-1 flex cursor-pointer flex-col transition-all hover:rounded-lg hover:bg-gray-100`
|
`group/message relative mb-1 flex cursor-pointer flex-col transition-all hover:rounded-lg hover:bg-gray-100 hover:dark:bg-secondary/50`
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onThreadClick(thread)
|
onThreadClick(thread)
|
||||||
@ -90,7 +90,7 @@ export default function ThreadList() {
|
|||||||
{thread.updated && displayDate(thread.updated)}
|
{thread.updated && displayDate(thread.updated)}
|
||||||
</p>
|
</p>
|
||||||
<h2 className="line-clamp-1 font-bold">{thread.title}</h2>
|
<h2 className="line-clamp-1 font-bold">{thread.title}</h2>
|
||||||
<p className="mt-1 line-clamp-1 text-xs text-gray-700 group-hover/message:max-w-[160px]">
|
<p className="mt-1 line-clamp-1 text-xs text-gray-700 group-hover/message:max-w-[160px] dark:text-gray-300">
|
||||||
{threadStates[thread.id]?.lastMessage
|
{threadStates[thread.id]?.lastMessage
|
||||||
? threadStates[thread.id]?.lastMessage
|
? threadStates[thread.id]?.lastMessage
|
||||||
: 'No new message'}
|
: 'No new message'}
|
||||||
@ -98,7 +98,7 @@ export default function ThreadList() {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
`group/icon invisible absolute bottom-2 right-2 z-20 rounded-lg p-1 text-muted-foreground hover:bg-gray-200 group-hover/message:visible`
|
`group/icon invisible absolute bottom-2 right-2 z-20 rounded-lg p-1 text-muted-foreground hover:bg-gray-200 group-hover/message:visible hover:dark:bg-secondary`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MoreVerticalIcon />
|
<MoreVerticalIcon />
|
||||||
@ -109,7 +109,7 @@ export default function ThreadList() {
|
|||||||
</div>
|
</div>
|
||||||
{activeThreadId === thread.id && (
|
{activeThreadId === thread.id && (
|
||||||
<m.div
|
<m.div
|
||||||
className="absolute inset-0 left-0 h-full w-full rounded-lg bg-gray-100 p-4"
|
className="absolute inset-0 left-0 h-full w-full rounded-lg bg-gray-100 p-4 dark:bg-secondary/50"
|
||||||
layoutId="active-thread"
|
layoutId="active-thread"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import React, { useContext, useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { useDropzone } from 'react-dropzone'
|
import { useDropzone } from 'react-dropzone'
|
||||||
|
|
||||||
@ -18,8 +18,6 @@ import { showLeftSideBarAtom } from '@/containers/Providers/KeyListener'
|
|||||||
|
|
||||||
import { snackbar } from '@/containers/Toast'
|
import { snackbar } from '@/containers/Toast'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { activeModelAtom } from '@/hooks/useActiveModel'
|
import { activeModelAtom } from '@/hooks/useActiveModel'
|
||||||
import { queuedMessageAtom, reloadModelAtom } from '@/hooks/useSendChatMessage'
|
import { queuedMessageAtom, reloadModelAtom } from '@/hooks/useSendChatMessage'
|
||||||
|
|
||||||
@ -31,6 +29,7 @@ import ChatInput from './ChatInput'
|
|||||||
import RequestDownloadModel from './RequestDownloadModel'
|
import RequestDownloadModel from './RequestDownloadModel'
|
||||||
import Sidebar from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
|
|
||||||
|
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
engineParamsUpdateAtom,
|
engineParamsUpdateAtom,
|
||||||
@ -63,7 +62,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
const reloadModel = useAtomValue(reloadModelAtom)
|
const reloadModel = useAtomValue(reloadModelAtom)
|
||||||
const [dragRejected, setDragRejected] = useState({ code: '' })
|
const [dragRejected, setDragRejected] = useState({ code: '' })
|
||||||
const setFileUpload = useSetAtom(fileUploadAtom)
|
const setFileUpload = useSetAtom(fileUploadAtom)
|
||||||
const { experimentalFeature } = useContext(FeatureToggleContext)
|
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
|
|
||||||
const activeModel = useAtomValue(activeModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export const HuggingFaceRepoDataLoadedModal = () => {
|
|||||||
? '❌ This model is not supported!'
|
? '❌ This model is not supported!'
|
||||||
: '✅ This model is supported!'}
|
: '✅ This model is supported!'}
|
||||||
</p>
|
</p>
|
||||||
{repoData.tags.includes('gguf') ? (
|
{repoData.tags?.includes('gguf') ? (
|
||||||
<p>...But you can import it manually!</p>
|
<p>...But you can import it manually!</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export const HuggingFaceSearchErrorModal = () => {
|
|||||||
<p className="text-2xl font-bold">Error!</p>
|
<p className="text-2xl font-bold">Error!</p>
|
||||||
<p className="text-gray-500">Fetch error</p>
|
<p className="text-gray-500">Fetch error</p>
|
||||||
</div>
|
</div>
|
||||||
<p>{fetchError.message}</p>
|
<p className="text-center">{fetchError.message}</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={getRepoData}
|
onClick={getRepoData}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export const HuggingFaceSearchModal = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
placeholder="e.g. username/repo-name"
|
placeholder="e.g. username/repo-name"
|
||||||
className="bg-white"
|
className="bg-white dark:bg-background"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setRepoID(e.target.value)
|
setRepoID(e.target.value)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useContext, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
@ -15,13 +15,12 @@ import {
|
|||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
import { UploadIcon, SearchIcon } from 'lucide-react'
|
import { UploadIcon, SearchIcon } from 'lucide-react'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { setImportModelStageAtom } from '@/hooks/useImportModel'
|
import { setImportModelStageAtom } from '@/hooks/useImportModel'
|
||||||
|
|
||||||
import ExploreModelList from './ExploreModelList'
|
import ExploreModelList from './ExploreModelList'
|
||||||
import { HuggingFaceModal } from './HuggingFaceModal'
|
import { HuggingFaceModal } from './HuggingFaceModal'
|
||||||
|
|
||||||
|
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
import {
|
import {
|
||||||
configuredModelsAtom,
|
configuredModelsAtom,
|
||||||
downloadedModelsAtom,
|
downloadedModelsAtom,
|
||||||
@ -38,7 +37,7 @@ const ExploreModelsScreen = () => {
|
|||||||
const [showHuggingFaceModal, setShowHuggingFaceModal] = useState(false)
|
const [showHuggingFaceModal, setShowHuggingFaceModal] = useState(false)
|
||||||
const setImportModelStage = useSetAtom(setImportModelStageAtom)
|
const setImportModelStage = useSetAtom(setImportModelStageAtom)
|
||||||
|
|
||||||
const { experimentalFeature } = useContext(FeatureToggleContext)
|
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
|
|
||||||
const filteredModels = configuredModels.filter((x) => {
|
const filteredModels = configuredModels.filter((x) => {
|
||||||
if (sortSelected === 'Downloaded') {
|
if (sortSelected === 'Downloaded') {
|
||||||
@ -91,13 +90,13 @@ const ExploreModelsScreen = () => {
|
|||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search models"
|
placeholder="Search models"
|
||||||
className="bg-white pl-9"
|
className="bg-white pl-9 dark:bg-background"
|
||||||
onChange={(e) => setsearchValue(e.target.value)}
|
onChange={(e) => setsearchValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
themes="outline"
|
themes="outline"
|
||||||
className="gap-2 bg-white"
|
className="gap-2 bg-white dark:bg-secondary"
|
||||||
onClick={onImportModelClick}
|
onClick={onImportModelClick}
|
||||||
>
|
>
|
||||||
<UploadIcon size={16} />
|
<UploadIcon size={16} />
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { Paintbrush, CodeIcon } from 'lucide-react'
|
import { Paintbrush, CodeIcon } from 'lucide-react'
|
||||||
import { ExternalLinkIcon, InfoIcon } from 'lucide-react'
|
import { ExternalLinkIcon, InfoIcon } from 'lucide-react'
|
||||||
@ -53,13 +53,15 @@ import SettingComponentBuilder from '../Chat/ModelSetting/SettingComponent'
|
|||||||
|
|
||||||
import { showRightSideBarAtom } from '../Chat/Sidebar'
|
import { showRightSideBarAtom } from '../Chat/Sidebar'
|
||||||
|
|
||||||
|
import {
|
||||||
|
apiServerCorsEnabledAtom,
|
||||||
|
apiServerHostAtom,
|
||||||
|
apiServerPortAtom,
|
||||||
|
apiServerVerboseLogEnabledAtom,
|
||||||
|
hostOptions,
|
||||||
|
} from '@/helpers/atoms/ApiServer.atom'
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
|
|
||||||
const corsEnabledAtom = atom(true)
|
|
||||||
const verboseEnabledAtom = atom(true)
|
|
||||||
const hostAtom = atom('127.0.0.1')
|
|
||||||
const portAtom = atom('1337')
|
|
||||||
|
|
||||||
const LocalServerScreen = () => {
|
const LocalServerScreen = () => {
|
||||||
const [errorRangePort, setErrorRangePort] = useState(false)
|
const [errorRangePort, setErrorRangePort] = useState(false)
|
||||||
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
|
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
|
||||||
@ -73,14 +75,14 @@ const LocalServerScreen = () => {
|
|||||||
const modelEngineParams = toSettingParams(selectedModel?.settings)
|
const modelEngineParams = toSettingParams(selectedModel?.settings)
|
||||||
const componentDataEngineSetting = getConfigurationsData(modelEngineParams)
|
const componentDataEngineSetting = getConfigurationsData(modelEngineParams)
|
||||||
|
|
||||||
const [isCorsEnabled, setIsCorsEnabled] = useAtom(corsEnabledAtom)
|
const [isCorsEnabled, setIsCorsEnabled] = useAtom(apiServerCorsEnabledAtom)
|
||||||
const [isVerboseEnabled, setIsVerboseEnabled] = useAtom(verboseEnabledAtom)
|
const [isVerboseEnabled, setIsVerboseEnabled] = useAtom(
|
||||||
const [host, setHost] = useAtom(hostAtom)
|
apiServerVerboseLogEnabledAtom
|
||||||
const [port, setPort] = useAtom(portAtom)
|
)
|
||||||
|
const [host, setHost] = useAtom(apiServerHostAtom)
|
||||||
|
const [port, setPort] = useAtom(apiServerPortAtom)
|
||||||
const [loadModelError, setLoadModelError] = useAtom(loadModelErrorAtom)
|
const [loadModelError, setLoadModelError] = useAtom(loadModelErrorAtom)
|
||||||
|
|
||||||
const hostOptions = ['127.0.0.1', '0.0.0.0']
|
|
||||||
|
|
||||||
const FIRST_TIME_VISIT_API_SERVER = 'firstTimeVisitAPIServer'
|
const FIRST_TIME_VISIT_API_SERVER = 'firstTimeVisitAPIServer'
|
||||||
|
|
||||||
const [firstTimeVisitAPIServer, setFirstTimeVisitAPIServer] =
|
const [firstTimeVisitAPIServer, setFirstTimeVisitAPIServer] =
|
||||||
@ -88,11 +90,7 @@ const LocalServerScreen = () => {
|
|||||||
|
|
||||||
const handleChangePort = useCallback(
|
const handleChangePort = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
if (Number(value) <= 0 || Number(value) >= 65536) {
|
setErrorRangePort(Number(value) <= 0 || Number(value) >= 65536)
|
||||||
setErrorRangePort(true)
|
|
||||||
} else {
|
|
||||||
setErrorRangePort(false)
|
|
||||||
}
|
|
||||||
setPort(value)
|
setPort(value)
|
||||||
},
|
},
|
||||||
[setPort]
|
[setPort]
|
||||||
@ -181,7 +179,7 @@ const LocalServerScreen = () => {
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div className="space-y-4 p-4">
|
<div className="space-y-4 p-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 block text-sm font-semibold text-zinc-500 ">
|
<p className="mb-2 block text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
||||||
Server Options
|
Server Options
|
||||||
</p>
|
</p>
|
||||||
<div className="flex w-full flex-shrink-0 items-center gap-x-2">
|
<div className="flex w-full flex-shrink-0 items-center gap-x-2">
|
||||||
@ -231,12 +229,15 @@ const LocalServerScreen = () => {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
id="cors"
|
id="cors"
|
||||||
className="mb-2 inline-flex items-start gap-x-2 font-bold text-zinc-500"
|
className="mb-2 inline-flex items-start gap-x-2 font-bold text-zinc-500 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Cross-Origin-Resource-Sharing (CORS)
|
Cross-Origin-Resource-Sharing (CORS)
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<InfoIcon size={16} className="mt-0.5 flex-shrink-0" />
|
<InfoIcon
|
||||||
|
size={16}
|
||||||
|
className="mt-0.5 flex-shrink-0 dark:text-gray-500"
|
||||||
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
<TooltipContent side="top" className="max-w-[240px]">
|
<TooltipContent side="top" className="max-w-[240px]">
|
||||||
@ -263,12 +264,15 @@ const LocalServerScreen = () => {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
id="verbose"
|
id="verbose"
|
||||||
className="mb-2 inline-flex items-start gap-x-2 font-bold text-zinc-500"
|
className="mb-2 inline-flex items-start gap-x-2 font-bold text-zinc-500 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Verbose Server Logs
|
Verbose Server Logs
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<InfoIcon size={16} className="mt-0.5 flex-shrink-0" />
|
<InfoIcon
|
||||||
|
size={16}
|
||||||
|
className="mt-0.5 flex-shrink-0 dark:text-gray-500"
|
||||||
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
<TooltipContent side="top" className="max-w-[240px]">
|
<TooltipContent side="top" className="max-w-[240px]">
|
||||||
@ -309,13 +313,13 @@ const LocalServerScreen = () => {
|
|||||||
|
|
||||||
{/* Middle Bar */}
|
{/* Middle Bar */}
|
||||||
<ScrollToBottom className="relative flex h-full w-full flex-col overflow-auto bg-background">
|
<ScrollToBottom className="relative flex h-full w-full flex-col overflow-auto bg-background">
|
||||||
<div className="sticky top-0 flex items-center justify-between bg-zinc-100 px-4 py-2">
|
<div className="sticky top-0 flex items-center justify-between bg-zinc-100 px-4 py-2 dark:bg-zinc-600">
|
||||||
<h2 className="font-bold">Server Logs</h2>
|
<h2 className="font-bold">Server Logs</h2>
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
themes="outline"
|
themes="outline"
|
||||||
className="bg-white"
|
className="bg-white dark:bg-secondary"
|
||||||
onClick={() => openServerLog()}
|
onClick={() => openServerLog()}
|
||||||
>
|
>
|
||||||
<CodeIcon size={16} className="mr-2" />
|
<CodeIcon size={16} className="mr-2" />
|
||||||
@ -324,7 +328,7 @@ const LocalServerScreen = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
themes="outline"
|
themes="outline"
|
||||||
className="bg-white"
|
className="bg-white dark:bg-secondary"
|
||||||
onClick={() => clearServerLog()}
|
onClick={() => clearServerLog()}
|
||||||
>
|
>
|
||||||
<Paintbrush size={16} className="mr-2" />
|
<Paintbrush size={16} className="mr-2" />
|
||||||
@ -380,7 +384,7 @@ const LocalServerScreen = () => {
|
|||||||
{/* Right bar */}
|
{/* Right bar */}
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'h-full flex-shrink-0 overflow-x-hidden border-l border-border bg-background transition-all duration-100',
|
'h-full flex-shrink-0 overflow-x-hidden border-l border-border bg-background transition-all duration-100 dark:bg-background/20',
|
||||||
showRightSideBar
|
showRightSideBar
|
||||||
? 'w-80 translate-x-0 opacity-100'
|
? 'w-80 translate-x-0 opacity-100'
|
||||||
: 'w-0 translate-x-full opacity-0'
|
: 'w-0 translate-x-full opacity-0'
|
||||||
@ -416,7 +420,7 @@ const LocalServerScreen = () => {
|
|||||||
<span>
|
<span>
|
||||||
Model failed to start. Access{' '}
|
Model failed to start. Access{' '}
|
||||||
<span
|
<span
|
||||||
className="cursor-pointer text-blue-600"
|
className="cursor-pointer text-primary dark:text-blue-400"
|
||||||
onClick={() => setModalTroubleShooting(true)}
|
onClick={() => setModalTroubleShooting(true)}
|
||||||
>
|
>
|
||||||
troubleshooting assistance
|
troubleshooting assistance
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import {
|
import { useEffect, useState, useCallback, ChangeEvent } from 'react'
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
useCallback,
|
|
||||||
ChangeEvent,
|
|
||||||
} from 'react'
|
|
||||||
|
|
||||||
import { openExternalUrl, fs } from '@janhq/core'
|
import { openExternalUrl, fs } from '@janhq/core'
|
||||||
|
|
||||||
@ -29,20 +23,27 @@ import {
|
|||||||
ScrollArea,
|
ScrollArea,
|
||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
import { AlertTriangleIcon, AlertCircleIcon } from 'lucide-react'
|
import { AlertTriangleIcon, AlertCircleIcon } from 'lucide-react'
|
||||||
|
|
||||||
import ShortcutModal from '@/containers/ShortcutModal'
|
import ShortcutModal from '@/containers/ShortcutModal'
|
||||||
|
|
||||||
import { snackbar, toaster } from '@/containers/Toast'
|
import { snackbar, toaster } from '@/containers/Toast'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
import { useSettings } from '@/hooks/useSettings'
|
import { useSettings } from '@/hooks/useSettings'
|
||||||
|
|
||||||
import DataFolder from './DataFolder'
|
import DataFolder from './DataFolder'
|
||||||
import FactoryReset from './FactoryReset'
|
import FactoryReset from './FactoryReset'
|
||||||
|
|
||||||
|
import {
|
||||||
|
experimentalFeatureEnabledAtom,
|
||||||
|
ignoreSslAtom,
|
||||||
|
proxyAtom,
|
||||||
|
proxyEnabledAtom,
|
||||||
|
vulkanEnabledAtom,
|
||||||
|
} from '@/helpers/atoms/AppConfig.atom'
|
||||||
|
|
||||||
type GPU = {
|
type GPU = {
|
||||||
id: string
|
id: string
|
||||||
vram: number | null
|
vram: number | null
|
||||||
@ -50,22 +51,19 @@ type GPU = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Advanced = () => {
|
const Advanced = () => {
|
||||||
const {
|
const [experimentalEnabled, setExperimentalEnabled] = useAtom(
|
||||||
experimentalFeature,
|
experimentalFeatureEnabledAtom
|
||||||
setExperimentalFeature,
|
)
|
||||||
ignoreSSL,
|
const [vulkanEnabled, setVulkanEnabled] = useAtom(vulkanEnabledAtom)
|
||||||
setIgnoreSSL,
|
const [proxyEnabled, setProxyEnabled] = useAtom(proxyEnabledAtom)
|
||||||
proxy,
|
const [proxy, setProxy] = useAtom(proxyAtom)
|
||||||
setProxy,
|
const [ignoreSSL, setIgnoreSSL] = useAtom(ignoreSslAtom)
|
||||||
proxyEnabled,
|
|
||||||
setProxyEnabled,
|
|
||||||
vulkanEnabled,
|
|
||||||
setVulkanEnabled,
|
|
||||||
} = useContext(FeatureToggleContext)
|
|
||||||
const [partialProxy, setPartialProxy] = useState<string>(proxy)
|
const [partialProxy, setPartialProxy] = useState<string>(proxy)
|
||||||
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
|
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
|
||||||
const [gpuList, setGpuList] = useState<GPU[]>([])
|
const [gpuList, setGpuList] = useState<GPU[]>([])
|
||||||
const [gpusInUse, setGpusInUse] = useState<string[]>([])
|
const [gpusInUse, setGpusInUse] = useState<string[]>([])
|
||||||
|
|
||||||
const { readSettings, saveSettings, validateSettings, setShowNotification } =
|
const { readSettings, saveSettings, validateSettings, setShowNotification } =
|
||||||
useSettings()
|
useSettings()
|
||||||
const { stopModel } = useActiveModel()
|
const { stopModel } = useActiveModel()
|
||||||
@ -169,8 +167,8 @@ const Advanced = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
checked={experimentalFeature}
|
checked={experimentalEnabled}
|
||||||
onCheckedChange={setExperimentalFeature}
|
onCheckedChange={setExperimentalEnabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -282,7 +280,7 @@ const Advanced = () => {
|
|||||||
disabled={gpuList.length === 0 || !gpuEnabled}
|
disabled={gpuList.length === 0 || !gpuEnabled}
|
||||||
value={selectedGpu.join()}
|
value={selectedGpu.join()}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[340px] bg-white">
|
<SelectTrigger className="w-[340px] bg-white dark:bg-gray-500">
|
||||||
<SelectValue placeholder={gpuSelectionPlaceHolder}>
|
<SelectValue placeholder={gpuSelectionPlaceHolder}>
|
||||||
<span className="line-clamp-1 w-full pr-8">
|
<span className="line-clamp-1 w-full pr-8">
|
||||||
{selectedGpu.join()}
|
{selectedGpu.join()}
|
||||||
@ -355,7 +353,7 @@ const Advanced = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Vulkan for AMD GPU/ APU and Intel Arc GPU */}
|
{/* Vulkan for AMD GPU/ APU and Intel Arc GPU */}
|
||||||
{!isMac && experimentalFeature && (
|
{!isMac && experimentalEnabled && (
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
<div className="flex-shrink-0 space-y-1.5">
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
|
|||||||
57
web/screens/Settings/Appearance/TogglePrimary.tsx
Normal file
57
web/screens/Settings/Appearance/TogglePrimary.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { motion as m } from 'framer-motion'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import { useUserConfigs } from '@/hooks/useUserConfigs'
|
||||||
|
|
||||||
|
type PrimaryColorOption = {
|
||||||
|
value: PrimaryColor
|
||||||
|
class: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryColorOptions: PrimaryColorOption[] = [
|
||||||
|
{
|
||||||
|
value: 'primary-blue',
|
||||||
|
class: 'bg-blue-500',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'primary-purple',
|
||||||
|
class: 'bg-purple-500',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'primary-green',
|
||||||
|
class: 'bg-green-500',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function TogglePrimary() {
|
||||||
|
const [config, setUserConfig] = useUserConfigs()
|
||||||
|
|
||||||
|
const handleChangeAccent = (primaryColor: PrimaryColor) => {
|
||||||
|
setUserConfig({ ...config, primaryColor })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center">
|
||||||
|
{primaryColorOptions.map((option, i) => {
|
||||||
|
const isActive = config.primaryColor === option.value
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative flex h-6 w-6 items-center justify-center"
|
||||||
|
key={i}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={twMerge('h-3.5 w-3.5 rounded-full', option.class)}
|
||||||
|
onClick={() => handleChangeAccent(option.value)}
|
||||||
|
/>
|
||||||
|
{isActive ? (
|
||||||
|
<m.div
|
||||||
|
className="absolute inset-0 h-full w-full rounded-full border border-primary/50 bg-primary/20"
|
||||||
|
layoutId="active-primary-menu"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -28,7 +28,7 @@ export default function ToggleTheme() {
|
|||||||
</button>
|
</button>
|
||||||
{isActive ? (
|
{isActive ? (
|
||||||
<m.div
|
<m.div
|
||||||
className="absolute inset-0 h-full w-full rounded-md border border-primary/50 bg-blue-500/20"
|
className="absolute inset-0 h-full w-full rounded-md border border-primary/50 bg-primary/20"
|
||||||
layoutId="active-theme-menu"
|
layoutId="active-theme-menu"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import ToggleAccent from '@/screens/Settings/Appearance/TogglePrimary'
|
||||||
import ToggleTheme from '@/screens/Settings/Appearance/ToggleTheme'
|
import ToggleTheme from '@/screens/Settings/Appearance/ToggleTheme'
|
||||||
|
|
||||||
export default function AppearanceOptions() {
|
export default function AppearanceOptions() {
|
||||||
@ -21,6 +22,7 @@ export default function AppearanceOptions() {
|
|||||||
Choose the primary accent color used throughout the app.
|
Choose the primary accent color used throughout the app.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<ToggleAccent />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -116,6 +116,11 @@ const EditModelInfoModal: React.FC = () => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onTagsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const tags = e.target.value.split(',')
|
||||||
|
setTags(tags)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={importModelStage === 'EDIT_MODEL_INFO'}
|
open={importModelStage === 'EDIT_MODEL_INFO'}
|
||||||
@ -126,24 +131,26 @@ const EditModelInfoModal: React.FC = () => {
|
|||||||
<ModalTitle>Edit Model Information</ModalTitle>
|
<ModalTitle>Edit Model Information</ModalTitle>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<div className="flex flex-row space-x-4 rounded-xl border p-4">
|
<div className="flex flex-row space-x-4 rounded-xl border border-border p-4">
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-400">
|
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-400">
|
||||||
<Paperclip />
|
<Paperclip color="#fff" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-1 flex-col">
|
||||||
<p>{editingModel.name}</p>
|
<p>{editingModel.name}</p>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<span className="mr-2 text-sm text-[#71717A]">
|
<span className="mr-2 text-sm text-[#71717A]">
|
||||||
{toGibibytes(editingModel.size)}
|
{toGibibytes(editingModel.size)}
|
||||||
</span>
|
</span>
|
||||||
|
<div className="flex flex-row space-x-1">
|
||||||
<span className="text-sm font-semibold text-[#71717A]">
|
<span className="text-sm font-semibold text-[#71717A]">
|
||||||
Format:{' '}
|
Format:
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm font-normal text-[#71717A]">
|
<span className="text-sm font-normal text-[#71717A]">
|
||||||
{editingModel.format.toUpperCase()}
|
{editingModel.format.toUpperCase()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="mt-1 flex flex-row items-center space-x-2">
|
<div className="mt-1 flex flex-row items-center space-x-2">
|
||||||
<span className="line-clamp-1 text-xs font-normal text-[#71717A]">
|
<span className="line-clamp-1 text-xs font-normal text-[#71717A]">
|
||||||
{modelPath}
|
{modelPath}
|
||||||
@ -189,7 +196,7 @@ const EditModelInfoModal: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<label className="mb-1">Tags</label>
|
<label className="mb-1">Tags</label>
|
||||||
<Input />
|
<Input value={tags.join(',')} onChange={onTagsChange} />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const ImportModelOptionSelection: React.FC<Props> = ({
|
|||||||
onClick={() => setSelectedOptionType(option.type)}
|
onClick={() => setSelectedOptionType(option.type)}
|
||||||
>
|
>
|
||||||
<div className="flex h-5 w-5 items-center justify-center rounded-full border border-[#2563EB]">
|
<div className="flex h-5 w-5 items-center justify-center rounded-full border border-[#2563EB]">
|
||||||
{checked && <div className="h-2 w-2 rounded-full bg-blue-500" />}
|
{checked && <div className="h-2 w-2 rounded-full bg-primary" />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ml-2 flex-1">
|
<div className="ml-2 flex-1">
|
||||||
|
|||||||
@ -29,8 +29,8 @@ const ImportSuccessIcon: React.FC<Props> = ({ onEditModelClick }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SuccessIcon: React.FC = React.memo(() => (
|
const SuccessIcon: React.FC = React.memo(() => (
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-500">
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-white">
|
||||||
<Check color="#FFF" />
|
<Check size={20} />
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -41,10 +41,10 @@ const EditIcon: React.FC<Props> = React.memo(({ onEditModelClick }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-gray-100"
|
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-secondary"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Pencil />
|
<Pencil size={20} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -37,11 +37,11 @@ const ImportingModelItem: React.FC<Props> = ({ model }) => {
|
|||||||
}, [model.status, model.size])
|
}, [model.status, model.size])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-row items-center space-x-3 rounded-lg border px-4 py-3">
|
<div className="flex w-full flex-row items-center space-x-3 rounded-lg border border-border px-4 py-3">
|
||||||
<p className="line-clamp-1 flex-1 font-semibold text-[#09090B]">
|
<p className="line-clamp-1 flex-1 font-semibold text-muted-foreground">
|
||||||
{model.name}
|
{model.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[#71717A]">{displayStatus}</p>
|
<p className="text-muted-foreground">{displayStatus}</p>
|
||||||
|
|
||||||
{model.status === 'IMPORTED' && (
|
{model.status === 'IMPORTED' && (
|
||||||
<ImportSuccessIcon onEditModelClick={onEditModelInfoClick} />
|
<ImportSuccessIcon onEditModelClick={onEditModelInfoClick} />
|
||||||
|
|||||||
@ -62,7 +62,9 @@ const ImportingModelModal: React.FC = () => {
|
|||||||
Importing model ({finishedImportModel}/{importingModels.length})
|
Importing model ({finishedImportModel}/{importingModels.length})
|
||||||
</ModalTitle>
|
</ModalTitle>
|
||||||
<div className="flex flex-row items-center space-x-2">
|
<div className="flex flex-row items-center space-x-2">
|
||||||
<label className="text-xs text-[#71717A]">{modelFolder}</label>
|
<label className="text-xs text-muted-foreground">
|
||||||
|
{modelFolder}
|
||||||
|
</label>
|
||||||
<Button
|
<Button
|
||||||
themes="ghost"
|
themes="ghost"
|
||||||
className="text-blue-500"
|
className="text-blue-500"
|
||||||
@ -78,9 +80,9 @@ const ImportingModelModal: React.FC = () => {
|
|||||||
<ImportingModelItem key={model.importId} model={model} />
|
<ImportingModelItem key={model.importId} model={model} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<ModalFooter className="mx-[-16px] mb-[-16px] flex flex-row rounded-b-lg bg-[#F4F4F5] px-2 py-2 ">
|
<ModalFooter className="mx-[-16px] mb-[-16px] flex flex-row rounded-b-lg bg-[#F4F4F5] px-2 py-2 dark:bg-secondary ">
|
||||||
<AlertCircle size={20} />
|
<AlertCircle size={20} />
|
||||||
<p className="text-sm font-semibold text-[#71717A]">
|
<p className="text-sm font-semibold text-muted-foreground">
|
||||||
Own your model configurations, use at your own risk.
|
Own your model configurations, use at your own risk.
|
||||||
Misconfigurations may result in lower quality or unexpected outputs.{' '}
|
Misconfigurations may result in lower quality or unexpected outputs.{' '}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -152,7 +152,7 @@ export default function RowModel(props: RowModelProps) {
|
|||||||
) : (
|
) : (
|
||||||
<PlayIcon size={16} className="text-muted-foreground" />
|
<PlayIcon size={16} className="text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
<span className="text-bold capitalize text-black">
|
<span className="text-bold capitalize text-black dark:text-muted-foreground">
|
||||||
{isActiveModel ? stateModel.state : 'Start'}
|
{isActiveModel ? stateModel.state : 'Start'}
|
||||||
Model
|
Model
|
||||||
</span>
|
</span>
|
||||||
@ -189,7 +189,9 @@ export default function RowModel(props: RowModelProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2Icon size={16} className="text-muted-foreground" />
|
<Trash2Icon size={16} className="text-muted-foreground" />
|
||||||
<span className="text-bold text-black">Delete Model</span>
|
<span className="text-bold text-black dark:text-muted-foreground">
|
||||||
|
Delete Model
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useDropzone } from 'react-dropzone'
|
import { useDropzone } from 'react-dropzone'
|
||||||
|
|
||||||
import { ImportingModel, baseName, fs } from '@janhq/core'
|
import { ImportingModel, baseName, fs, joinPath } from '@janhq/core'
|
||||||
import { Modal, ModalContent, ModalHeader, ModalTitle } from '@janhq/uikit'
|
import { Modal, ModalContent, ModalHeader, ModalTitle } from '@janhq/uikit'
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
@ -34,14 +34,31 @@ const SelectingModelModal: React.FC = () => {
|
|||||||
const sanitizedFilePaths: FilePathWithSize[] = []
|
const sanitizedFilePaths: FilePathWithSize[] = []
|
||||||
for (const filePath of filePaths) {
|
for (const filePath of filePaths) {
|
||||||
const fileStats = await fs.fileStat(filePath, true)
|
const fileStats = await fs.fileStat(filePath, true)
|
||||||
if (!fileStats || fileStats.isDirectory) continue
|
if (!fileStats) continue
|
||||||
|
|
||||||
|
if (!fileStats.isDirectory) {
|
||||||
const fileName = await baseName(filePath)
|
const fileName = await baseName(filePath)
|
||||||
sanitizedFilePaths.push({
|
sanitizedFilePaths.push({
|
||||||
path: filePath,
|
path: filePath,
|
||||||
name: fileName,
|
name: fileName,
|
||||||
size: fileStats.size,
|
size: fileStats.size,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// allowing only one level of directory
|
||||||
|
const files = await fs.readdirSync(filePath)
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const fullPath = await joinPath([filePath, file])
|
||||||
|
const fileStats = await fs.fileStat(fullPath, true)
|
||||||
|
if (!fileStats || fileStats.isDirectory) continue
|
||||||
|
|
||||||
|
sanitizedFilePaths.push({
|
||||||
|
path: fullPath,
|
||||||
|
name: file,
|
||||||
|
size: fileStats.size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsupportedFiles = sanitizedFilePaths.filter(
|
const unsupportedFiles = sanitizedFilePaths.filter(
|
||||||
@ -68,7 +85,7 @@ const SelectingModelModal: React.FC = () => {
|
|||||||
)
|
)
|
||||||
if (unsupportedFiles.length > 0) {
|
if (unsupportedFiles.length > 0) {
|
||||||
snackbar({
|
snackbar({
|
||||||
description: `File has to be a .gguf file`,
|
description: `Only files with .gguf extension can be imported.`,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -84,9 +101,11 @@ const SelectingModelModal: React.FC = () => {
|
|||||||
onDrop: onDropModels,
|
onDrop: onDropModels,
|
||||||
})
|
})
|
||||||
|
|
||||||
const borderColor = isDragActive ? 'border-primary' : 'border-[#F4F4F5]'
|
const borderColor = isDragActive ? 'border-primary' : 'border-border'
|
||||||
const textColor = isDragActive ? 'text-blue-600' : 'text-[#71717A]'
|
const textColor = isDragActive ? 'text-primary' : 'text-muted-foreground'
|
||||||
const dragAndDropBgColor = isDragActive ? 'bg-[#EFF6FF]' : 'bg-white'
|
const dragAndDropBgColor = isDragActive
|
||||||
|
? 'bg-[#EFF6FF] dark:bg-blue-50/10'
|
||||||
|
: 'bg-background'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -99,7 +118,7 @@ const SelectingModelModal: React.FC = () => {
|
|||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<ModalTitle>Import Model</ModalTitle>
|
<ModalTitle>Import Model</ModalTitle>
|
||||||
|
|
||||||
<p className="text-sm font-medium text-[#71717A]">
|
<p className="text-sm font-medium text-muted-foreground">
|
||||||
Import any model file (GGUF) or folder. Your imported model will be
|
Import any model file (GGUF) or folder. Your imported model will be
|
||||||
private to you.
|
private to you.
|
||||||
</p>
|
</p>
|
||||||
@ -116,7 +135,7 @@ const SelectingModelModal: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<span className="text-sm font-bold text-blue-600">
|
<span className="text-sm font-bold text-primary">
|
||||||
Click to upload
|
Click to upload
|
||||||
</span>
|
</span>
|
||||||
<span className={`text-sm ${textColor} font-medium`}>
|
<span className={`text-sm ${textColor} font-medium`}>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const SettingMenu: React.FC<Props> = ({ activeMenu, onMenuClick }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMenus([
|
setMenus([
|
||||||
'My Models',
|
'My Models',
|
||||||
|
'My Settings',
|
||||||
'Advanced Settings',
|
'Advanced Settings',
|
||||||
...(window.electronAPI ? ['Extensions'] : []),
|
...(window.electronAPI ? ['Extensions'] : []),
|
||||||
])
|
])
|
||||||
@ -38,7 +39,7 @@ const SettingMenu: React.FC<Props> = ({ activeMenu, onMenuClick }) => {
|
|||||||
|
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<m.div
|
<m.div
|
||||||
className="absolute inset-0 -left-3 h-full w-[calc(100%+24px)] rounded-md bg-gray-200"
|
className="absolute inset-0 -left-3 h-full w-[calc(100%+24px)] rounded-md bg-primary/50"
|
||||||
layoutId="active-static-menu"
|
layoutId="active-static-menu"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import Advanced from '@/screens/Settings/Advanced'
|
import Advanced from '@/screens/Settings/Advanced'
|
||||||
|
import AppearanceOptions from '@/screens/Settings/Appearance'
|
||||||
import ExtensionCatalog from '@/screens/Settings/CoreExtensions'
|
import ExtensionCatalog from '@/screens/Settings/CoreExtensions'
|
||||||
|
|
||||||
import Models from '@/screens/Settings/Models'
|
import Models from '@/screens/Settings/Models'
|
||||||
@ -14,6 +14,9 @@ const handleShowOptions = (menu: string) => {
|
|||||||
case 'Extensions':
|
case 'Extensions':
|
||||||
return <ExtensionCatalog />
|
return <ExtensionCatalog />
|
||||||
|
|
||||||
|
case 'My Settings':
|
||||||
|
return <AppearanceOptions />
|
||||||
|
|
||||||
case 'Advanced Settings':
|
case 'Advanced Settings':
|
||||||
return <Advanced />
|
return <Advanced />
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
.message {
|
.message {
|
||||||
@apply text-black;
|
@apply text-black dark:text-gray-300;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
@ -10,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@apply text-blue-600;
|
@apply text-blue-600 dark:text-blue-300;
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply underline;
|
@apply underline;
|
||||||
}
|
}
|
||||||
|
|||||||
6
web/types/appearance.d.ts
vendored
Normal file
6
web/types/appearance.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
type PrimaryColor = 'primary-blue' | 'primary-green' | 'primary-purple'
|
||||||
|
|
||||||
|
type UserConfig = {
|
||||||
|
gettingStartedShow?: boolean
|
||||||
|
primaryColor?: PrimaryColor
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user