Merge and resolve conflict
This commit is contained in:
commit
b98e2b8c16
@ -14,9 +14,11 @@
|
||||
"axios": "^1.4.0",
|
||||
"bootstrap": "^5.3.1",
|
||||
"buffer": "^6.0.3",
|
||||
"echarts": "^5.5.1",
|
||||
"pdfjs-dist": "^4.3.136",
|
||||
"pinia": "^2.1.6",
|
||||
"vue": "^3.3.4",
|
||||
"vue-echarts": "^6.7.3",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.2.4",
|
||||
"vuex": "^4.1.0"
|
||||
|
||||
@ -11,88 +11,104 @@
|
||||
@mouseup.stop="selectionEnd"
|
||||
/>
|
||||
<div v-if="!disabled" class="editor-control" :style="editorCtrlStyle">
|
||||
<div v-for="(item, index) in iconList" :key="index" class="editor-item">
|
||||
<button
|
||||
class="item-icon"
|
||||
:class="{
|
||||
activity: commandStates.indexOf(item.type) !== -1,
|
||||
last: index === iconList.length - 1
|
||||
}"
|
||||
:data-info="item.name"
|
||||
@click="iconClick($event, item.type)"
|
||||
:data-bs-toggle="item.drop ? 'dropdown' : ''"
|
||||
data-bs-auto-close="outside"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg-icon :icon="item.icon" class-name="icon" />
|
||||
</button>
|
||||
<div class="dropmenu drop-style" v-if="item.type === 'style'">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'p', 'style')"><p>Text</p></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'pre', 'style')"><pre>code</pre></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'blockquote', 'style')"
|
||||
><blockquote>引用</blockquote></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h1', 'style')"><h1>Caption 1</h1></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h2', 'style')"><h2>Caption 2</h2></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h3', 'style')"><h3>Caption 3</h3></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h4', 'style')"><h4>Caption 4</h4></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h5', 'style')"><h5>Caption 5</h5></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h6', 'style')"><h6>Caption 6</h6></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dropmenu drop-style" v-if="item.type === 'alignjustify'">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyCenter', 'alignjustify')">
|
||||
<i class="iconfont icon-aligncenter"></i>
|
||||
<span>middle</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyLeft', 'alignjustify')">
|
||||
<i class="iconfont icon-alignleft"></i>
|
||||
<span>left aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyRight', 'alignjustify')">
|
||||
<i class="iconfont icon-alignright"></i>
|
||||
<span>right aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyFull', 'alignjustify')">
|
||||
<i class="iconfont icon-alignjustify"></i>
|
||||
<span>default aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<transition-group appear name="fade-transform" mode="out-in">
|
||||
<template v-if="!inputing">
|
||||
<div v-for="(item, index) in iconList" :key="index" class="editor-item">
|
||||
<button
|
||||
class="item-icon"
|
||||
:class="{
|
||||
activity: commandStates.indexOf(item.type) !== -1,
|
||||
last: index === iconList.length - 1
|
||||
}"
|
||||
:data-info="item.name"
|
||||
@click="iconClick($event, item.type)"
|
||||
:data-bs-toggle="item.drop ? 'dropdown' : ''"
|
||||
data-bs-auto-close="outside"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg-icon :icon="item.icon" class-name="icon" />
|
||||
</button>
|
||||
<div class="dropmenu drop-style" v-if="item.type === 'style'">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'p', 'style')"><p>Text</p></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'pre', 'style')"><pre>code</pre></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'blockquote', 'style')"
|
||||
><blockquote>引用</blockquote></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h1', 'style')"><h1>Caption 1</h1></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h2', 'style')"><h2>Caption 2</h2></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h3', 'style')"><h3>Caption 3</h3></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h4', 'style')"><h4>Caption 4</h4></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h5', 'style')"><h5>Caption 5</h5></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h6', 'style')"><h6>Caption 6</h6></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dropmenu drop-style" v-if="item.type === 'alignjustify'">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyCenter', 'alignjustify')">
|
||||
<i class="iconfont icon-aligncenter"></i>
|
||||
<span>middle</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyLeft', 'alignjustify')">
|
||||
<i class="iconfont icon-alignleft"></i>
|
||||
<span>left aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyRight', 'alignjustify')">
|
||||
<i class="iconfont icon-alignright"></i>
|
||||
<span>right aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyFull', 'alignjustify')">
|
||||
<i class="iconfont icon-alignjustify"></i>
|
||||
<span>default aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="inputing">
|
||||
<div class="editor-item">
|
||||
<button class="item-icon item-icon-back" @click="backAction" data-info="back">
|
||||
<svg-icon icon="fe-back" class-name="icon" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="editor-item">
|
||||
<input type="text" v-model="linkUrl" autofocus @keydown.enter="inputAction" />
|
||||
<svg-icon icon="msg-enter" class-name="item-enter-icon" />
|
||||
</div>
|
||||
</template>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import { websiteValidator } from '@/utils/validator'
|
||||
export default {
|
||||
name: 'FreeleapsEditor',
|
||||
props: {
|
||||
@ -112,6 +128,8 @@ export default {
|
||||
selectedRange: '',
|
||||
editorCtrlStyle: {},
|
||||
commandStates: [],
|
||||
inputing: false,
|
||||
linkUrl: '',
|
||||
iconList: [
|
||||
// {
|
||||
// name: 'lable', // hover name
|
||||
@ -175,6 +193,13 @@ export default {
|
||||
icon: 'fe-orderedlist',
|
||||
drop: false,
|
||||
canChoose: true
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
type: 'link',
|
||||
icon: 'fe-link',
|
||||
drop: false,
|
||||
canChoose: true
|
||||
}
|
||||
// {
|
||||
// name: 'align-justify',
|
||||
@ -202,6 +227,8 @@ export default {
|
||||
} else {
|
||||
this.selectedRange = null
|
||||
this.editorCtrlStyle = { display: 'none' }
|
||||
this.inputing = false
|
||||
this.linkUrl = ''
|
||||
// this.commandStates = []
|
||||
}
|
||||
},
|
||||
@ -225,6 +252,10 @@ export default {
|
||||
this.$refs.editor.focus()
|
||||
this.selectedRange = this.getSelect()
|
||||
this.restoreSelection()
|
||||
if (type === 'link') {
|
||||
this.inputing = true
|
||||
return
|
||||
}
|
||||
this.changeStyle(type)
|
||||
this.$nextTick(() => {
|
||||
if (dropType) {
|
||||
@ -238,6 +269,26 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
backAction() {
|
||||
this.inputing = false
|
||||
this.linkUrl = ''
|
||||
this.$refs.editor.focus()
|
||||
this.restoreSelection()
|
||||
},
|
||||
inputAction() {
|
||||
const error = websiteValidator.validate(this.linkUrl)
|
||||
if (error) {
|
||||
alert(error)
|
||||
return
|
||||
}
|
||||
const a_node = document.createElement('a')
|
||||
a_node.setAttribute('href', this.linkUrl)
|
||||
this.selectedRange.surroundContents(a_node)
|
||||
this.backAction()
|
||||
this.$refs.editor.blur()
|
||||
this.linkUrl = ''
|
||||
this.editorCtrlStyle = { display: 'none' }
|
||||
},
|
||||
getSelect() {
|
||||
if (window.getSelection) {
|
||||
let sel = window.getSelection()
|
||||
@ -417,6 +468,30 @@ export default {
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
height: 24px;
|
||||
border: 1px solid #e1e1e1;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
padding-left: 5px;
|
||||
padding-right: 24px;
|
||||
|
||||
&:focus {
|
||||
border-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.item-enter-icon {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 3px;
|
||||
background-color: #e1e1e1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
@ -486,6 +561,9 @@ export default {
|
||||
&.activity + .dropmenu {
|
||||
display: block;
|
||||
}
|
||||
&.item-icon-back {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -521,4 +599,23 @@ export default {
|
||||
.editor-body ol {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
/* fade-transform */
|
||||
.fade-transform-leave-active,
|
||||
.fade-transform-enter-active {
|
||||
transition: all 300;
|
||||
}
|
||||
|
||||
.fade-transform-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50px);
|
||||
}
|
||||
|
||||
.fade-transform-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
}
|
||||
.fade-transform-active {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div
|
||||
class="information-bar"
|
||||
@click="gotoMessages"
|
||||
:class="{ active: activePath == 'message', unread: unreadCount > 0 }"
|
||||
:class="{ active: activePath == 'message', unread: unreadConversationCount > 0 }"
|
||||
>
|
||||
<img alt="freeleaps logo" src="@/assets/message.png" />
|
||||
</div>
|
||||
@ -15,7 +15,7 @@
|
||||
:class="activePath == 'Workspace' ? 'active' : ''"
|
||||
>
|
||||
<svg-icon icon="workspace" class-name="icon" />
|
||||
Workspace
|
||||
{{ $t('Workspace') }}
|
||||
</button>
|
||||
<button
|
||||
class="navigation-item"
|
||||
@ -23,7 +23,7 @@
|
||||
:class="activePath == 'Requests' ? 'active' : ''"
|
||||
>
|
||||
<svg-icon icon="requests" class-name="icon" />
|
||||
Requests
|
||||
{{ $t('Requests') }}
|
||||
</button>
|
||||
<button
|
||||
class="navigation-item"
|
||||
@ -31,7 +31,7 @@
|
||||
:class="activePath == 'Providers' ? 'active' : ''"
|
||||
>
|
||||
<svg-icon icon="providers" class-name="icon" />
|
||||
Providers
|
||||
{{ $t('Providers') }}
|
||||
</button>
|
||||
<button
|
||||
class="navigation-item"
|
||||
@ -39,7 +39,7 @@
|
||||
:class="activePath == 'Post' ? 'active' : ''"
|
||||
>
|
||||
<svg-icon icon="post" class-name="icon" />
|
||||
Post
|
||||
{{ $t('Post') }}
|
||||
</button>
|
||||
<div class="form-check form-switch header-switch-container">
|
||||
<input
|
||||
@ -50,10 +50,10 @@
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="personal-earning-now-checkbox">
|
||||
<span>Providing service</span>
|
||||
<span>{{ $t('Providing service') }}</span>
|
||||
</label>
|
||||
<div class="header-switch-desc">
|
||||
Please go to profile page to add money receiving method
|
||||
{{ $t('Please go to profile page to add money receiving method') }}
|
||||
</div>
|
||||
</div>
|
||||
<laguage-switch class="laguage-switch" />
|
||||
@ -68,17 +68,17 @@
|
||||
/>
|
||||
<ul class="dropdown-menu" aria-labelledby="accountButton">
|
||||
<li>
|
||||
<button class="account-menu-button" @click="gotoProfile">Profile</button>
|
||||
<button class="account-menu-button" @click="gotoProfile">{{ $t('Profile') }}</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="account-menu-button" @click="gotoHistory">History</button>
|
||||
<button class="account-menu-button" @click="gotoHistory">{{ $t('History') }}</button>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider" />
|
||||
</li>
|
||||
<li>
|
||||
<button class="account-menu-button" @click="signout">
|
||||
Log out ({{ userIdentityNote }})
|
||||
{{ $t('Log out') }} ({{ userIdentityNote }})
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
@ -94,8 +94,8 @@ export default {
|
||||
name: 'HeaderGuest',
|
||||
components: { LaguageSwitch },
|
||||
computed: {
|
||||
unreadCount() {
|
||||
return this.$store.getters['basic/unreadCount']
|
||||
unreadConversationCount() {
|
||||
return this.$store.getters['basic/unreadConversationCount']
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
1
frontend/src/icons/fe-back.svg
Normal file
1
frontend/src/icons/fe-back.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16315" width="64" height="64"><path d="M189.056 547.072l554.666667 384A42.666667 42.666667 0 0 0 810.666667 896V128a42.666667 42.666667 0 0 0-66.944-35.114667l-554.666667 384a42.794667 42.794667 0 0 0 0 70.186667z" p-id="16316"></path></svg>
|
||||
|
After Width: | Height: | Size: 325 B |
1
frontend/src/icons/fe-link.svg
Normal file
1
frontend/src/icons/fe-link.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4251" width="64" height="64"><path d="M607.934444 417.856853c-6.179746-6.1777-12.766768-11.746532-19.554358-16.910135l-0.01228 0.011256c-6.986111-6.719028-16.47216-10.857279-26.930349-10.857279-21.464871 0-38.864146 17.400299-38.864146 38.864146 0 9.497305 3.411703 18.196431 9.071609 24.947182l-0.001023 0c0.001023 0.001023 0.00307 0.00307 0.005117 0.004093 2.718925 3.242857 5.953595 6.03853 9.585309 8.251941 3.664459 3.021823 7.261381 5.997598 10.624988 9.361205l3.203972 3.204995c40.279379 40.229237 28.254507 109.539812-12.024871 149.820214L371.157763 796.383956c-40.278355 40.229237-105.761766 40.229237-146.042167 0l-3.229554-3.231601c-40.281425-40.278355-40.281425-105.809861 0-145.991002l75.93546-75.909877c9.742898-7.733125 15.997346-19.668968 15.997346-33.072233 0-23.312962-18.898419-42.211381-42.211381-42.211381-8.797363 0-16.963347 2.693342-23.725354 7.297197-0.021489-0.045025-0.044002-0.088004-0.066515-0.134053l-0.809435 0.757247c-2.989077 2.148943-5.691629 4.669346-8.025791 7.510044l-78.913281 73.841775c-74.178443 74.229608-74.178443 195.632609 0 269.758863l3.203972 3.202948c74.178443 74.127278 195.529255 74.127278 269.707698 0l171.829484-171.880649c74.076112-74.17435 80.357166-191.184297 6.282077-265.311575L607.934444 417.856853z" fill="#5D5D5D" p-id="4252"></path><path d="M855.61957 165.804257l-3.203972-3.203972c-74.17742-74.178443-195.528232-74.178443-269.706675 0L410.87944 334.479911c-74.178443 74.178443-78.263481 181.296089-4.085038 255.522628l3.152806 3.104711c3.368724 3.367701 6.865361 6.54302 10.434653 9.588379 2.583848 2.885723 5.618974 5.355985 8.992815 7.309476 0.025583 0.020466 0.052189 0.041956 0.077771 0.062422l0.011256-0.010233c5.377474 3.092431 11.608386 4.870938 18.257829 4.870938 20.263509 0 36.68962-16.428158 36.68962-36.68962 0-5.719258-1.309832-11.132548-3.645017-15.95846l0 0c-4.850471-10.891048-13.930267-17.521049-20.210297-23.802102l-3.15383-3.102664c-40.278355-40.278355-24.982998-98.79612 15.295358-139.074476l171.930791-171.830507c40.179095-40.280402 105.685018-40.280402 145.965419 0l3.206018 3.152806c40.279379 40.281425 40.279379 105.838513 0 146.06775l-75.686796 75.737962c-10.296507 7.628748-16.97358 19.865443-16.97358 33.662681 0 23.12365 18.745946 41.87062 41.87062 41.87062 8.048303 0 15.563464-2.275833 21.944801-6.211469 0.048095 0.081864 0.093121 0.157589 0.141216 0.240477l1.173732-1.083681c3.616364-2.421142 6.828522-5.393847 9.529027-8.792247l79.766718-73.603345C929.798013 361.334535 929.798013 239.981676 855.61957 165.804257z"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@ -180,5 +180,28 @@ export default {
|
||||
'Payment plan proposed by the service provider': 'Payment plan proposed by the service provider',
|
||||
'Execution plan proposed by the service provider':
|
||||
'Execution plan proposed by the service provider',
|
||||
'Proceed to workspace': 'Proceed to workspace'
|
||||
'Proceed to workspace': 'Proceed to workspace',
|
||||
|
||||
Workspace: 'Workspace',
|
||||
Requests: 'Requests',
|
||||
Providers: 'Providers',
|
||||
Post: 'Post',
|
||||
'Providing service': 'Providing service',
|
||||
'Please go to profile page to add money receiving method':
|
||||
'Please go to profile page to add money receiving method',
|
||||
History: 'History',
|
||||
'Log out': 'Log out',
|
||||
Proposals: 'Proposals',
|
||||
'Milestone(s)': 'Milestone(s)',
|
||||
Information: 'Information',
|
||||
'Operation Complete': 'Operation Complete',
|
||||
'Wait for completion': 'Wait for completion',
|
||||
'Mission complete': 'Mission complete',
|
||||
'Wait for payment': 'Wait for payment',
|
||||
'Wait for confirmation': 'Wait for confirmation',
|
||||
'Confirm receipt': 'Confirm receipt',
|
||||
Ready: 'Ready',
|
||||
'Not available': 'Not available',
|
||||
'Please input issue description': 'Please input issue description',
|
||||
'day(s)': 'day(s)'
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import cover_picture from '@/assets/images/lab-translation.png'
|
||||
export default {
|
||||
name: 'LabHome',
|
||||
components: {},
|
||||
@ -31,7 +32,28 @@ export default {
|
||||
title_text: 'Machine Translation',
|
||||
summary_text: 'Translate lanuages leverage AI power',
|
||||
icon_picture: '',
|
||||
cover_picture: 'src/assets/images/lab-translation.png'
|
||||
cover_picture: cover_picture
|
||||
},
|
||||
{
|
||||
path: 'task-completion',
|
||||
title_text: 'Task Completion',
|
||||
summary_text: 'Respone for a user prompty',
|
||||
icon_picture: '',
|
||||
cover_picture: cover_picture
|
||||
},
|
||||
{
|
||||
path: 'multiturn-chat',
|
||||
title_text: 'Multi turn chat',
|
||||
summary_text: 'Respone based on multi turn messages ',
|
||||
icon_picture: '',
|
||||
cover_picture: cover_picture
|
||||
},
|
||||
{
|
||||
path: 'image-generation',
|
||||
title_text: 'image generation',
|
||||
summary_text: 'Generate a image based on user prompt',
|
||||
icon_picture: '',
|
||||
cover_picture: cover_picture
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
52
frontend/src/pages/lab/openai/ImageGeneration.vue
Normal file
52
frontend/src/pages/lab/openai/ImageGeneration.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="input_containter">
|
||||
<input
|
||||
class="input_text"
|
||||
type="text"
|
||||
v-model="input_text"
|
||||
@keyup.enter="image_generation($event)"
|
||||
/>
|
||||
<img class="responded_image" :src="responded_image" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { LabApi } from '@/utils/index'
|
||||
export default {
|
||||
name: 'ImageGeneration',
|
||||
components: {},
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
image_generation($event) {
|
||||
LabApi.image_generation(this.input_text)
|
||||
.then((response) => {
|
||||
this.responded_image = response.data
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
input_text: null,
|
||||
responded_image: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input_containter {
|
||||
@extend .container;
|
||||
@extend .m-3;
|
||||
}
|
||||
|
||||
.input_text {
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
.responded_image {
|
||||
@extend .w-100;
|
||||
}
|
||||
</style>
|
||||
56
frontend/src/pages/lab/openai/MultiturnChat.vue
Normal file
56
frontend/src/pages/lab/openai/MultiturnChat.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="input_containter">
|
||||
<input class="input_text" type="text" v-model="input_text" @keyup.enter="lets_chat($event)" />
|
||||
<p class="responded_text">{{ responded_text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { LabApi } from '@/utils/index'
|
||||
export default {
|
||||
name: 'MultiturnChat',
|
||||
components: {},
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
lets_chat($event) {
|
||||
this.messages.push({
|
||||
role: 'user',
|
||||
content: this.input_text
|
||||
})
|
||||
LabApi.multiturn_chat(this.messages)
|
||||
.then((response) => {
|
||||
this.responded_text = response.data
|
||||
this.messages.push({
|
||||
role: 'assistant',
|
||||
content: this.responded_text
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
input_text: null,
|
||||
messages: [{ role: 'system', content: 'You are a helpful assistant.' }],
|
||||
responded_text: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input_containter {
|
||||
@extend .container;
|
||||
@extend .m-3;
|
||||
}
|
||||
|
||||
.input_text {
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
.responded_text {
|
||||
@extend .w-100;
|
||||
}
|
||||
</style>
|
||||
52
frontend/src/pages/lab/openai/TaskCompletion.vue
Normal file
52
frontend/src/pages/lab/openai/TaskCompletion.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="input_containter">
|
||||
<input
|
||||
class="input_text"
|
||||
type="text"
|
||||
v-model="input_text"
|
||||
@keyup.enter="task_completion($event)"
|
||||
/>
|
||||
<p class="responded_text">{{ responded_text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { LabApi } from '@/utils/index'
|
||||
export default {
|
||||
name: 'TaskCompletion',
|
||||
components: {},
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
task_completion($event) {
|
||||
LabApi.task_completion(this.input_text)
|
||||
.then((response) => {
|
||||
this.responded_text = response.data
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
input_text: null,
|
||||
responded_text: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input_containter {
|
||||
@extend .container;
|
||||
@extend .m-3;
|
||||
}
|
||||
|
||||
.input_text {
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
.responded_text {
|
||||
@extend .w-100;
|
||||
}
|
||||
</style>
|
||||
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div class="directories_containter">
|
||||
<div class="directory_container" v-for="(directory, index) in directories" :key="index"
|
||||
@click="view_link(directory)">
|
||||
<div
|
||||
class="directory_container"
|
||||
v-for="(directory, index) in directories"
|
||||
:key="index"
|
||||
@click="view_link(directory)"
|
||||
>
|
||||
<img class="directory_cover_image" :src="directory.cover_picture" />
|
||||
<p class="directory-title">{{ directory.title_text }}</p>
|
||||
<p class="directory-subtitle">{{ directory.summary_text }}</p>
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div v-if="blogs" class="blogs_containter">
|
||||
<div class="blog_containter" v-for="(blog, index) in blogs" :key="index" @click="view_blog(blog)">
|
||||
<div
|
||||
class="blog_containter"
|
||||
v-for="(blog, index) in blogs"
|
||||
:key="index"
|
||||
@click="view_blog(blog)"
|
||||
>
|
||||
<h2>{{ blog.blog_name }}</h2>
|
||||
<img class="blog_cover_image" :src="blog.cover_picture" />
|
||||
<p v-text="retrieve_summary(blog)"></p>
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div class="career_containter">
|
||||
<div class="career-item" v-for="(directory, index) in directories" :key="index" @click="view_link(directory)">
|
||||
<div
|
||||
class="career-item"
|
||||
v-for="(directory, index) in directories"
|
||||
:key="index"
|
||||
@click="view_link(directory)"
|
||||
>
|
||||
<p class="career-title">
|
||||
{{ directory.title_text }}
|
||||
</p>
|
||||
|
||||
@ -271,7 +271,11 @@
|
||||
class="profile-photo"
|
||||
alt="user portrait"
|
||||
id="personal-photo-operation-image"
|
||||
src="@/assets/profile.png"
|
||||
:src="
|
||||
userProfile.account.basic.photo.base64
|
||||
? userProfile.account.basic.photo.base64
|
||||
: profileUrl
|
||||
"
|
||||
/>
|
||||
<label class="profile-item-label" for="personal-photo-operation-image">{{
|
||||
$t('Portrait')
|
||||
@ -362,7 +366,11 @@
|
||||
class="user-portrait-img"
|
||||
id="personal-photo-operation-image"
|
||||
alt="user portrait"
|
||||
src="@/assets/profile.png"
|
||||
:src="
|
||||
userProfile.account.basic.photo.base64
|
||||
? userProfile.account.basic.photo.base64
|
||||
: profileUrl
|
||||
"
|
||||
v-tooltip
|
||||
title="Click to update"
|
||||
@click="selectUserPhoto()"
|
||||
@ -521,7 +529,7 @@
|
||||
<div class="panel-table-content">
|
||||
<span class="panel-table-label">{{ $t('On Freeleaps') }}</span>
|
||||
<span class="panel-table-span">
|
||||
{{ userProfile.achievemnt.activeness.days_of_staying_on }} day(s)
|
||||
{{ userProfile.achievemnt.activeness.days_of_staying_on }} {{ $t('day(s)') }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
@ -724,7 +732,7 @@
|
||||
<script>
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import { moneyCollectionTypeEnum } from '@/types/index'
|
||||
|
||||
import profileUrl from '@/assets/profile.png'
|
||||
import { UserProfileApi, elementHandler, textAreaAujuster, passwordValidator } from '@/utils/index'
|
||||
import FreeleapsEditor from '@/components/FreeleapsEditor.vue'
|
||||
|
||||
@ -764,6 +772,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
profileUrl,
|
||||
userProfile: null,
|
||||
message: null,
|
||||
accountNeedAttention: false,
|
||||
@ -886,7 +895,7 @@ export default {
|
||||
updatePhoto(base64, filename) {
|
||||
UserProfileApi.updateUserPhoto(base64, filename)
|
||||
.then((response) => {
|
||||
this.userProfile.account.basic.photo = response.data
|
||||
this.userProfile.account.basic.photo = response.data.photo
|
||||
this.updateLocalIdentityData()
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
:key="index"
|
||||
class="conversation-container"
|
||||
:class="{
|
||||
selected: current_thread?.conversation?.id === conversation.id
|
||||
selected: selConversation?.id === conversation?.id
|
||||
}"
|
||||
@click="selectConversation(conversation)"
|
||||
>
|
||||
@ -20,11 +20,10 @@
|
||||
<span class="conversation-last-update-date">{{
|
||||
getDateFromFulltimeString(conversation.create_time)
|
||||
}}</span>
|
||||
<!-- <span v-if="unreadCountMapper()" class="conversation-unreadcount">2</span> -->
|
||||
</div>
|
||||
<!-- <div class="conversation-summary-highlight-container">
|
||||
{{ conversation.summary.last_message?.message_body }}
|
||||
</div> -->
|
||||
<div class="conversation-summary-highlight-container">
|
||||
{{ conversation.last_message?.message_body }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -35,12 +34,12 @@
|
||||
{{ $t('Empty conversation') }}
|
||||
</div>
|
||||
<div class="message-panel-container">
|
||||
<div v-if="current_thread" class="message-thread-container">
|
||||
<div v-if="messages && messages.length > 0" class="message-thread-container">
|
||||
<div
|
||||
v-for="(item, index) in current_thread.conversation.messages"
|
||||
v-for="(item, index) in messages"
|
||||
:key="index"
|
||||
class="message-item-container"
|
||||
:class="item.raw_data.sender_id == userIdentityNote ? 'me' : ''"
|
||||
:class="item.sender_id == userIdentityNote ? 'me' : ''"
|
||||
>
|
||||
<div class="message-item-header-container">
|
||||
<img
|
||||
@ -49,17 +48,17 @@
|
||||
src="@/assets/profile.png"
|
||||
/>
|
||||
<span class="message-item-sender-fullname">
|
||||
{{ item.sender_profile.first_name }}
|
||||
{{ item.sender_profile.last_name }}
|
||||
{{ item.sender_firstname }}
|
||||
{{ item.sender_lastname }}
|
||||
</span>
|
||||
<span class="message-item-create-time">
|
||||
{{ getDateFromFulltimeString(item.raw_data.create_time) }}</span
|
||||
{{ getDateFromFulltimeString(item.create_time) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="message-item-message-body">{{ item.raw_data.message_body }}</div>
|
||||
<div class="message-item-message-body">{{ item.message_body }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!current_thread" class="message-thread-empty-container">
|
||||
<div v-if="!messages || messages.length == 0" class="message-thread-empty-container">
|
||||
{{ $t('Please choose conversation') }}
|
||||
</div>
|
||||
<div class="message-writing-panel-container">
|
||||
@ -68,7 +67,7 @@
|
||||
class="writing-message-input"
|
||||
type="text"
|
||||
v-model="writtenMessage"
|
||||
@keypress.enter="sendMessage(current_thread.conversation.information.conversation_id)"
|
||||
@keypress.enter="sendMessage(selConversation.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -78,58 +77,49 @@
|
||||
<script>
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import { MessageHubApi, DateUtils } from '@/utils/index'
|
||||
import { userUtils } from '@/utils/store/index'
|
||||
export default {
|
||||
components: { SvgIcon },
|
||||
name: 'MessageHub',
|
||||
props: {},
|
||||
mounted() {
|
||||
this.fetchConversations()
|
||||
},
|
||||
watch: {
|
||||
conversations: {
|
||||
handler: function (val) {
|
||||
if (val && val.length > 0) {
|
||||
//this.current_thread = val[0]
|
||||
//this.clearUnreadMessageBy(val[0])
|
||||
}
|
||||
},
|
||||
deep: false
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
data() {
|
||||
return {
|
||||
userIdentityNote: this.mnx_getUserIdentity(),
|
||||
conversations: [],
|
||||
current_thread: null,
|
||||
selConversation: null,
|
||||
messages: [],
|
||||
writtenMessage: null
|
||||
}
|
||||
},
|
||||
// computed: {
|
||||
// unreadCountMapper() {
|
||||
// return this.$store.getters['basic/unreadCountMapper']
|
||||
// }
|
||||
// },
|
||||
computed: {
|
||||
conversations() {
|
||||
return this.$store.getters['basic/conversations']
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
conversations(n_val) {
|
||||
if (!this.selConversation && n_val[0]) {
|
||||
this.selConversation = n_val[0]
|
||||
this.messages = n_val[0].messages || []
|
||||
this.clearUnreadMessageBy(n_val[0])
|
||||
} else {
|
||||
if (this.selConversation.id === n_val?.[0]?.id) {
|
||||
this.messages = n_val[0].messages || []
|
||||
this.clearUnreadMessageBy(n_val[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchConversations() {
|
||||
MessageHubApi.fetchConversations(new Date('01 Jan 1970 00:00:00 GMT').toISOString())
|
||||
.then((response) => {
|
||||
this.conversations = response.data.conversations
|
||||
//TEST
|
||||
this.conversations.forEach((conversation) => {
|
||||
this.fetchMessageForConversation(conversation.id)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
})
|
||||
},
|
||||
fetchMessageForConversation(conversation_id) {
|
||||
const jwt = userUtils.getJwtToken()
|
||||
MessageHubApi.fetchMessages(
|
||||
conversation_id,
|
||||
new Date('01 Jan 1975 00:00:00 GMT').toISOString()
|
||||
new Date('01 Jan 1975 00:00:00 GMT').toISOString(),
|
||||
jwt
|
||||
)
|
||||
.then((response) => {
|
||||
messages = response.data
|
||||
this.messages = response?.data || []
|
||||
console.log('Received message for conversation:', conversation_id)
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -137,20 +127,26 @@ export default {
|
||||
})
|
||||
},
|
||||
selectConversation(conversation) {
|
||||
this.current_thread = conversation
|
||||
this.selConversation = conversation
|
||||
this.fetchMessageForConversation(conversation.id)
|
||||
this.clearUnreadMessageBy(conversation)
|
||||
},
|
||||
clearUnreadMessageBy(current) {
|
||||
const sender = current.conversation.messages?.[0].raw_data.sender_id
|
||||
if (sender) {
|
||||
this.$store.dispatch('basic/readMessageBy', sender)
|
||||
if (current.unread) {
|
||||
this.$store.dispatch('basic/readMessageBy', current.id)
|
||||
}
|
||||
},
|
||||
sendMessage(conversation_id) {
|
||||
MessageHubApi.sendMessageToConversation(conversation_id, this.writtenMessage)
|
||||
const jwt = userUtils.getJwtToken()
|
||||
MessageHubApi.sendMessageToConversation(conversation_id, this.writtenMessage, jwt)
|
||||
.then((response) => {
|
||||
let new_message = response.data
|
||||
this.current_thread.conversation.messages.push(new_message)
|
||||
this.messages.push({
|
||||
...new_message.raw_data,
|
||||
sender_firstname: new_message.sender_profile.first_name,
|
||||
sender_lastname: new_message.sender_profile.last_name,
|
||||
sender_photo: new_message.sender_profile.photo
|
||||
})
|
||||
this.writtenMessage = null
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -218,6 +214,7 @@ export default {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.conversation-last-update-date {
|
||||
|
||||
@ -34,7 +34,8 @@
|
||||
>{{ $t('Stay on Freeleaps') }}</label
|
||||
>
|
||||
<span class="provider-stay-on-freeleaps-span" id="provider-stay-on-freeleaps">
|
||||
{{ provider.activeness_achievement.days_of_staying_on }} day(s)</span
|
||||
{{ provider.activeness_achievement.days_of_staying_on }}
|
||||
{{ $t('day(s)') }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="provider-delivered-projects-container">
|
||||
@ -101,7 +102,8 @@
|
||||
$t('Project delivering time')
|
||||
}}</label>
|
||||
<span class="dd-project-span" id="delivery-time-per-project">
|
||||
{{ provider.provider_deliveries.delivering_time_per_project_in_day }} day(s)
|
||||
{{ provider.provider_deliveries.delivering_time_per_project_in_day }}
|
||||
{{ $t('day(s)') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
@ -445,11 +447,6 @@ export default {
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
// .statistics-content-label {
|
||||
// @extend .label-text-light;
|
||||
// @extend .w-100;
|
||||
// }
|
||||
|
||||
.statistics-content-container {
|
||||
@extend .flex-colum-container;
|
||||
padding: 0;
|
||||
|
||||
@ -77,11 +77,23 @@
|
||||
{{ $t('Propose') }}
|
||||
</button>
|
||||
<div class="request-description-content" v-html="request.content"></div>
|
||||
<div v-for="(file, index) in request.attached_files" :key="index">
|
||||
<button @click="previewAttachedFile(request.id, file.document_id)">
|
||||
<div
|
||||
class="pdf-actions"
|
||||
v-for="(file, index) in request.attached_files"
|
||||
:key="index"
|
||||
>
|
||||
<button
|
||||
class="btn btn-link"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#pdf-viewer"
|
||||
@click="previewAttachedFile(request.id, file.document_id, file.file_name)"
|
||||
>
|
||||
{{ $t('Preview') }}{{ file.file_name }}
|
||||
</button>
|
||||
<button @click="downloadAttachedFile(request.id, file.document_id)">
|
||||
<button
|
||||
class="btn btn-link"
|
||||
@click="downloadAttachedFile(request.id, file.document_id)"
|
||||
>
|
||||
{{ $t('Download') }}{{ file.file_name }}
|
||||
</button>
|
||||
</div>
|
||||
@ -119,7 +131,7 @@
|
||||
>
|
||||
<span class="issuer-achievement-stay-content-text">
|
||||
{{ request.issuer_achievement.activeness.days_of_staying_on }}
|
||||
day(s)</span
|
||||
{{ $t('day(s)') }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@ -179,12 +191,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="modal fade"
|
||||
id="pdf-viewer"
|
||||
tabindex="-1"
|
||||
aria-labelledby="pdf-viewer-label"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="pdf-viewer-label">{{ pdfDocument.title }}</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<PDFReader :doc="pdfDocument.doc" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { RequestHubApi, WorksapceApi, DateUtils, requestHubUtils } from '@/utils/index'
|
||||
import { proposingModelEnum } from '@/types/index'
|
||||
import PDFReader from '@/components/PDFReader.vue'
|
||||
export default {
|
||||
components: { PDFReader },
|
||||
name: 'RequestHub',
|
||||
props: {},
|
||||
mounted() {
|
||||
@ -193,7 +231,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
requestGroups: [],
|
||||
message: null
|
||||
message: null,
|
||||
pdfDocument: {}
|
||||
}
|
||||
},
|
||||
|
||||
@ -214,17 +253,16 @@ export default {
|
||||
getDateFromFulltimeString(fulltime) {
|
||||
return DateUtils.FromJsonToDateString(fulltime)
|
||||
},
|
||||
previewAttachedFile(request_id, document_id) {
|
||||
// !!! SHOULD NOT use PdfContentViewer which is designed for unlogged in users.
|
||||
// !!! Instead, should have a dedicated pdf viewer which should follow the figma design.
|
||||
// WorksapceApi.fetchAttachedFileAsMediaData(request_id, document_id)
|
||||
// .then((response) => {
|
||||
// let media_data = response.data
|
||||
// console.log(media_data)
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// this.mnx_backendErrorHandler(error)
|
||||
// })
|
||||
previewAttachedFile(request_id, document_id, title) {
|
||||
this.pdfDocument.title = title
|
||||
WorksapceApi.fetchAttachedFileAsMediaData(request_id, document_id)
|
||||
.then((response) => {
|
||||
let media_data = response.data
|
||||
this.pdfDocument.doc = { url: media_data }
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
})
|
||||
},
|
||||
downloadAttachedFile(request_id, document_id) {
|
||||
WorksapceApi.fetchAttachedFileAsDownload(request_id, document_id)
|
||||
@ -413,4 +451,16 @@ export default {
|
||||
@extend .text-start;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pdf-actions {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
|
||||
.btn-link {
|
||||
padding: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -12,10 +12,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
UserProfileApi
|
||||
// userProfileValidator,
|
||||
} from '@/utils/index'
|
||||
export default {
|
||||
name: 'ProposalSubmitted',
|
||||
props: {
|
||||
|
||||
@ -199,7 +199,7 @@
|
||||
/>
|
||||
<label :for="`stage-duration-content-${index}`">{{ $t('Duration') }}</label>
|
||||
</div>
|
||||
<span class="btn-start">day(s)</span>
|
||||
<span class="btn-start">{{ $t('day(s)') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -221,36 +221,6 @@
|
||||
<button class="stage-item-delete-button">
|
||||
<svg-icon v-if="index != 0" icon="delete" @click="removeStage(index)" />
|
||||
</button>
|
||||
<!-- <div class="stage-item-content-container" id="stage-item-content">
|
||||
<div class="stage-payment-container">
|
||||
<label class="stage-content-label" for="stage-payment-content">Payment</label>
|
||||
<div class="stage-payment-content-container" id="stage-payment-content">
|
||||
<input
|
||||
type="text"
|
||||
class="stage-payment-content-text"
|
||||
id="stage-payment-content-text"
|
||||
v-model="stage.payment"
|
||||
/>
|
||||
<span class="stage-payment-content-span"> {{ stage.currency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-duration-container">
|
||||
<label class="stage-content-label" for="stage-duration-content">Duration</label>
|
||||
<div class="stage-duration-content-container" id="stage-duration-content">
|
||||
<input
|
||||
type="text"
|
||||
class="stage-duration-content-text"
|
||||
v-model="stage.duration_in_days"
|
||||
/>
|
||||
<span class="stage-duration-content-span"> day(s)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-note-container">
|
||||
<label class="stage-content-label" for="stage-note-content">Notes</label>
|
||||
<input class="stage-note-content-text" id="stage-note-content" v-model="stage.note" />
|
||||
</div>
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="stage-more-action-container">
|
||||
<button class="stage-add-more-button" @click="addStage()">
|
||||
@ -266,7 +236,7 @@
|
||||
{{ $t('Total payment') }}:{{ summary.total_payment }} {{ summary.currency }}
|
||||
</span>
|
||||
<span id="summary-total-duration-content">
|
||||
{{ $t('Total duration') }}:{{ summary.total_duration_in_days }} day(s)
|
||||
{{ $t('Total duration') }}:{{ summary.total_duration_in_days }} {{ $t('day(s)') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -350,12 +320,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// loadTemplates() {
|
||||
// this.mnx_navToLoadTemplates()
|
||||
// },
|
||||
// copyExisting() {
|
||||
// this.mnx_navToCopyProposals()
|
||||
// },
|
||||
isUserInCNY() {
|
||||
return window.location.host.includes('localhost') || window.location.href.includes('com.cn')
|
||||
},
|
||||
|
||||
@ -207,7 +207,7 @@
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-body" v-if="isOngoingProject(project)">
|
||||
<div class="accordion-body inline-accordion-body" v-if="isOngoingProject(project)">
|
||||
<div class="project-invite-collaborator-containter">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
@ -225,23 +225,15 @@
|
||||
data-bs-parent="#collapse-project-invite-collaborator"
|
||||
>
|
||||
<div class="project-invite-collaborator-form-container">
|
||||
<div class="project-invite-collaborator-form">
|
||||
<label class="project-item-label">{{
|
||||
$t('Input E-mail to invite other')
|
||||
}}</label>
|
||||
<input
|
||||
class="project-invite-collaborator-input"
|
||||
v-model="newInviteCollaborator[project_index]"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="project-invite-collaborator-action-button"
|
||||
@click="
|
||||
<input
|
||||
type="text"
|
||||
v-model="newInviteCollaborator[project_index]"
|
||||
:placeholder="$t('Input E-mail to invite other')"
|
||||
@keydown.enter="
|
||||
inviteCollaborator(project.project_id, newInviteCollaborator[project_index])
|
||||
"
|
||||
>
|
||||
{{ $t('Submit') }}
|
||||
</button>
|
||||
/>
|
||||
<svg-icon icon="msg-enter" class-name="project-invite-enter" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -256,7 +248,7 @@
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-project-milestone"
|
||||
>
|
||||
<div class="project-milestone-bar-container">
|
||||
<div class="project-milestone-bar-container dashed-container">
|
||||
<div class="project-milestone-bar-progress">
|
||||
<label class="project-item-label">{{ $t('Progress') }}</label>
|
||||
<p class="project-item-text">
|
||||
@ -296,65 +288,92 @@
|
||||
class="accordion-collapse collapse"
|
||||
data-bs-parent="#collapse-project-milestone"
|
||||
>
|
||||
<div class="project-milestones-containter">
|
||||
<div
|
||||
class="project-milestone-container"
|
||||
<table class="project-milestones-table">
|
||||
<tbody
|
||||
v-for="milestone in project.project.progress.milestones"
|
||||
:key="milestone.index"
|
||||
:id="'project-milestone-' + milestone.index"
|
||||
>
|
||||
<div class="project-milestone-index">
|
||||
<label class="project-item-label">{{ $t('Milestone') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ milestone.index }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="project-milestone-description">
|
||||
<label class="project-item-label">{{ $t('Description') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ milestone.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="project-milestone-status">
|
||||
<label class="project-item-label">{{ $t('Status') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ fromIntToMilestoneStatus(milestone.status) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="project-milestone-payment">
|
||||
<label class="project-item-label">{{ $t('Payment') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ milestone.actual_paid }} / {{ milestone.expected_payment }}
|
||||
{{ project.project.progress.payment_currency }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="project-milestone-update">
|
||||
<label class="project-item-label">{{ $t('Update') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ getDateFromFulltimeString(milestone.update_time) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="project-milestone-action-button-container">
|
||||
<button
|
||||
class="project-milestone-action-button"
|
||||
:disabled="isMilestoneActionButtonDisabled(project.project, milestone)"
|
||||
:hidden="isMilestoneActionButtonHidden(project.project, milestone)"
|
||||
@click="handleMilestoneAction(project.project, milestone)"
|
||||
>
|
||||
{{ fetchMilestoneActionButtonText(project.project, milestone) }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="this.qrCode.index === milestone.index">
|
||||
<img width="100" height="100" :src="this.qrCode.imageUrl" />
|
||||
<button
|
||||
class="project-milestone-payment-confirm-button"
|
||||
@click="handlePaymentAction(project.project, milestone)"
|
||||
>
|
||||
{{ $t('Mark As Paid') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="project-milestones-table-content">
|
||||
<span class="project-milestones-table-label">{{
|
||||
$t('Milestone')
|
||||
}}</span>
|
||||
<span class="project-milestones-table-span">{{
|
||||
milestone.index
|
||||
}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="project-milestones-table-content">
|
||||
<span class="project-milestones-table-label">{{
|
||||
$t('Description')
|
||||
}}</span>
|
||||
<span class="project-milestones-table-span">{{
|
||||
milestone.description
|
||||
}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="project-milestones-table-content">
|
||||
<span class="project-milestones-table-label">{{ $t('Status') }}</span>
|
||||
<span class="project-milestones-table-span">{{
|
||||
fromIntToMilestoneStatus(milestone.status)
|
||||
}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="project-milestones-table-content">
|
||||
<span class="project-milestones-table-label">{{
|
||||
$t('Payment')
|
||||
}}</span>
|
||||
<span class="project-milestones-table-span">
|
||||
{{ milestone.actual_paid }} / {{ milestone.expected_payment }}
|
||||
{{ project.project.progress.payment_currency }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="project-milestones-table-content">
|
||||
<span class="project-milestones-table-label">{{ $t('Update') }}</span>
|
||||
<span class="project-milestones-table-span">{{
|
||||
getDateFromFulltimeString(milestone.update_time)
|
||||
}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="project-milestones-table-content">
|
||||
<span class="project-milestones-table-label">{{ $t('Action') }}</span>
|
||||
<button
|
||||
class="btn btn-link"
|
||||
:disabled="
|
||||
isMilestoneActionButtonDisabled(project.project, milestone)
|
||||
"
|
||||
:hidden="isMilestoneActionButtonHidden(project.project, milestone)"
|
||||
@click="handleMilestoneAction(project.project, milestone)"
|
||||
>
|
||||
{{ fetchMilestoneActionButtonText(project.project, milestone) }}
|
||||
</button>
|
||||
<!-- <span class="project-milestones-table-span">{{ getDateFromFulltimeString(milestone.update_time) }}</span> -->
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="this.qrCode.index === milestone.index">
|
||||
<td colspan="6">
|
||||
<div class="project-milestones-qrcode-content">
|
||||
<img :src="this.qrCode.imageUrl" alt="freeleaps" />
|
||||
<button
|
||||
class="project-milestones-qrcode-button"
|
||||
@click="handlePaymentAction(project.project, milestone)"
|
||||
>
|
||||
{{ $t('Mark As Paid') }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
@ -367,7 +386,7 @@
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-project-code"
|
||||
>
|
||||
<div class="project-code-bar-container">
|
||||
<div class="project-code-bar-container dashed-container">
|
||||
<div class="project-code-git-status">
|
||||
<label class="project-item-label">{{ $t('Code Depot') }}</label>
|
||||
<p class="project-item-text">{{ getGitStatus(project) }}</p>
|
||||
@ -408,9 +427,12 @@
|
||||
{{ $t('copy git url') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="project-code-statistics-container">
|
||||
<!-- <div class="project-code-statistics-container">
|
||||
<button class="project-code-manage-button">{{ $t('Manage') }}</button>
|
||||
{{ $t('TO BE IMPLEMENTED.') }}
|
||||
</div> -->
|
||||
<div class="chart-container">
|
||||
<v-chart :option="currentChartData" autoresize />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -425,7 +447,7 @@
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-project-issue"
|
||||
>
|
||||
<div class="project-issue-bar-container">
|
||||
<div class="project-issue-bar-container dashed-container">
|
||||
<div class="project-issue-open-issues">
|
||||
<label class="project-item-label">{{ $t('Open issues') }}</label>
|
||||
<p class="project-item-text">
|
||||
@ -452,11 +474,7 @@
|
||||
class="accordion-collapse collapse"
|
||||
data-bs-parent="#collapse-project-issue"
|
||||
>
|
||||
<!-- <div class="project-issue-statistics-container">
|
||||
<button class="project-issue-manage-button">Manage</button>
|
||||
TO BE IMPLEMENTED.
|
||||
</div> -->
|
||||
<div class="project-new-issue-containter">
|
||||
<div class="project-invite-collaborator-containter">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
@ -465,7 +483,7 @@
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-project-issue"
|
||||
>
|
||||
<div class="project-add-new-issue">+ {{ $t('Add Issue') }}</div>
|
||||
<div class="project-invite-collaborator">+ {{ $t('Add Issue') }}</div>
|
||||
</button>
|
||||
<div
|
||||
id="collapse-project-new-issue"
|
||||
@ -477,15 +495,8 @@
|
||||
<label class="project-item-label">{{
|
||||
$t('New issue description')
|
||||
}}</label>
|
||||
<textarea
|
||||
class="project-new-issue-textarea"
|
||||
type="text"
|
||||
v-model="newIssueDescriptions[project_index]"
|
||||
/>
|
||||
</div>
|
||||
<div class="project-new-issue-action-container">
|
||||
<button
|
||||
class="project-new-issue-action-button"
|
||||
class="project-issue-description-btn"
|
||||
@click="
|
||||
postNewIssue(
|
||||
project.request.product_id,
|
||||
@ -497,10 +508,14 @@
|
||||
{{ $t('Submit') }}
|
||||
</button>
|
||||
</div>
|
||||
<textarea type="text" v-model="newIssueDescriptions[project_index]" />
|
||||
<!-- <div class="project-new-issue-action-container">
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="project-issues-containter">
|
||||
<div>
|
||||
<div
|
||||
class="project-issue-container"
|
||||
v-for="(issue, issue_index) in project.project.issue.open_issues"
|
||||
@ -517,21 +532,23 @@
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-project-issue-details"
|
||||
>
|
||||
<div class="project-issue-title">
|
||||
<label class="project-item-label">{{ $t('Issue title') }}</label>
|
||||
<p class="project-item-text">{{ issue.title }}</p>
|
||||
</div>
|
||||
<div class="project-issue-status">
|
||||
<label class="project-item-label">{{ $t('Status') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ fromIntToProjectIssueStatus(issue.status) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="project-issue-update">
|
||||
<label class="project-item-label">{{ $t('Last updated') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ getDateFromFulltimeString(issue.update_time) }}
|
||||
</p>
|
||||
<div class="project-issue-header dashed-container">
|
||||
<div class="project-issue-title">
|
||||
<label class="project-item-label">{{ $t('Issue title') }}</label>
|
||||
<p class="project-item-text">{{ issue.title }}</p>
|
||||
</div>
|
||||
<div class="project-issue-status">
|
||||
<label class="project-item-label">{{ $t('Status') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ fromIntToProjectIssueStatus(issue.status) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="project-issue-update">
|
||||
<label class="project-item-label">{{ $t('Last updated') }}</label>
|
||||
<p class="project-item-text">
|
||||
{{ getDateFromFulltimeString(issue.update_time) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</h2>
|
||||
@ -541,34 +558,38 @@
|
||||
:data-bs-parent="'#collapse-project-issue-details' + issue_index"
|
||||
>
|
||||
<div class="project-issue-description-container">
|
||||
<label class="project-item-label">{{
|
||||
$t('Issue description')
|
||||
}}</label>
|
||||
<div class="project-issue-description">
|
||||
<label class="project-item-label">{{
|
||||
$t('Issue description')
|
||||
}}</label>
|
||||
<button
|
||||
:hidden="
|
||||
!showIssueActionButton(project.project, issue, 'Resolve')
|
||||
"
|
||||
class="project-issue-description-btn"
|
||||
@click="setProjectIssueStatus(issue.id, 1)"
|
||||
>
|
||||
{{ $t('Resolve') }}
|
||||
</button>
|
||||
<button
|
||||
:hidden="
|
||||
!showIssueActionButton(project.project, issue, 'Confirm')
|
||||
"
|
||||
class="project-issue-description-btn"
|
||||
@click="setProjectIssueStatus(issue.id, 2)"
|
||||
>
|
||||
{{ $t('Confirm') }}
|
||||
</button>
|
||||
<button
|
||||
:hidden="!showIssueActionButton(project.project, issue, 'Reopen')"
|
||||
class="project-issue-description-btn"
|
||||
@click="setProjectIssueStatus(issue.id, 0)"
|
||||
>
|
||||
{{ $t('Reopen') }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="project-item-text">{{ issue.description }}</p>
|
||||
</div>
|
||||
<div class="project-issue-action-container">
|
||||
<button
|
||||
:hidden="!showIssueActionButton(project.project, issue, 'Resolve')"
|
||||
class="project-issue-action-button"
|
||||
@click="setProjectIssueStatus(issue.id, 1)"
|
||||
>
|
||||
{{ $t('Resolve') }}
|
||||
</button>
|
||||
<button
|
||||
:hidden="!showIssueActionButton(project.project, issue, 'Confirm')"
|
||||
class="project-issue-action-button"
|
||||
@click="setProjectIssueStatus(issue.id, 2)"
|
||||
>
|
||||
{{ $t('Confirm') }}
|
||||
</button>
|
||||
<button
|
||||
:hidden="!showIssueActionButton(project.project, issue, 'Reopen')"
|
||||
class="project-issue-action-button"
|
||||
@click="setProjectIssueStatus(issue.id, 0)"
|
||||
>
|
||||
{{ $t('Reopen') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -603,8 +624,15 @@ import {
|
||||
convertIntoToMilestoneStatus,
|
||||
convertIntoToProjectIssueStatus
|
||||
} from '@/types/index'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { LineChart } from 'echarts/charts'
|
||||
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
|
||||
import VChart from 'vue-echarts'
|
||||
use([CanvasRenderer, LineChart, LegendComponent, GridComponent, TooltipComponent])
|
||||
export default {
|
||||
name: 'Workspace',
|
||||
components: { VChart },
|
||||
props: {},
|
||||
mounted() {
|
||||
this.fetchView()
|
||||
@ -620,7 +648,26 @@ export default {
|
||||
imageUrl: null,
|
||||
index: null
|
||||
},
|
||||
|
||||
currentChartData: {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||
type: 'line',
|
||||
areaStyle: { color: 'rgba(63,73,255,0.1)' },
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
connectNulls: true
|
||||
}
|
||||
]
|
||||
},
|
||||
downstream_web_socket: null
|
||||
}
|
||||
},
|
||||
@ -670,14 +717,14 @@ export default {
|
||||
getTitleForItemInfo(project) {
|
||||
switch (project.status) {
|
||||
case projectStatusEnum.RECRUITING:
|
||||
return 'Proposals'
|
||||
return this.$t('Proposals')
|
||||
case projectStatusEnum.PENDING:
|
||||
case projectStatusEnum.REJECTED:
|
||||
return 'Issuer'
|
||||
return this.$t('Issuer')
|
||||
case projectStatusEnum.ONGOING:
|
||||
return 'Milestone(s)'
|
||||
return this.$t('Milestone(s)')
|
||||
default:
|
||||
return 'Information'
|
||||
return this.$t('Information')
|
||||
}
|
||||
},
|
||||
getContentForItemInfo(project) {
|
||||
@ -779,6 +826,10 @@ export default {
|
||||
WorksapceApi.setMillestoneStatus(project.id, milestone.index, milestoneStatusEnum.PAID)
|
||||
.then((response) => {
|
||||
this.fetchView()
|
||||
this.qrCode = {
|
||||
imageUrl: null,
|
||||
index: null
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
@ -786,25 +837,25 @@ export default {
|
||||
},
|
||||
fetchMilestoneActionButtonText(project, milestone) {
|
||||
if (milestone.index < project.current_milestone) {
|
||||
return 'Operation Complete'
|
||||
return this.$t('Operation Complete')
|
||||
} else if (milestone.index === project.current_milestone) {
|
||||
if (milestone.status === milestoneStatusEnum.IMPLEMENTING) {
|
||||
if (project.issuers.includes(project.current_user_id)) {
|
||||
return 'Wait for completion'
|
||||
return this.$t('Wait for completion')
|
||||
} else {
|
||||
return 'Mission complete'
|
||||
return this.$t('Mission complete')
|
||||
}
|
||||
} else if (milestone.status === milestoneStatusEnum.OUTSTANDING) {
|
||||
if (project.issuers.includes(project.current_user_id)) {
|
||||
return 'Payment'
|
||||
return this.$t('Payment')
|
||||
} else {
|
||||
return 'Wait for payment'
|
||||
return this.$t('Wait for payment')
|
||||
}
|
||||
} else if (milestone.status === milestoneStatusEnum.PAID) {
|
||||
if (project.issuers.includes(project.current_user_id)) {
|
||||
return 'Wait for confirmation'
|
||||
return this.$t('Wait for confirmation')
|
||||
} else {
|
||||
return 'Confirm receipt'
|
||||
return this.$t('Confirm receipt')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -812,7 +863,7 @@ export default {
|
||||
}
|
||||
},
|
||||
getGitStatus(project) {
|
||||
return project.project?.code?.git_url ? 'Ready' : 'Not available'
|
||||
return project.project?.code?.git_url ? this.$t('Ready') : this.$t('Not available')
|
||||
},
|
||||
copyCodeGit(project) {
|
||||
if (project.project.code.git_url) {
|
||||
@ -888,7 +939,7 @@ export default {
|
||||
|
||||
postNewIssue(product_id, project_id, issue_description) {
|
||||
if (issue_description == null || issue_description == '') {
|
||||
alert('Please input issue description')
|
||||
alert(this.$t('Please input issue description'))
|
||||
return
|
||||
}
|
||||
WorksapceApi.postIssueForProduct(product_id, project_id, issue_description)
|
||||
@ -911,17 +962,17 @@ export default {
|
||||
showIssueActionButton(project, issue, button_text) {
|
||||
switch (issue.status) {
|
||||
case projectIssueStatusEnum.OPEN:
|
||||
if (button_text === 'Resolve') {
|
||||
if (button_text === this.$t('Resolve')) {
|
||||
return project.providers.includes(project.current_user_id)
|
||||
}
|
||||
break
|
||||
case 1:
|
||||
if (button_text === 'Reopen' || button_text === 'Confirm') {
|
||||
if (button_text === this.$t('Reopen') || button_text === this.$t('Confirm')) {
|
||||
return project.issuers.includes(project.current_user_id)
|
||||
}
|
||||
break
|
||||
case projectIssueStatusEnum.CLOSED:
|
||||
if (button_text === 'Reopen') {
|
||||
if (button_text === this.$t('Reopen')) {
|
||||
return project.issuers.includes(project.current_user_id)
|
||||
}
|
||||
break
|
||||
@ -1219,6 +1270,76 @@ export default {
|
||||
@extend .initiate-button;
|
||||
}
|
||||
|
||||
.project-milestones-table {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
|
||||
.project-milestones-table-content {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.project-milestones-table-label {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
line-height: 1;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.project-milestones-table-span {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #242424;
|
||||
line-height: 1;
|
||||
}
|
||||
.btn-link {
|
||||
padding: 0;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
.project-milestones-qrcode-content {
|
||||
padding: 32px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 154px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.project-milestones-qrcode-button {
|
||||
padding: 2px 6px;
|
||||
font-size: 14px;
|
||||
color: $primary;
|
||||
font-weight: bold;
|
||||
border: 1px solid $primary;
|
||||
background-color: #f3f6ff;
|
||||
box-shadow: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
|
||||
td:first-child {
|
||||
.project-milestones-table-content {
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
td:last-child {
|
||||
.project-milestones-table-content {
|
||||
padding-right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-milestones-containter {
|
||||
@extend .container;
|
||||
@extend .border;
|
||||
@ -1260,12 +1381,13 @@ export default {
|
||||
|
||||
.project-code-git-url-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-start;
|
||||
@extend .justify-content-end;
|
||||
@extend .my-3;
|
||||
}
|
||||
|
||||
.project-code-copy-git-url {
|
||||
@extend .initiate-button;
|
||||
@extend .btn;
|
||||
@extend .btn-link;
|
||||
}
|
||||
|
||||
.project-code-statistics-container {
|
||||
@ -1304,8 +1426,33 @@ export default {
|
||||
}
|
||||
|
||||
.project-issue-description-container {
|
||||
@extend .text-start;
|
||||
@extend .flex-grow-1;
|
||||
padding: 12px;
|
||||
|
||||
textarea {
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e1e1e1;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
.project-issue-description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.project-issue-description-btn {
|
||||
@extend .btn;
|
||||
@extend .btn-link;
|
||||
padding: 0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.project-issue-action-container {
|
||||
@ -1313,7 +1460,8 @@ export default {
|
||||
}
|
||||
|
||||
.project-issue-action-button {
|
||||
@extend .initiate-button;
|
||||
@extend .btn;
|
||||
@extend .btn-link;
|
||||
@extend .float-end;
|
||||
}
|
||||
|
||||
@ -1323,15 +1471,14 @@ export default {
|
||||
@extend .p-3;
|
||||
}
|
||||
|
||||
.project-issues-containter {
|
||||
@extend .container;
|
||||
@extend .border;
|
||||
}
|
||||
|
||||
.project-issue-container {
|
||||
@extend .justify-content-between;
|
||||
}
|
||||
|
||||
.project-issue-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.project-issue-title {
|
||||
@extend .text-start;
|
||||
@extend .flex-grow-1;
|
||||
@ -1357,20 +1504,58 @@ export default {
|
||||
width: 6vw;
|
||||
}
|
||||
|
||||
.inline-accordion-body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.project-invite-collaborator-containter {
|
||||
@extend .container;
|
||||
@extend .border;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
|
||||
.accordion-button {
|
||||
// border: 1px solid $primary;
|
||||
// color: $primary;
|
||||
// background-color: #F3F6FF;
|
||||
padding: 12px !important;
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-invite-collaborator {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
@extend .initiate-button;
|
||||
color: $primary;
|
||||
background-color: #f3f6ff;
|
||||
}
|
||||
|
||||
.project-invite-collaborator-form-container {
|
||||
@extend .container;
|
||||
height: 100px;
|
||||
margin: 12px;
|
||||
height: 37px;
|
||||
display: flex;
|
||||
padding: 0 12px;
|
||||
border-radius: 3px;
|
||||
align-items: center;
|
||||
border: 1px solid #e7e8eb;
|
||||
&:focus-within {
|
||||
border: 1px solid #1748f8;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
margin-right: 12px;
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.project-invite-enter {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 3px;
|
||||
background: #f3f3f5;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.project-invite-collaborator-form {
|
||||
@ -1386,4 +1571,8 @@ export default {
|
||||
@extend .initiate-button;
|
||||
@extend .float-end;
|
||||
}
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 357px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -10,10 +10,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
UserProfileApi
|
||||
// requestHubUtils,
|
||||
} from '@/utils/index'
|
||||
export default {
|
||||
name: 'RquestIssueDeposit',
|
||||
props: {
|
||||
|
||||
@ -8,10 +8,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
UserProfileApi
|
||||
// userProfileValidator,
|
||||
} from '@/utils/index'
|
||||
export default {
|
||||
name: 'RquestIssueDeposited',
|
||||
props: {
|
||||
|
||||
@ -91,7 +91,7 @@
|
||||
|
||||
<script>
|
||||
import { WorksapceApi, requestIssueUtils, DateUtils } from '@/utils/index'
|
||||
import { requestStatusEnum, convertIntoToRequestStatus } from '@/types/index'
|
||||
import { convertIntoToRequestStatus } from '@/types/index'
|
||||
export default {
|
||||
name: 'RequestManage',
|
||||
props: {
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
>
|
||||
<div class="execution-duration-containter" id="execution-duration-containter">
|
||||
<span class="execution-duration-span" id="execution-duration-span">
|
||||
{{ proposal.duration_in_day }} day(s)</span
|
||||
{{ proposal.duration_in_day }} {{ $t('day(s)') }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -66,6 +66,9 @@ import HeaderUser from '@/headers/HeaderUser.vue'
|
||||
//Lab
|
||||
import LabHome from '@/pages/lab/Home.vue'
|
||||
import TranslationHome from '@/pages/lab/translation/Home.vue'
|
||||
import TaskCompletion from '@/pages/lab/openai/TaskCompletion.vue'
|
||||
import MultiturnChat from '@/pages/lab/openai/MultiturnChat.vue'
|
||||
import ImageGeneration from '@/pages/lab/openai/ImageGeneration.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
@ -389,6 +392,24 @@ const router = createRouter({
|
||||
path: '/machine-translation',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: TranslationHome, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'task-completion',
|
||||
path: '/task-completion',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: TaskCompletion, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'multiturn-chat',
|
||||
path: '/multiturn-chat',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: MultiturnChat, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'image-generation',
|
||||
path: '/image-generation',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: ImageGeneration, footer: FooterUser, header: HeaderUser }
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
import { i18n } from '@/lang'
|
||||
import { WsConnectionFactory } from '@/utils/backend/websocket'
|
||||
import { MessageHubApi } from '@/utils/backend/messageHub'
|
||||
|
||||
const ignoreEventType = ['test']
|
||||
const GWT = new Date('01 Jan 1970 00:00:00 GMT').toISOString()
|
||||
|
||||
const basicStore = {
|
||||
namespaced: true,
|
||||
state() {
|
||||
return {
|
||||
language: 'zh',
|
||||
unreadCountMapper: [],
|
||||
conversations: [],
|
||||
unreadConversationCount: 0,
|
||||
downstream_web_socket: null
|
||||
}
|
||||
},
|
||||
@ -23,20 +28,59 @@ const basicStore = {
|
||||
() => {
|
||||
// keep
|
||||
setInterval(() => {
|
||||
state.downstream_web_socket.send(1)
|
||||
state.downstream_web_socket.send('keep alive')
|
||||
}, 1000 * 60)
|
||||
console.log('downstream_web_socket open')
|
||||
},
|
||||
(e) => {
|
||||
const data = JSON.parse(e.data)
|
||||
let unread = state.unreadCountMapper[data.sender_id]
|
||||
if (unread) {
|
||||
unread++
|
||||
} else {
|
||||
unread = 1
|
||||
// console.log('downstream_web_socket onmessage: ', data)
|
||||
if (ignoreEventType.indexOf(data.event) !== -1) {
|
||||
return
|
||||
}
|
||||
state.unreadCountMapper[data.sender_id] = unread
|
||||
console.log('downstream_web_socket onmessage: ', data, state.unreadCountMapper)
|
||||
|
||||
if (data.event === 'connected') {
|
||||
// 读取缓存
|
||||
let local_conversations, local_unreadConversationCount
|
||||
try {
|
||||
local_conversations = JSON.parse(localStorage.getItem('conversations'))
|
||||
local_unreadConversationCount = localStorage.getItem('unreadConversationCount')
|
||||
} catch (error) {
|
||||
console.log('local error', error)
|
||||
}
|
||||
if (local_conversations) {
|
||||
state.conversations = local_conversations
|
||||
state.unreadConversationCount = local_unreadConversationCount || 0
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
MessageHubApi.fetchConversations(GWT, token).then((response) => {
|
||||
const conversations = response.data.conversations || []
|
||||
let updateLength = 0
|
||||
if (data.event !== 'connected') {
|
||||
updateLength = conversations.length - state.conversations.length
|
||||
if (updateLength === 0) updateLength = 1
|
||||
for (let i = 0; i < updateLength; i++) {
|
||||
conversations[i].unread = true
|
||||
}
|
||||
}
|
||||
const conversation = conversations[0]
|
||||
if (conversation?.id) {
|
||||
MessageHubApi.fetchMessages(
|
||||
conversation.id,
|
||||
conversation.message_update_time ? conversation.message_update_time : GWT,
|
||||
token
|
||||
).then((response) => {
|
||||
conversations[0].messages = response?.data || []
|
||||
conversations[0].message_update_time = new Date().toISOString()
|
||||
state.conversations = conversations
|
||||
state.unreadConversationCount = updateLength
|
||||
localStorage.setItem('conversations', JSON.stringify(conversations))
|
||||
localStorage.setItem('unreadConversationCount', updateLength)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
() => {
|
||||
console.log('downstream_web_socket error')
|
||||
@ -46,8 +90,17 @@ const basicStore = {
|
||||
}
|
||||
)
|
||||
},
|
||||
readMessageBy(state, sender) {
|
||||
delete state.unreadCountMapper?.[sender]
|
||||
readMessageBy(state, conversation_id) {
|
||||
for (let i = 0; i < state.conversations.length; i++) {
|
||||
if (conversation_id === state.conversations[i].id) {
|
||||
const nsc = Object.assign([], state.conversations)
|
||||
nsc[i].unread = false
|
||||
state.conversations = nsc
|
||||
if (state.unreadConversationCount > 0) {
|
||||
state.unreadConversationCount = state.unreadConversationCount - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@ -65,17 +118,11 @@ const basicStore = {
|
||||
language(state) {
|
||||
return state.language
|
||||
},
|
||||
unreadCount(state) {
|
||||
let count = 0
|
||||
for (let key in state.unreadCountMapper) {
|
||||
if (state.unreadCountMapper.hasOwnProperty(key)) {
|
||||
count += state.unreadCountMapper[key]
|
||||
}
|
||||
}
|
||||
return count
|
||||
unreadConversationCount(state) {
|
||||
return state.unreadConversationCount
|
||||
},
|
||||
unreadCountMapper(state) {
|
||||
return state.unreadCountMapper
|
||||
conversations(state) {
|
||||
return state.conversations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,11 @@ class ContentApi {
|
||||
return request
|
||||
}
|
||||
static retrieve_career_directories(host) {
|
||||
const request = backendAxios.post('/api/content/retrieve-career-directories', { host: host }, {})
|
||||
const request = backendAxios.post(
|
||||
'/api/content/retrieve-career-directories',
|
||||
{ host: host },
|
||||
{}
|
||||
)
|
||||
return request
|
||||
}
|
||||
static retrieve_contact_directories(host) {
|
||||
|
||||
@ -14,5 +14,45 @@ class LabApi {
|
||||
)
|
||||
return request
|
||||
}
|
||||
static task_completion(prompt) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.post(
|
||||
'/api/lab/openai-completion',
|
||||
{
|
||||
user_prompt: prompt
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${jwt}` }
|
||||
}
|
||||
)
|
||||
return request
|
||||
}
|
||||
static image_generation(prompt) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.post(
|
||||
'/api/lab/generate-image',
|
||||
{
|
||||
user_prompt: prompt
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${jwt}` }
|
||||
}
|
||||
)
|
||||
return request
|
||||
}
|
||||
|
||||
static multiturn_chat(messages) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.post(
|
||||
'/api/lab/openai-chat',
|
||||
{
|
||||
messages: messages
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${jwt}` }
|
||||
}
|
||||
)
|
||||
return request
|
||||
}
|
||||
}
|
||||
export { LabApi }
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { backendAxios } from './axios'
|
||||
import { userUtils } from '../store/index'
|
||||
// import { userUtils } from '../store/index'
|
||||
|
||||
class MessageHubApi {
|
||||
static fetchConversations(last_update_time) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
static fetchConversations(last_update_time, jwt) {
|
||||
// let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.post(
|
||||
'/api/messages/fetch-conversations-for-user',
|
||||
{
|
||||
@ -15,8 +15,8 @@ class MessageHubApi {
|
||||
)
|
||||
return request
|
||||
}
|
||||
static fetchMessages(conversation_id, last_update_time) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
static fetchMessages(conversation_id, last_update_time, jwt) {
|
||||
// let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.post(
|
||||
'/api/messages/fetch-message-thread-for-conversation',
|
||||
{
|
||||
@ -30,8 +30,8 @@ class MessageHubApi {
|
||||
return request
|
||||
}
|
||||
|
||||
static sendMessageToConversation(conversation_id, message) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
static sendMessageToConversation(conversation_id, message, jwt) {
|
||||
// let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.post(
|
||||
'/api/messages/send-message-to-conversation',
|
||||
{
|
||||
|
||||
@ -8,6 +8,7 @@ class WsConnectionFactory {
|
||||
this.socket.onmessage = onMessage
|
||||
this.socket.onerror = onError
|
||||
this.socket.onclose = onClose
|
||||
return this.socket
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export { passwordValidator } from './passwordValidator'
|
||||
export { applicantValidator } from './applicantValidator'
|
||||
export { websiteValidator } from './websiteValidator'
|
||||
|
||||
@ -22,5 +22,5 @@ class WebsiteValidator extends TextValidator {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export { WebsiteValidator }
|
||||
const websiteValidator = new WebsiteValidator()
|
||||
export { websiteValidator }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user