llms-py 2.0.24__py3-none-any.whl → 2.0.26__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llms/llms.json +10 -1
- llms/main.py +361 -87
- llms/ui/App.mjs +7 -2
- llms/ui/Avatar.mjs +61 -4
- llms/ui/Main.mjs +8 -5
- llms/ui/OAuthSignIn.mjs +92 -0
- llms/ui/ai.mjs +68 -5
- llms/ui/app.css +36 -0
- {llms_py-2.0.24.dist-info → llms_py-2.0.26.dist-info}/METADATA +343 -42
- {llms_py-2.0.24.dist-info → llms_py-2.0.26.dist-info}/RECORD +14 -13
- {llms_py-2.0.24.dist-info → llms_py-2.0.26.dist-info}/licenses/LICENSE +1 -2
- {llms_py-2.0.24.dist-info → llms_py-2.0.26.dist-info}/WHEEL +0 -0
- {llms_py-2.0.24.dist-info → llms_py-2.0.26.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.24.dist-info → llms_py-2.0.26.dist-info}/top_level.txt +0 -0
llms/ui/Avatar.mjs
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
|
-
import { computed, inject } from "vue"
|
|
1
|
+
import { computed, inject, ref, onMounted, onUnmounted } from "vue"
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
4
|
template:`
|
|
5
|
-
<div v-if="$ai.auth?.profileUrl"
|
|
6
|
-
<img
|
|
5
|
+
<div v-if="$ai.auth?.profileUrl" class="relative" ref="avatarContainer">
|
|
6
|
+
<img
|
|
7
|
+
@click.stop="toggleMenu"
|
|
8
|
+
:src="$ai.auth.profileUrl"
|
|
9
|
+
:title="authTitle"
|
|
10
|
+
class="size-8 rounded-full cursor-pointer hover:ring-2 hover:ring-gray-300"
|
|
11
|
+
/>
|
|
12
|
+
<div
|
|
13
|
+
v-if="showMenu"
|
|
14
|
+
@click.stop
|
|
15
|
+
class="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg py-1 z-50 border border-gray-200 dark:border-gray-700"
|
|
16
|
+
>
|
|
17
|
+
<div class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 border-b border-gray-200 dark:border-gray-700">
|
|
18
|
+
<div class="font-medium whitespace-nowrap overflow-hidden text-ellipsis">{{ $ai.auth.displayName || $ai.auth.userName }}</div>
|
|
19
|
+
<div class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap overflow-hidden text-ellipsis">{{ $ai.auth.email }}</div>
|
|
20
|
+
</div>
|
|
21
|
+
<button type="button"
|
|
22
|
+
@click="handleLogout"
|
|
23
|
+
class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center whitespace-nowrap"
|
|
24
|
+
>
|
|
25
|
+
<svg class="w-4 h-4 mr-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
26
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
|
|
27
|
+
</svg>
|
|
28
|
+
Sign Out
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
7
31
|
</div>
|
|
8
32
|
`,
|
|
9
33
|
setup() {
|
|
10
34
|
const ai = inject('ai')
|
|
35
|
+
const showMenu = ref(false)
|
|
36
|
+
const avatarContainer = ref(null)
|
|
37
|
+
|
|
11
38
|
const authTitle = computed(() => {
|
|
12
39
|
if (!ai.auth) return ''
|
|
13
40
|
const { userId, userName, displayName, bearerToken, roles } = ai.auth
|
|
@@ -20,9 +47,39 @@ export default {
|
|
|
20
47
|
]
|
|
21
48
|
return sb.filter(x => x).join('\n')
|
|
22
49
|
})
|
|
23
|
-
|
|
50
|
+
|
|
51
|
+
function toggleMenu() {
|
|
52
|
+
showMenu.value = !showMenu.value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function handleLogout() {
|
|
56
|
+
showMenu.value = false
|
|
57
|
+
await ai.signOut()
|
|
58
|
+
// Reload the page to show sign-in screen
|
|
59
|
+
window.location.reload()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Close menu when clicking outside
|
|
63
|
+
const handleClickOutside = (event) => {
|
|
64
|
+
if (showMenu.value && avatarContainer.value && !avatarContainer.value.contains(event.target)) {
|
|
65
|
+
showMenu.value = false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onMounted(() => {
|
|
70
|
+
document.addEventListener('click', handleClickOutside)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
onUnmounted(() => {
|
|
74
|
+
document.removeEventListener('click', handleClickOutside)
|
|
75
|
+
})
|
|
76
|
+
|
|
24
77
|
return {
|
|
25
78
|
authTitle,
|
|
79
|
+
handleLogout,
|
|
80
|
+
showMenu,
|
|
81
|
+
toggleMenu,
|
|
82
|
+
avatarContainer,
|
|
26
83
|
}
|
|
27
84
|
}
|
|
28
85
|
}
|
llms/ui/Main.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { storageObject, addCopyButtons, formatCost, statsTitle } from './utils.m
|
|
|
6
6
|
import { renderMarkdown } from './markdown.mjs'
|
|
7
7
|
import ChatPrompt, { useChatPrompt } from './ChatPrompt.mjs'
|
|
8
8
|
import SignIn from './SignIn.mjs'
|
|
9
|
+
import OAuthSignIn from './OAuthSignIn.mjs'
|
|
9
10
|
import Avatar from './Avatar.mjs'
|
|
10
11
|
import ModelSelector from './ModelSelector.mjs'
|
|
11
12
|
import SystemPromptSelector from './SystemPromptSelector.mjs'
|
|
@@ -22,32 +23,34 @@ export default {
|
|
|
22
23
|
SystemPromptEditor,
|
|
23
24
|
ChatPrompt,
|
|
24
25
|
SignIn,
|
|
26
|
+
OAuthSignIn,
|
|
25
27
|
Avatar,
|
|
26
28
|
Welcome,
|
|
27
29
|
},
|
|
28
30
|
template: `
|
|
29
31
|
<div class="flex flex-col h-full w-full">
|
|
30
|
-
<!-- Header with model and prompt selectors -->
|
|
31
|
-
<div class="border-b border-gray-200 bg-white px-2 py-2 w-full min-h-16">
|
|
32
|
+
<!-- Header with model and prompt selectors (hidden when auth required and not authenticated) -->
|
|
33
|
+
<div v-if="!($ai.requiresAuth && !$ai.auth)" class="border-b border-gray-200 bg-white px-2 py-2 w-full min-h-16">
|
|
32
34
|
<div class="flex items-center justify-between w-full">
|
|
33
35
|
<ModelSelector :models="models" v-model="selectedModel" @updated="configUpdated" />
|
|
34
36
|
|
|
35
37
|
<div class="flex items-center space-x-2">
|
|
36
|
-
<SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
|
|
38
|
+
<SystemPromptSelector :prompts="prompts" v-model="selectedPrompt"
|
|
37
39
|
:show="showSystemPrompt" @toggle="showSystemPrompt = !showSystemPrompt" />
|
|
38
40
|
<Avatar />
|
|
39
41
|
</div>
|
|
40
42
|
</div>
|
|
41
43
|
</div>
|
|
42
44
|
|
|
43
|
-
<SystemPromptEditor v-if="showSystemPrompt"
|
|
45
|
+
<SystemPromptEditor v-if="showSystemPrompt && !($ai.requiresAuth && !$ai.auth)"
|
|
44
46
|
v-model="currentSystemPrompt" :prompts="prompts" :selected="selectedPrompt" />
|
|
45
47
|
|
|
46
48
|
<!-- Messages Area -->
|
|
47
49
|
<div class="flex-1 overflow-y-auto" ref="messagesContainer">
|
|
48
50
|
<div class="mx-auto max-w-6xl px-4 py-6">
|
|
49
51
|
<div v-if="$ai.requiresAuth && !$ai.auth">
|
|
50
|
-
<
|
|
52
|
+
<OAuthSignIn v-if="$ai.authType === 'oauth'" @done="$ai.signIn($event)" />
|
|
53
|
+
<SignIn v-else @done="$ai.signIn($event)" />
|
|
51
54
|
</div>
|
|
52
55
|
<!-- Welcome message when no thread is selected -->
|
|
53
56
|
<div v-else-if="!currentThread" class="text-center py-12">
|
llms/ui/OAuthSignIn.mjs
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { inject, ref, onMounted } from "vue"
|
|
2
|
+
import Welcome from './Welcome.mjs'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
components: {
|
|
6
|
+
Welcome,
|
|
7
|
+
},
|
|
8
|
+
template: `
|
|
9
|
+
<div class="min-h-full -mt-36 flex flex-col justify-center sm:px-6 lg:px-8">
|
|
10
|
+
<div class="sm:mx-auto sm:w-full sm:max-w-md text-center">
|
|
11
|
+
<Welcome />
|
|
12
|
+
</div>
|
|
13
|
+
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
|
14
|
+
<div v-if="errorMessage" class="mb-3 bg-red-50 border border-red-200 text-red-800 rounded-lg px-4 py-3">
|
|
15
|
+
<div class="flex items-start space-x-2">
|
|
16
|
+
<div class="flex-1">
|
|
17
|
+
<div class="text-base font-medium">{{ errorMessage }}</div>
|
|
18
|
+
</div>
|
|
19
|
+
<button type="button"
|
|
20
|
+
@click="errorMessage = null"
|
|
21
|
+
class="text-red-400 hover:text-red-600 flex-shrink-0"
|
|
22
|
+
>
|
|
23
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
24
|
+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
25
|
+
</svg>
|
|
26
|
+
</button>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="py-8 px-4 sm:px-10">
|
|
30
|
+
<div class="space-y-4">
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
@click="signInWithGitHub"
|
|
34
|
+
class="w-full inline-flex items-center justify-center px-4 py-3 border border-gray-300 rounded-md shadow-sm text-base font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transition-colors"
|
|
35
|
+
>
|
|
36
|
+
<svg class="w-6 h-6 mr-3" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
37
|
+
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
|
|
38
|
+
</svg>
|
|
39
|
+
Sign in with GitHub
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
`,
|
|
46
|
+
emits: ['done'],
|
|
47
|
+
setup(props, { emit }) {
|
|
48
|
+
const ai = inject('ai')
|
|
49
|
+
const errorMessage = ref(null)
|
|
50
|
+
|
|
51
|
+
function signInWithGitHub() {
|
|
52
|
+
// Redirect to GitHub OAuth endpoint
|
|
53
|
+
window.location.href = '/auth/github'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check for session token in URL (after OAuth callback redirect)
|
|
57
|
+
onMounted(async () => {
|
|
58
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
59
|
+
const sessionToken = urlParams.get('session')
|
|
60
|
+
|
|
61
|
+
if (sessionToken) {
|
|
62
|
+
try {
|
|
63
|
+
// Validate session with server
|
|
64
|
+
const response = await ai.get(`/auth/session?session=${sessionToken}`)
|
|
65
|
+
|
|
66
|
+
if (response.ok) {
|
|
67
|
+
const sessionData = await response.json()
|
|
68
|
+
|
|
69
|
+
// Clean up URL
|
|
70
|
+
const url = new URL(window.location.href)
|
|
71
|
+
url.searchParams.delete('session')
|
|
72
|
+
window.history.replaceState({}, '', url.toString())
|
|
73
|
+
|
|
74
|
+
// Emit done event with session data
|
|
75
|
+
emit('done', sessionData)
|
|
76
|
+
} else {
|
|
77
|
+
errorMessage.value = 'Failed to validate session'
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Session validation error:', error)
|
|
81
|
+
errorMessage.value = 'Failed to validate session'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
signInWithGitHub,
|
|
88
|
+
errorMessage,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
llms/ui/ai.mjs
CHANGED
|
@@ -6,12 +6,13 @@ const headers = { 'Accept': 'application/json' }
|
|
|
6
6
|
const prefsKey = 'llms.prefs'
|
|
7
7
|
|
|
8
8
|
export const o = {
|
|
9
|
-
version: '2.0.
|
|
9
|
+
version: '2.0.26',
|
|
10
10
|
base,
|
|
11
11
|
prefsKey,
|
|
12
12
|
welcome: 'Welcome to llms.py',
|
|
13
13
|
auth: null,
|
|
14
14
|
requiresAuth: false,
|
|
15
|
+
authType: 'apikey', // 'oauth' or 'apikey' - controls which SignIn component to use
|
|
15
16
|
headers,
|
|
16
17
|
|
|
17
18
|
resolveUrl(url){
|
|
@@ -50,26 +51,88 @@ export const o = {
|
|
|
50
51
|
this.auth = auth
|
|
51
52
|
if (auth?.apiKey) {
|
|
52
53
|
this.headers.Authorization = `Bearer ${auth.apiKey}`
|
|
53
|
-
|
|
54
|
+
//localStorage.setItem('llms:auth', JSON.stringify({ apiKey: auth.apiKey }))
|
|
55
|
+
} else if (auth?.sessionToken) {
|
|
56
|
+
this.headers['X-Session-Token'] = auth.sessionToken
|
|
57
|
+
localStorage.setItem('llms:auth', JSON.stringify({ sessionToken: auth.sessionToken }))
|
|
58
|
+
} else {
|
|
59
|
+
if (this.headers.Authorization) {
|
|
60
|
+
delete this.headers.Authorization
|
|
61
|
+
}
|
|
62
|
+
if (this.headers['X-Session-Token']) {
|
|
63
|
+
delete this.headers['X-Session-Token']
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
async signOut() {
|
|
68
|
+
if (this.auth?.sessionToken) {
|
|
69
|
+
// Call logout endpoint for OAuth sessions
|
|
70
|
+
try {
|
|
71
|
+
await this.post('/auth/logout', {
|
|
72
|
+
headers: {
|
|
73
|
+
'X-Session-Token': this.auth.sessionToken
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Logout error:', error)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.auth = null
|
|
81
|
+
if (this.headers.Authorization) {
|
|
54
82
|
delete this.headers.Authorization
|
|
55
83
|
}
|
|
84
|
+
if (this.headers['X-Session-Token']) {
|
|
85
|
+
delete this.headers['X-Session-Token']
|
|
86
|
+
}
|
|
87
|
+
localStorage.removeItem('llms:auth')
|
|
56
88
|
},
|
|
57
89
|
async init() {
|
|
58
90
|
// Load models and prompts
|
|
59
91
|
const { initDB } = useThreadStore()
|
|
60
|
-
const [_, configRes, modelsRes
|
|
92
|
+
const [_, configRes, modelsRes] = await Promise.all([
|
|
61
93
|
initDB(),
|
|
62
94
|
this.getConfig(),
|
|
63
95
|
this.getModels(),
|
|
64
|
-
this.getAuth(),
|
|
65
96
|
])
|
|
66
97
|
const config = await configRes.json()
|
|
67
98
|
const models = await modelsRes.json()
|
|
68
|
-
|
|
99
|
+
|
|
100
|
+
// Update auth settings from server config
|
|
101
|
+
if (config.requiresAuth != null) {
|
|
102
|
+
this.requiresAuth = config.requiresAuth
|
|
103
|
+
}
|
|
104
|
+
if (config.authType != null) {
|
|
105
|
+
this.authType = config.authType
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Try to restore session from localStorage
|
|
109
|
+
if (this.requiresAuth) {
|
|
110
|
+
const storedAuth = localStorage.getItem('llms:auth')
|
|
111
|
+
if (storedAuth) {
|
|
112
|
+
try {
|
|
113
|
+
const authData = JSON.parse(storedAuth)
|
|
114
|
+
if (authData.sessionToken) {
|
|
115
|
+
this.headers['X-Session-Token'] = authData.sessionToken
|
|
116
|
+
}
|
|
117
|
+
// else if (authData.apiKey) {
|
|
118
|
+
// this.headers.Authorization = `Bearer ${authData.apiKey}`
|
|
119
|
+
// }
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.error('Failed to restore auth from localStorage:', e)
|
|
122
|
+
localStorage.removeItem('llms:auth')
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Get auth status
|
|
128
|
+
const authRes = await this.getAuth()
|
|
129
|
+
const auth = this.requiresAuth
|
|
69
130
|
? await authRes.json()
|
|
70
131
|
: null
|
|
71
132
|
if (auth?.responseStatus?.errorCode) {
|
|
72
133
|
console.error(auth.responseStatus.errorCode, auth.responseStatus.message)
|
|
134
|
+
// Clear invalid session from localStorage
|
|
135
|
+
localStorage.removeItem('llms:auth')
|
|
73
136
|
} else {
|
|
74
137
|
this.signIn(auth)
|
|
75
138
|
}
|
llms/ui/app.css
CHANGED
|
@@ -426,6 +426,9 @@
|
|
|
426
426
|
.-mt-12 {
|
|
427
427
|
margin-top: calc(var(--spacing) * -12);
|
|
428
428
|
}
|
|
429
|
+
.-mt-36 {
|
|
430
|
+
margin-top: calc(var(--spacing) * -36);
|
|
431
|
+
}
|
|
429
432
|
.mt-1 {
|
|
430
433
|
margin-top: calc(var(--spacing) * 1);
|
|
431
434
|
}
|
|
@@ -662,6 +665,9 @@
|
|
|
662
665
|
.w-32 {
|
|
663
666
|
width: calc(var(--spacing) * 32);
|
|
664
667
|
}
|
|
668
|
+
.w-48 {
|
|
669
|
+
width: calc(var(--spacing) * 48);
|
|
670
|
+
}
|
|
665
671
|
.w-72 {
|
|
666
672
|
width: calc(var(--spacing) * 72);
|
|
667
673
|
}
|
|
@@ -852,6 +858,13 @@
|
|
|
852
858
|
margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
|
|
853
859
|
}
|
|
854
860
|
}
|
|
861
|
+
.space-y-4 {
|
|
862
|
+
:where(& > :not(:last-child)) {
|
|
863
|
+
--tw-space-y-reverse: 0;
|
|
864
|
+
margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
|
|
865
|
+
margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
|
|
866
|
+
}
|
|
867
|
+
}
|
|
855
868
|
.space-y-6 {
|
|
856
869
|
:where(& > :not(:last-child)) {
|
|
857
870
|
--tw-space-y-reverse: 0;
|
|
@@ -1431,6 +1444,9 @@
|
|
|
1431
1444
|
.break-words {
|
|
1432
1445
|
overflow-wrap: break-word;
|
|
1433
1446
|
}
|
|
1447
|
+
.text-ellipsis {
|
|
1448
|
+
text-overflow: ellipsis;
|
|
1449
|
+
}
|
|
1434
1450
|
.whitespace-nowrap {
|
|
1435
1451
|
white-space: nowrap;
|
|
1436
1452
|
}
|
|
@@ -2214,6 +2230,21 @@
|
|
|
2214
2230
|
}
|
|
2215
2231
|
}
|
|
2216
2232
|
}
|
|
2233
|
+
.hover\:ring-2 {
|
|
2234
|
+
&:hover {
|
|
2235
|
+
@media (hover: hover) {
|
|
2236
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, hsl(var(--ring)));
|
|
2237
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
.hover\:ring-gray-300 {
|
|
2242
|
+
&:hover {
|
|
2243
|
+
@media (hover: hover) {
|
|
2244
|
+
--tw-ring-color: var(--color-gray-300);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2217
2248
|
.hover\:file\:bg-violet-100 {
|
|
2218
2249
|
&:hover {
|
|
2219
2250
|
@media (hover: hover) {
|
|
@@ -2292,6 +2323,11 @@
|
|
|
2292
2323
|
--tw-ring-color: var(--color-cyan-500);
|
|
2293
2324
|
}
|
|
2294
2325
|
}
|
|
2326
|
+
.focus\:ring-gray-500 {
|
|
2327
|
+
&:focus {
|
|
2328
|
+
--tw-ring-color: var(--color-gray-500);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2295
2331
|
.focus\:ring-green-500 {
|
|
2296
2332
|
&:focus {
|
|
2297
2333
|
--tw-ring-color: var(--color-green-500);
|